Commit abd621ab9f2695794acbaac06038fe581efaae06
1 parent
ff34c958
Exists in
master
and in
1 other branch
Non-votes files, tweaks to other csv files, better tests for csvs
Showing
4 changed files
with
70 additions
and
25 deletions
Show diff stats
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 < 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 < 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 < 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 | ... | ... |