Commit abd621ab9f2695794acbaac06038fe581efaae06

Authored by Dhruv Kapadia
1 parent ff34c958

Non-votes files, tweaks to other csv files, better tests for csvs

app/models/appearance.rb
@@ -2,5 +2,9 @@ class Appearance < ActiveRecord::Base @@ -2,5 +2,9 @@ class Appearance < ActiveRecord::Base
2 belongs_to :voter, :class_name => "Visitor", :foreign_key => 'voter_id' 2 belongs_to :voter, :class_name => "Visitor", :foreign_key => 'voter_id'
3 belongs_to :prompt 3 belongs_to :prompt
4 belongs_to :question 4 belongs_to :question
  5 +
  6 + #technically, an appearance should either one vote or one skip, not one of both objects, but these declarations provide some useful helper methods
  7 + # we could refactor this to use rails polymorphism, but currently the foreign key is stored in the vote and skip object
5 has_one :vote 8 has_one :vote
  9 + has_one :skip
6 end 10 end
app/models/question.rb
@@ -350,7 +350,7 @@ class Question < ActiveRecord::Base @@ -350,7 +350,7 @@ class Question < ActiveRecord::Base
350 outfile = "ideamarketplace_#{self.id}_votes.csv" 350 outfile = "ideamarketplace_#{self.id}_votes.csv"
351 351
352 headers = ['Vote ID', 'Session ID', 'Question ID','Winner ID', 'Winner Text', 'Loser ID', 'Loser Text', 352 headers = ['Vote ID', 'Session ID', 'Question ID','Winner ID', 'Winner Text', 'Loser ID', 'Loser Text',
353 - 'Prompt ID', 'Left Choice ID', 'Right Choice ID', 'Created at', 'Updated at', 353 + 'Prompt ID', 'Left Choice ID', 'Right Choice ID', 'Created at', 'Updated at', 'Appearance ID',
354 'Response Time (s)', 'Session Identifier'] 354 'Response Time (s)', 'Session Identifier']
355 355
356 when 'ideas' 356 when 'ideas'
@@ -358,11 +358,11 @@ class Question < ActiveRecord::Base @@ -358,11 +358,11 @@ class Question < ActiveRecord::Base
358 headers = ['Ideamarketplace ID','Idea ID', 'Idea Text', 'Wins', 'Losses', 'Times involved in Cant Decide', 'Score', 358 headers = ['Ideamarketplace ID','Idea ID', 'Idea Text', 'Wins', 'Losses', 'Times involved in Cant Decide', 'Score',
359 'User Submitted', 'Session ID', 'Created at', 'Last Activity', 'Active', 359 'User Submitted', 'Session ID', 'Created at', 'Last Activity', 'Active',
360 'Appearances on Left', 'Appearances on Right'] 360 'Appearances on Left', 'Appearances on Right']
361 - when 'skips'  
362 - outfile = "ideamarketplace_#{self.id}_skips.csv"  
363 - headers = ['Skip ID', 'Session ID', 'Question ID','Left Choice ID', 'Left Choice Text', 361 + when 'non_votes'
  362 + outfile = "ideamarketplace_#{self.id}_non_votes.csv"
  363 + headers = ['Record Type', 'Record ID', 'Session ID', 'Question ID','Left Choice ID', 'Left Choice Text',
364 'Right Choice ID', 'Right Choice Text', 'Prompt ID', 'Appearance ID', 'Reason', 364 'Right Choice ID', 'Right Choice Text', 'Prompt ID', 'Appearance ID', 'Reason',
365 - 'Created at', 'Updated at', 'Response Time (ms)'] 365 + 'Created at', 'Updated at', 'Response Time (s)', 'Session Identifier']
366 else 366 else
367 raise "Unsupported export type: #{type}" 367 raise "Unsupported export type: #{type}"
368 end 368 end
@@ -377,15 +377,15 @@ class Question < ActiveRecord::Base @@ -377,15 +377,15 @@ class Question < ActiveRecord::Base
377 case type 377 case type
378 when 'votes' 378 when 'votes'
379 379
380 - self.votes.find_each(:include => [:prompt, :choice, :loser_choice]) do |v| 380 + self.votes.find_each(:include => [:prompt, :choice, :loser_choice, :voter]) do |v|
381 prompt = v.prompt 381 prompt = v.prompt
382 # these may not exist 382 # these may not exist
383 loser_data = v.loser_choice.nil? ? "" : "'#{v.loser_choice.data.strip}'" 383 loser_data = v.loser_choice.nil? ? "" : "'#{v.loser_choice.data.strip}'"
384 left_id = v.prompt.nil? ? "" : v.prompt.left_choice_id 384 left_id = v.prompt.nil? ? "" : v.prompt.left_choice_id
385 right_id = v.prompt.nil? ? "" : v.prompt.right_choice_id 385 right_id = v.prompt.nil? ? "" : v.prompt.right_choice_id
386 386
387 - csv << [ v.id, v.voter_id, v.question_id, v.choice_id, "\'#{v.choice.data.strip}'", v.loser_choice_id, loser_data,  
388 - v.prompt_id, left_id, right_id, v.created_at, v.updated_at, 387 + csv << [ v.id, v.voter_id, v.question_id, v.choice_id, "'#{v.choice.data.strip}'", v.loser_choice_id, loser_data,
  388 + v.prompt_id, left_id, right_id, v.created_at, v.updated_at, v.appearance_id,
389 v.time_viewed.to_f / 1000.0 , v.voter.identifier] 389 v.time_viewed.to_f / 1000.0 , v.voter.identifier]
390 end 390 end
391 391
@@ -404,14 +404,30 @@ class Question &lt; ActiveRecord::Base @@ -404,14 +404,30 @@ class Question &lt; ActiveRecord::Base
404 user_submitted , c.item.creator_id, c.created_at, c.updated_at, c.active, 404 user_submitted , c.item.creator_id, c.created_at, c.updated_at, c.active,
405 left_appearances, right_appearances] 405 left_appearances, right_appearances]
406 end 406 end
407 - when 'skips' 407 + when 'non_votes'
408 408
409 - self.skips.find_each(:include => :prompt) do |s|  
410 - prompt = s.prompt  
411 - csv << [ s.id, s.skipper_id, s.question_id, s.prompt.left_choice.id, s.prompt.left_choice.data.strip,  
412 - s.prompt.right_choice.id, s.prompt.right_choice.data.strip, s.prompt_id, s.appearance_id, s.skip_reason,  
413 - s.created_at, s.updated_at, s.time_viewed]  
414 - end 409 + self.appearances.find_each(:include => [:skip, :vote, :voter]) do |a|
  410 + # we only display skips and orphaned appearances in this csv file
  411 + unless a.vote.nil?
  412 + next
  413 + end
  414 +
  415 + #If no skip and no vote, this is an orphaned appearance
  416 + if a.skip.nil?
  417 + prompt = a.prompt
  418 + csv << [ "Orphaned Appearance", a.id, a.voter_id, a.question_id, a.prompt.left_choice.id, a.prompt.left_choice.data.strip,
  419 + a.prompt.right_choice.id, a.prompt.right_choice.data.strip, a.prompt_id, 'N/A', 'N/A',
  420 + a.created_at, a.updated_at, 'N/A', a.voter.identifier]
  421 +
  422 + else
  423 + #If this appearance belongs to a skip, show information on the skip instead
  424 + s = a.skip
  425 + prompt = s.prompt
  426 + csv << [ "Skip", s.id, s.skipper_id, s.question_id, s.prompt.left_choice.id, s.prompt.left_choice.data.strip,
  427 + s.prompt.right_choice.id, s.prompt.right_choice.data.strip, s.prompt_id, s.appearance_id, s.skip_reason,
  428 + s.created_at, s.updated_at, s.time_viewed.to_f / 1000.0 , s.skipper.identifier]
  429 + end
  430 + end
