visitor.rb
3.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
class Visitor < ActiveRecord::Base
belongs_to :site, :class_name => "User", :foreign_key => "site_id"
has_many :questions, :class_name => "Question", :foreign_key => "creator_id"
has_many :votes, :class_name => "Vote", :foreign_key => "voter_id"
has_many :skips, :class_name => "Skip", :foreign_key => "skipper_id"
has_many :choices, :class_name => "Choice", :foreign_key => "creator_id"
has_many :clicks
has_many :appearances, :foreign_key => "voter_id"
validates_presence_of :site, :on => :create, :message => "can't be blank"
# validates_uniqueness_of :identifier, :on => :create, :message => "must be unique", :scope => :site_id
named_scope :with_tracking, lambda { |*args| {:include => :votes, :conditions => { :identifier => args.first } }}
def owns?(question)
questions.include? question
end
def vote_for!(options)
return nil if !options || !options[:prompt] || !options[:direction]
prompt = options.delete(:prompt)
ordinality = (options.delete(:direction) == "left") ? 0 : 1
if options.delete(:skip_fraud_protection)
last_answered_appearance = self.appearances.find(:first,
:conditions => ["appearances. question_id = ? AND appearances.answerable_id IS NOT NULL", prompt.question_id],
:order => 'id DESC')
if last_answered_appearance && last_answered_appearance.answerable_type == "Skip"
options.merge!(:valid_record => false)
options.merge!(:validity_information => "Fraud protection: last visitor action was a skip")
end
end
if options.delete(:force_invalid_vote)
options.merge!(:valid_record => false)
options.merge!(:validity_information => "API call forced invalid vote")
end
associate_appearance = false
if options[:appearance_lookup]
@appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup))
return nil unless @appearance # don't allow people to fake appearance lookups
associate_appearance = true
end
choice = prompt.choices[ordinality] #we need to guarantee that the choices are in the right order (by position)
other_choices = prompt.choices - [choice]
loser_choice = other_choices.first
options.merge!(:question_id => prompt.question_id, :prompt => prompt, :voter => self, :choice => choice, :loser_choice => loser_choice)
v = votes.create!(options)
safely_associate_appearance(v, @appearance) if associate_appearance
v
end
def skip!(options)
return nil if !options || !options[:prompt]
prompt = options.delete(:prompt)
associate_appearance = false
if options[:appearance_lookup]
@appearance = prompt.appearances.find_by_lookup(options.delete(:appearance_lookup))
return nil unless @appearance
associate_appearance = true
end
if options.delete(:force_invalid_vote)
options.merge!(:valid_record => false)
options.merge!(:validity_information => "API call forced invalid vote")
end
options.merge!(:question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id => self.id)
prompt_skip = skips.create!(options)
if associate_appearance
safely_associate_appearance(prompt_skip, @appearance)
end
prompt_skip
end
# Safely associates appearance with object, but making sure no other object
# is already associated wit this appearance. object is either vote or skip.
def safely_associate_appearance(object, appearance)
# Manually update Appearance with id to ensure no double votes for a
# single appearance. Only update the answerable_id if it is NULL.
# If we can't find any rows to update, then this object should be invalid.
rows_updated = Appearance.update_all("answerable_id = #{object.id}, answerable_type = '#{object.class.to_s}', updated_at = '#{Time.now.utc.to_s(:db)}'", "id = #{appearance.id} AND answerable_id IS NULL")
if rows_updated != 1
object.update_attributes!(:valid_record => false, :validity_information => "Appearance #{appearance.id} already answered")
end
end
end