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 2 belongs_to :voter, :class_name => "Visitor", :foreign_key => 'voter_id'
3 3 belongs_to :prompt
4 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 8 has_one :vote
  9 + has_one :skip
6 10 end
... ...
app/models/question.rb
... ... @@ -350,7 +350,7 @@ class Question < ActiveRecord::Base
350 350 outfile = "ideamarketplace_#{self.id}_votes.csv"
351 351  
352 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 354 'Response Time (s)', 'Session Identifier']
355 355  
356 356 when 'ideas'
... ... @@ -358,11 +358,11 @@ class Question < ActiveRecord::Base
358 358 headers = ['Ideamarketplace ID','Idea ID', 'Idea Text', 'Wins', 'Losses', 'Times involved in Cant Decide', 'Score',
359 359 'User Submitted', 'Session ID', 'Created at', 'Last Activity', 'Active',
360 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 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 366 else
367 367 raise "Unsupported export type: #{type}"
368 368 end
... ... @@ -377,15 +377,15 @@ class Question < ActiveRecord::Base
377 377 case type
378 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 381 prompt = v.prompt
382 382 # these may not exist
383 383 loser_data = v.loser_choice.nil? ? "" : "'#{v.loser_choice.data.strip}'"
384 384 left_id = v.prompt.nil? ? "" : v.prompt.left_choice_id
385 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 389 v.time_viewed.to_f / 1000.0 , v.voter.identifier]
390 390 end
391 391  
... ... @@ -404,14 +404,30 @@ class Question &lt; ActiveRecord::Base
404 404 user_submitted , c.item.creator_id, c.created_at, c.updated_at, c.active,
405 405 left_appearances, right_appearances]
406 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 431 end
416 432  
417 433 end
... ... @@ -421,9 +437,9 @@ class Question &lt; ActiveRecord::Base
421 437 if options[:redis_key].nil?
422 438 raise "No :redis_key specified"
423 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 442 $redis.lpush(options[:redis_key], filename)
426   - $redis.expire(options[:redis_key], 24*60*60 * 3) #Expire in three days
427 443 #TODO implement response_type == 'email' for use by customers of the API (not local)
428 444 end
429 445  
... ...
app/models/visitor.rb
... ... @@ -43,6 +43,7 @@ class Visitor &lt; ActiveRecord::Base
43 43  
44 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 47 prompt_skip = skips.create!(skip_create_options.merge(options))
47 48  
48 49 end
... ...
spec/models/question_spec.rb
... ... @@ -62,7 +62,7 @@ describe Question do
62 62 @catchup_q.save!
63 63  
64 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 66 end
67 67 end
68 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 131 end
132 132  
133 133 context "exporting data" do
134   - before(:each) do
  134 + before(:all) do
135 135 user = Factory.create(:user)
136 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 160 end
139 161  
140 162  
... ... @@ -162,27 +184,28 @@ describe Question do
162 184 filename.should match /.*ideamarketplace_#{@question.id}_votes[.]csv$/
163 185 File.exists?(filename).should be_true
164 186 $redis.lpop(redis_key).should == filename
165   -
166   - # Not specifying exact file syntax, it's likely to change frequently
167 187 $redis.del(redis_key) # clean up
  188 + File.delete(filename).should be_true
168 189  
169 190 end
170 191 it "should email question owner after completing an export, if email option set" do
171 192 #TODO
172 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 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 200 File.exists?(filename).should be_true
180 201  
181 202 # Not specifying exact file syntax, it's likely to change frequently
182 203 #
183 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 207 rows.first.should_not include("Idea ID")
  208 + puts filename
186 209 File.delete(filename).should_not be_nil
187 210  
188 211  
... ... @@ -199,6 +222,7 @@ describe Question do
199 222 rows = FasterCSV.read(filename)
200 223 rows.first.should include("Idea ID")
201 224 rows.first.should_not include("Skip ID")
  225 + puts filename
202 226 File.delete(filename).should_not be_nil
203 227  
204 228 end
... ...