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" | ... | ... |