Commit 2e9e5c92327e227f4e1d40449fbfd0726e9ab6f5

Authored by Luke Baker
1 parent 9675033d

add atomic appearance updates on vote / skip

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
... ... @@ -0,0 +1,9 @@
  1 +class AddIndexOnAnswerableToAppearances < ActiveRecord::Migration
  2 + def self.up
  3 + add_index :appearances, [:answerable_id, :answerable_type]
  4 + end
  5 +
  6 + def self.down
  7 + remove_index :appearances, [:answerable_id, :answerable_type]
  8 + end
  9 +end
... ...
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 =&gt; 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"
... ...