415 end 431 end
416 432
417 end 433 end
@@ -421,9 +437,9 @@ class Question &lt; ActiveRecord::Base @@ -421,9 +437,9 @@ class Question &lt; ActiveRecord::Base
421 if options[:redis_key].nil? 437 if options[:redis_key].nil?
422 raise "No :redis_key specified" 438 raise "No :redis_key specified"
423 end 439 end
424 - 440 + #The client should use blpop to listen for a key
  441 + #The client is responsible for deleting the redis key (auto expiration results failure in testing)
425 $redis.lpush(options[:redis_key], filename) 442 $redis.lpush(options[:redis_key], filename)
426 - $redis.expire(options[:redis_key], 24*60*60 * 3) #Expire in three days  
427 #TODO implement response_type == 'email' for use by customers of the API (not local) 443 #TODO implement response_type == 'email' for use by customers of the API (not local)
428 end 444 end
429 445
app/models/visitor.rb
@@ -43,6 +43,7 @@ class Visitor &lt; ActiveRecord::Base @@ -43,6 +43,7 @@ class Visitor &lt; ActiveRecord::Base
43 43
44 skip_create_options = { :question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id=> self.id, :time_viewed => time_viewed, :appearance_id => @a.id} 44 skip_create_options = { :question_id => prompt.question_id, :prompt_id => prompt.id, :skipper_id=> self.id, :time_viewed => time_viewed, :appearance_id => @a.id}
45 45
  46 + #the most common optional reason is 'skip_reason', probably want to refactor to make time viewed an optional parameter
