Commit 2e9e5c92327e227f4e1d40449fbfd0726e9ab6f5
1 parent
9675033d
Exists in
master
and in
1 other branch
add atomic appearance updates on vote / skip
Showing
3 changed files
with
43 additions
and
19 deletions
Show diff stats
app/models/visitor.rb
@@ -21,18 +21,6 @@ class Visitor < ActiveRecord::Base | @@ -21,18 +21,6 @@ class Visitor < ActiveRecord::Base | ||
21 | 21 | ||
22 | prompt = options.delete(:prompt) | 22 | prompt = options.delete(:prompt) |
23 | ordinality = (options.delete(:direction) == "left") ? 0 : 1 | 23 | ordinality = (options.delete(:direction) == "left") ? 0 : 1 |
24 | - | ||
25 | - if options[:appearance_lookup] | ||
26 | - @appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup)) | ||
27 | - return nil unless @appearance # don't allow people to fake appearance lookups | ||
28 | - | ||
29 | - if @appearance.answered? | ||
30 | - options.merge!(:valid_record => false) | ||
31 | - options.merge!(:validity_information => "Appearance #{@appearance.id} already answered") | ||
32 | - else | ||
33 | - options.merge!(:appearance => @appearance) | ||
34 | - end | ||
35 | - end | ||
36 | 24 | ||
37 | if options.delete(:skip_fraud_protection) | 25 | if options.delete(:skip_fraud_protection) |
38 | last_answered_appearance = self.appearances.find(:first, | 26 | last_answered_appearance = self.appearances.find(:first, |
@@ -43,6 +31,13 @@ class Visitor < ActiveRecord::Base | @@ -43,6 +31,13 @@ class Visitor < ActiveRecord::Base | ||
43 | options.merge!(:validity_information => "Fraud protection: last visitor action was a skip") | 31 | options.merge!(:validity_information => "Fraud protection: last visitor action was a skip") |
44 | end | 32 | end |
45 | end | 33 | end |
34 | + | ||
35 | + associate_appearance = false | ||
36 | + if options[:appearance_lookup] | ||
37 | + @appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup)) | ||
38 | + return nil unless @appearance # don't allow people to fake appearance lookups | ||
39 | + associate_appearance = true | ||
40 | + end | ||
46 | 41 | ||
47 | choice = prompt.choices[ordinality] #we need to guarantee that the choices are in the right order (by position) | 42 | choice = prompt.choices[ordinality] #we need to guarantee that the choices are in the right order (by position) |
48 | other_choices = prompt.choices - [choice] | 43 | other_choices = prompt.choices - [choice] |
@@ -51,6 +46,8 @@ class Visitor < ActiveRecord::Base | @@ -51,6 +46,8 @@ class Visitor < ActiveRecord::Base | ||
51 | options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :voter_id=> self.id, :choice_id => choice.id, :loser_choice_id => loser_choice.id) | 46 | options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :voter_id=> self.id, :choice_id => choice.id, :loser_choice_id => loser_choice.id) |
52 | 47 | ||
53 | v = votes.create!(options) | 48 | v = votes.create!(options) |
49 | + safely_associate_appearance(v, @appearance) if associate_appearance | ||
50 | + v | ||
54 | end | 51 | end |
55 | 52 | ||
56 | def skip!(options) | 53 | def skip!(options) |
@@ -58,18 +55,35 @@ class Visitor < ActiveRecord::Base | @@ -58,18 +55,35 @@ class Visitor < ActiveRecord::Base | ||
58 | 55 | ||
59 | prompt = options.delete(:prompt) | 56 | prompt = options.delete(:prompt) |
60 | 57 | ||
58 | + associate_appearance = false | ||
61 | if options[:appearance_lookup] | 59 | if options[:appearance_lookup] |
62 | @appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup)) | 60 | @appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup)) |
63 | return nil unless @appearance | 61 | return nil unless @appearance |
64 | - if @appearance.answered? | ||
65 | - options.merge!(:valid_record => false) | ||
66 | - options.merge!(:validity_information => "Appearance #{@appearance.id} already answered") | ||
67 | - else | ||
68 | - options.merge!(:appearance => @appearance) | ||
69 | - end | 62 | + associate_appearance = true |
70 | end | 63 | end |
71 | 64 | ||
72 | options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id => self.id) | 65 | options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id => self.id) |
73 | prompt_skip = skips.create!(options) | 66 | prompt_skip = skips.create!(options) |
67 | + if associate_appearance | ||
68 | + safely_associate_appearance(prompt_skip, @appearance) | ||
69 | + end | ||
70 | + prompt_skip | ||
74 | end | 71 | end |
72 | + | ||
73 | + # Safely associates appearance with object, but making sure no other object | ||
74 | + # is already associated wit this appearance. object is either vote or skip. | ||
75 | + def safely_associate_appearance(object, appearance) | ||
76 | + # Manually update Appearance with id to ensure no double votes for a | ||
77 | + # single appearance. Only update the answerable_id if it is NULL. | ||
78 | + # If we can't find any rows to update, then this object should be invalid. | ||
79 | + rows_updated = Appearance.update_all("answerable_id = #{object.id}, answerable_type = '#{object.class.to_s}'", "id = #{appearance.id} AND answerable_id IS NULL") | ||
80 | + if rows_updated === 1 | ||
81 | + # update relationship the ActiveRecord way, now | ||
82 | + # that we know it is safe to do so | ||
83 | + object.update_attributes!(:appearance => appearance) | ||
84 | + else | ||
85 | + object.update_attributes!(:valid_record => false, :validity_information => "Appearance #{appearance.id} already answered") | ||
86 | + end | ||
87 | + end | ||
88 | + | ||
75 | end | 89 | end |
db/migrate/20111017171903_add_index_on_answerable_to_appearances.rb
0 → 100644
db/schema.rb
@@ -9,7 +9,7 @@ | @@ -9,7 +9,7 @@ | ||
9 | # | 9 | # |
10 | # It's strongly recommended to check this file into your version control system. | 10 | # It's strongly recommended to check this file into your version control system. |
11 | 11 | ||
12 | -ActiveRecord::Schema.define(:version => 20110628160608) do | 12 | +ActiveRecord::Schema.define(:version => 20111017171903) do |
13 | 13 | ||
14 | create_table "appearances", :force => true do |t| | 14 | create_table "appearances", :force => true do |t| |
15 | t.integer "voter_id" | 15 | t.integer "voter_id" |
@@ -25,6 +25,7 @@ ActiveRecord::Schema.define(:version => 20110628160608) do | @@ -25,6 +25,7 @@ ActiveRecord::Schema.define(:version => 20110628160608) do | ||
25 | t.string "validity_information" | 25 | t.string "validity_information" |
26 | end | 26 | end |
27 | 27 | ||
28 | + add_index "appearances", ["answerable_id", "answerable_type"], :name => "index_appearances_on_answerable_id_and_answerable_type" | ||
28 | add_index "appearances", ["lookup"], :name => "index_appearances_on_lookup" | 29 | add_index "appearances", ["lookup"], :name => "index_appearances_on_lookup" |
29 | add_index "appearances", ["prompt_id"], :name => "index_appearances_on_prompt_id" | 30 | add_index "appearances", ["prompt_id"], :name => "index_appearances_on_prompt_id" |
30 | add_index "appearances", ["question_id", "voter_id"], :name => "index_appearances_on_question_id_voter_id" | 31 | add_index "appearances", ["question_id", "voter_id"], :name => "index_appearances_on_question_id_voter_id" |