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 | 21 | |
22 | 22 | prompt = options.delete(:prompt) |
23 | 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 | 25 | if options.delete(:skip_fraud_protection) |
38 | 26 | last_answered_appearance = self.appearances.find(:first, |
... | ... | @@ -43,6 +31,13 @@ class Visitor < ActiveRecord::Base |
43 | 31 | options.merge!(:validity_information => "Fraud protection: last visitor action was a skip") |
44 | 32 | end |
45 | 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 | 42 | choice = prompt.choices[ordinality] #we need to guarantee that the choices are in the right order (by position) |
48 | 43 | other_choices = prompt.choices - [choice] |
... | ... | @@ -51,6 +46,8 @@ class Visitor < ActiveRecord::Base |
51 | 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 | 48 | v = votes.create!(options) |
49 | + safely_associate_appearance(v, @appearance) if associate_appearance | |
50 | + v | |
54 | 51 | end |
55 | 52 | |
56 | 53 | def skip!(options) |
... | ... | @@ -58,18 +55,35 @@ class Visitor < ActiveRecord::Base |
58 | 55 | |
59 | 56 | prompt = options.delete(:prompt) |
60 | 57 | |
58 | + associate_appearance = false | |
61 | 59 | if options[:appearance_lookup] |
62 | 60 | @appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup)) |
63 | 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 | 63 | end |
71 | 64 | |
72 | 65 | options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id => self.id) |
73 | 66 | prompt_skip = skips.create!(options) |
67 | + if associate_appearance | |
68 | + safely_associate_appearance(prompt_skip, @appearance) | |
69 | + end | |
70 | + prompt_skip | |
74 | 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 | 89 | end | ... | ... |
db/migrate/20111017171903_add_index_on_answerable_to_appearances.rb
0 → 100644
db/schema.rb
... | ... | @@ -9,7 +9,7 @@ |
9 | 9 | # |
10 | 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 | 14 | create_table "appearances", :force => true do |t| |
15 | 15 | t.integer "voter_id" |
... | ... | @@ -25,6 +25,7 @@ ActiveRecord::Schema.define(:version => 20110628160608) do |
25 | 25 | t.string "validity_information" |
26 | 26 | end |
27 | 27 | |
28 | + add_index "appearances", ["answerable_id", "answerable_type"], :name => "index_appearances_on_answerable_id_and_answerable_type" | |
28 | 29 | add_index "appearances", ["lookup"], :name => "index_appearances_on_lookup" |
29 | 30 | add_index "appearances", ["prompt_id"], :name => "index_appearances_on_prompt_id" |
30 | 31 | add_index "appearances", ["question_id", "voter_id"], :name => "index_appearances_on_question_id_voter_id" | ... | ... |