46 prompt_skip = skips.create!(skip_create_options.merge(options)) 47 prompt_skip = skips.create!(skip_create_options.merge(options))
47 48
48 end 49 end
spec/models/question_spec.rb
@@ -62,7 +62,7 @@ describe Question do @@ -62,7 +62,7 @@ describe Question do
62 @catchup_q.save! 62 @catchup_q.save!
63 63
64 100.times.each do |num| 64 100.times.each do |num|
65 - user.create_choice("visitor identifier", @catchup_q, {:data => num, :local_identifier => "exmaple"}) 65 + user.create_choice("visitor identifier", @catchup_q, {:data => num.to_s, :local_identifier => "exmaple"})
66 end 66 end
67 end 67 end
68 it "should choose an active prompt using catchup algorithm on a large number of choices" do 68 it "should choose an active prompt using catchup algorithm on a large number of choices" do
@@ -131,10 +131,32 @@ describe Question do @@ -131,10 +131,32 @@ describe Question do
131 end 131 end
132 132
133 context "exporting data" do 133 context "exporting data" do
134 - before(:each) do 134 + before(:all) do
135 user = Factory.create(:user) 135 user = Factory.create(:user)
136 @question = Factory.create(:aoi_question, :site => user, :creator => user.default_visitor) 136 @question = Factory.create(:aoi_question, :site => user, :creator => user.default_visitor)
  137 + @question.it_should_autoactivate_ideas = true
  138 + @question.save!
  139 +
  140 + visitor = user.visitors.find_or_create_by_identifier('visitor identifier')
  141 + 100.times.each do |num|
  142 + user.create_choice(visitor.identifier, @question, {:data => num.to_s, :local_identifier => "example creator"})
  143 + end
  144 +
  145 + 200.times.each do |num|
  146 + @p = @question.picked_prompt
137 147
  148 + @a = user.record_appearance(visitor, @p)
  149 +
  150 + choice = rand(3)
  151 + case choice
  152 + when 0
  153 + user.record_vote(visitor.identifier, @a.lookup, @p, rand(2), rand(1000))
  154 + when 1
  155 + user.record_skip(visitor.identifier, @a.lookup, @p, rand(1000))
  156 + when 2
  157 + #this is an orphaned appearance, so do nothing
  158 + end
  159 + end
138 end 160 end
139 161
140 162
@@ -162,27 +184,28 @@ describe Question do @@ -162,27 +184,28 @@ describe Question do
162 filename.should match /.*ideamarketplace_#{@question.id}_votes[.]csv$/ 184 filename.should match /.*ideamarketplace_#{@question.id}_votes[.]csv$/
163 File.exists?(filename).should be_true 185 File.exists?(filename).should be_true
164 $redis.lpop(redis_key).should == filename 186 $redis.lpop(redis_key).should == filename
165 -  
166 - # Not specifying exact file syntax, it's likely to change frequently  
167 $redis.del(redis_key) # clean up 187 $redis.del(redis_key) # clean up
  188 + File.delete(filename).should be_true
168 189
169 end 190 end
170 it "should email question owner after completing an export, if email option set" do 191 it "should email question owner after completing an export, if email option set" do
171 #TODO 192 #TODO
172 end 193 end
173 194
174 - it "should export skip data to a csv file" do  
175 - filename = @question.export('skips') 195 + it "should export non vote data to a csv file" do
  196 + filename = @question.export('non_votes')
176 197
177 filename.should_not be nil 198 filename.should_not be nil
178 - filename.should match /.*ideamarketplace_#{@question.id}_skips[.]csv$/ 199 + filename.should match /.*ideamarketplace_#{@question.id}_non_votes[.]csv$/
179 File.exists?(filename).should be_true 200 File.exists?(filename).should be_true
180 201
181 # Not specifying exact file syntax, it's likely to change frequently 202 # Not specifying exact file syntax, it's likely to change frequently
182 # 203 #
183 rows = FasterCSV.read(filename) 204 rows = FasterCSV.read(filename)
184 - rows.first.should include("Skip ID") 205 + rows.first.should include("Record ID")
  206 + rows.first.should include("Record Type")
185 rows.first.should_not include("Idea ID") 207 rows.first.should_not include("Idea ID")
  208 + puts filename
186 File.delete(filename).should_not be_nil 209 File.delete(filename).should_not be_nil
187 210
188 211
@@ -199,6 +222,7 @@ describe Question do @@ -199,6 +222,7 @@ describe Question do
199 rows = FasterCSV.read(filename) 222 rows = FasterCSV.read(filename)
200 rows.first.should include("Idea ID") 223 rows.first.should include("Idea ID")
201 rows.first.should_not include("Skip ID") 224 rows.first.should_not include("Skip ID")
  225 + puts filename
202 File.delete(filename).should_not be_nil 226 File.delete(filename).should_not be_nil
203 227
204 end 228 end