turkee 1.2.1 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,18 +5,6 @@ Seamlessly convert your Rails forms for use on Mechanical Turk. Then, easily im
5
5
  External forms are created using a simple form helper. HITs are created by issuing a rake command. Retrieving submitted response data and importing that data into your model(s) requires just one more rake command.
6
6
 
7
7
 
8
- == Rails 2
9
-
10
- I'm no longer supporting Rails 2.x. If you have Rails 2 changes, message me and we can look to maintaing a Rails 2.x branch.
11
-
12
-
13
- == Mechanical Turk API Changes
14
-
15
- Mechanical Turk is now requiring that the hitId, workerId, and the turkSubmitTo parameters be passed in along with the assignmentId and form parameters.
16
-
17
- What does this mean for you? Not much besides the fact that now when you construct your forms using turkee_form_for, you'll be passing in your entire params hash instead of just the assignment_id. The code snippet below reflect this change.
18
-
19
-
20
8
  == Install/Upgrade
21
9
 
22
10
  Add turkee to your Gemfile as a gem dependency, then do a 'bundle install':
@@ -131,6 +119,14 @@ As for Mechanical Turk approval, if the row is created and you haven't specified
131
119
  end
132
120
  end
133
121
 
122
+ 9) If all of specified assignments for a HIT have not been completed by the end of the HITs lifetime, Turkee will attempt to call the optional hit_expired class method for the model. This can be used for any cleanup logic. E.g. :
123
+ class Survey < ActiveRecord::Base
124
+ def self.hit_expired(turkee_task)
125
+ #do something
126
+ end
127
+ end
128
+
129
+
134
130
  == Advanced Usage
135
131
 
136
132
  1) You can use the params hash to pass object IDs to your forms. E.g. if you wanted to setup a form of questions about a given URL (let's call the model UrlSurvey), your code would look something like :
data/Rakefile CHANGED
@@ -5,6 +5,10 @@ Bundler::GemHelper.install_tasks
5
5
 
6
6
  $:.push File.expand_path("../lib", __FILE__)
7
7
 
8
+ task :default do
9
+ sh "rspec spec/"
10
+ end
11
+
8
12
  begin
9
13
  INSTALL_MESSAGE = %q{
10
14
  ========================================================================
@@ -0,0 +1,15 @@
1
+ class AddExpired < ActiveRecord::Migration
2
+
3
+ def self.up
4
+ unless column_exists? :turkee_tasks, :expired
5
+ add_column :turkee_tasks, :expired, :integer
6
+ end
7
+ end
8
+
9
+ def self.down
10
+ if column_exists? :turkee_tasks, :expired
11
+ remove_column :turkee_tasks, :expired
12
+ end
13
+ end
14
+ end
15
+
@@ -34,6 +34,10 @@ class TurkeeGenerator < Rails::Generators::Base
34
34
 
35
35
  migration_template "add_hit_duration.rb.erb", "db/migrate/add_hit_duration.rb"
36
36
 
37
+ sleep 1
38
+
39
+ migration_template "add_expired.rb.erb", "db/migrate/add_expired.rb"
40
+
37
41
  end
38
42
 
39
43
  def create_initializer
@@ -12,6 +12,14 @@ module Turkee
12
12
  # Model simply tracks what assignments have been imported
13
13
  class TurkeeImportedAssignment < ActiveRecord::Base
14
14
  attr_accessible :assignment_id, :turkee_task_id, :worker_id, :result_id
15
+
16
+ def self.record_imported_assignment(assignment, result, turk)
17
+ TurkeeImportedAssignment.create!(:assignment_id => assignment.id,
18
+ :turkee_task_id => turk.id,
19
+ :worker_id => assignment.worker_id,
20
+ :result_id => result.id)
21
+ end
22
+
15
23
  end
16
24
 
17
25
  class TurkeeTask < ActiveRecord::Base
@@ -36,29 +44,26 @@ module Turkee
36
44
  turks.each do |turk|
37
45
  hit = RTurk::Hit.new(turk.hit_id)
38
46
 
39
- models = Set.new
47
+ callback_models = Set.new
40
48
  hit.assignments.each do |assignment|
41
49
  next unless submitted?(assignment.status)
42
- next unless TurkeeImportedAssignment.find_by_assignment_id(assignment.id).nil?
43
-
44
- params = assignment_params(assignment.answers)
45
- param_hash = Rack::Utils.parse_nested_query(params)
46
- model = find_model(param_hash)
50
+ next if assignment_exists?(assignment)
47
51
 
52
+ model, param_hash = map_imported_values(assignment)
48
53
  next if model.nil?
49
- models << model
54
+
55
+ callback_models << model
50
56
 
51
- logger.debug "param_hash = #{param_hash}"
52
- result = model.create(param_hash[model.to_s.underscore])
57
+ result = save_imported_values(model, param_hash)
53
58
 
54
59
  # If there's a custom approve? method, see if we should approve the submitted assignment
55
60
  # otherwise just approve it by default
56
- process_result(assignment, result, turk)
61
+ turk.process_result(assignment, result)
57
62
 
58
- TurkeeImportedAssignment.create!(:assignment_id => assignment.id, :turkee_task_id => turk.id, :worker_id => assignment.worker_id, :result_id => result.id)
63
+ TurkeeImportedAssignment.record_imported_assignment(assignment, result, turk)
59
64
  end
60
65
 
61
- check_hit_completeness(hit, turk, models)
66
+ turk.set_expired?(callback_models) if !turk.set_complete?(hit, callback_models)
62
67
  end
63
68
  end
64
69
  rescue Lockfile::MaxTriesLockError => e
@@ -67,9 +72,13 @@ module Turkee
67
72
 
68
73
  end
69
74
 
75
+ def self.save_imported_values(model, param_hash)
76
+ model.create(param_hash[model.to_s.underscore])
77
+ end
78
+
70
79
  # Creates a new Mechanical Turk task on AMZN with the given title, desc, etc
71
80
  def self.create_hit(host, hit_title, hit_description, typ, num_assignments, reward, lifetime, duration = nil, qualifications = {}, params = {}, opts = {})
72
- model = Object::const_get(typ)
81
+ model = typ.to_s.constantize
73
82
  f_url = build_url(host, model, params, opts)
74
83
 
75
84
  h = RTurk::Hit.create(:title => hit_title) do |hit|
@@ -112,19 +121,13 @@ module Turkee
112
121
  hits.each do |hit|
113
122
  begin
114
123
  hit.expire!
115
-
116
124
  hit.assignments.each do |assignment|
117
-
118
125
  logger.info "Assignment status : #{assignment.status}"
119
-
120
126
  assignment.approve!('__clear_all_turks__approved__') if assignment.status == 'Submitted'
121
127
  end
122
128
 
123
129
  turkee_task = TurkeeTask.find_by_hit_id(hit.id)
124
- if turkee_task
125
- turkee_task.complete = true
126
- turkee_task.save
127
- end
130
+ turkee_task.complete_task
128
131
 
129
132
  hit.dispose!
130
133
  rescue Exception => e
@@ -136,43 +139,77 @@ module Turkee
136
139
 
137
140
  end
138
141
 
139
- private
140
-
141
- def logger
142
- @logger ||= Logger.new($stderr)
142
+ def complete_task
143
+ self.complete = true
144
+ save!
143
145
  end
144
146
 
145
- def self.check_hit_completeness(hit, turk, models)
146
- logger.debug "#### turk.completed_assignments == turk.hit_num_assignments :: #{turk.completed_assignments} == #{turk.hit_num_assignments}"
147
- if turk.completed_assignments == turk.hit_num_assignments
147
+ def set_complete?(hit, models)
148
+ if completed_assignments?
148
149
  hit.dispose!
149
- turk.complete = true
150
- turk.save
151
- models.each { |model| model.hit_complete(turk) if model.respond_to?(:hit_complete) }
150
+ complete_task
151
+ initiate_callback(:hit_complete, models)
152
+ return true
153
+ end
154
+
155
+ false
156
+ end
157
+
158
+ def set_expired?(models)
159
+ if expired?
160
+ self.expired = true
161
+ save!
162
+ initiate_callback(:hit_expired, models)
152
163
  end
153
164
  end
154
165
 
166
+ def initiate_callback(method, models)
167
+ models.each { |model| model.send(method, self) if model.respond_to?(method) }
168
+ end
155
169
 
156
- def self.process_result(assignment, result, turk)
170
+ def process_result(assignment, result)
157
171
  if result.errors.size > 0
158
172
  logger.info "Errors : #{result.inspect}"
159
173
  assignment.reject!('Failed to enter proper data.')
160
174
  elsif result.respond_to?(:approve?)
161
175
  logger.debug "Approving : #{result.inspect}"
162
- increment_complete_assignments(turk)
176
+ self.increment_complete_assignments
163
177
  result.approve? ? assignment.approve!('') : assignment.reject!('Rejected criteria.')
164
178
  else
165
- increment_complete_assignments(turk)
179
+ self.increment_complete_assignments
166
180
  assignment.approve!('')
167
181
  end
168
182
  end
169
183
 
170
- def self.increment_complete_assignments(turk)
171
- # Backward compatibility; completed_assignments may not exist in the table
172
- if turk.respond_to?(:completed_assignments)
173
- turk.completed_assignments += 1
174
- turk.save
175
- end
184
+ def increment_complete_assignments
185
+ raise "Missing :completed_assignments attribute. Please upgrade Turkee to the most recent version." unless respond_to?(:completed_assignments)
186
+
187
+ self.completed_assignments += 1
188
+ save
189
+ end
190
+
191
+ private
192
+
193
+ def logger
194
+ @logger ||= Logger.new($stderr)
195
+ end
196
+
197
+ def self.map_imported_values(assignment)
198
+ params = assignment_params(assignment.answers)
199
+ param_hash = Rack::Utils.parse_nested_query(params)
200
+ return find_model(param_hash), param_hash
201
+ end
202
+
203
+ def self.assignment_exists?(assignment)
204
+ TurkeeImportedAssignment.find_by_assignment_id(assignment.id).present?
205
+ end
206
+
207
+ def completed_assignments?
208
+ completed_assignments == hit_num_assignments
209
+ end
210
+
211
+ def expired?
212
+ Time.now >= (created_at + hit_lifetime.days)
176
213
  end
177
214
 
178
215
  def self.task_items(turkee_task)
@@ -194,7 +231,7 @@ module Turkee
194
231
  def self.find_model(param_hash)
195
232
  param_hash.each do |k, v|
196
233
  if v.is_a?(Hash)
197
- model = Object::const_get(k.to_s.camelize) rescue next
234
+ model = k.to_s.camelize.constantize rescue next
198
235
  return model if model.descends_from_active_record? rescue next
199
236
  end
200
237
  end
@@ -4,7 +4,6 @@ require "bundler/setup"
4
4
  require 'factory_girl'
5
5
  require 'rspec'
6
6
  require 'spork'
7
- require 'growl'
8
7
  require 'rails'
9
8
  require 'rturk'
10
9
  require 'lockfile'
@@ -22,9 +21,18 @@ ActiveRecord::Schema.define(:version => 1) do
22
21
  t.decimal "hit_reward", :precision => 10, :scale => 2
23
22
  t.integer "hit_num_assignments"
24
23
  t.integer "hit_lifetime"
25
- t.integer "hit_duration"
26
24
  t.string "form_url"
25
+ t.integer "completed_assignments", :default => 0
27
26
  t.boolean "complete"
27
+ t.boolean "expired"
28
+ t.datetime "created_at", :null => false
29
+ t.datetime "updated_at", :null => false
30
+ t.integer "turkee_flow_id"
31
+ t.integer "hit_duration"
32
+ end
33
+
34
+ create_table :surveys do |t|
35
+ t.string :answer
28
36
  end
29
37
  end
30
38
 
@@ -1,23 +1,125 @@
1
1
  require 'spec_helper'
2
2
 
3
-
4
3
  describe Turkee::TurkeeTask do
5
- #include ActionController::Routing::Routes
6
4
  class TestTask < ActiveRecord::Base
7
- def self.abstract_class; true; end
5
+ def self.abstract_class
6
+ true
7
+ end
8
+
8
9
  attr_accessor :description
9
10
  end
10
11
 
11
- describe "#process_hits" do
12
- before(:each) do
12
+ class Survey < ActiveRecord::Base
13
+ def self.abstract_class
14
+ true
15
+ end
16
+
17
+ def self.hit_complete(hit)
18
+ end
19
+ end
20
+
21
+ describe ".completed_assignments?" do
22
+ it "is not complete" do
23
+ turkee_task = FactoryGirl.create(:turkee_task)
24
+ turkee_task.send("completed_assignments?").should be_false
25
+ end
26
+
27
+ it "is complete" do
28
+ turkee_task = FactoryGirl.create(:turkee_task, :completed_assignments => 100)
29
+ turkee_task.send("completed_assignments?").should be_true
30
+ end
31
+ end
13
32
 
33
+ describe ".expired?" do
34
+ it "is not expired" do
35
+ turkee_task = FactoryGirl.create(:turkee_task)
36
+ turkee_task.send("expired?").should be_false
14
37
  end
15
38
 
39
+ it "is expired" do
40
+ turkee_task = FactoryGirl.create(:turkee_task, :created_at => Time.now - 2.days, :hit_lifetime => 1)
41
+ turkee_task.send("expired?").should be_true
42
+
43
+ turkee_task = FactoryGirl.create(:turkee_task, :created_at => Time.now - 1.day, :hit_lifetime => 1)
44
+ turkee_task.send("expired?").should be_true
45
+ end
16
46
  end
17
47
 
48
+ describe ".set_complete?" do
49
+ before do
50
+ @hit = RTurk::Hit.new(123)
51
+ end
52
+ context "completed hits" do
53
+ before do
54
+ @turkee_task = FactoryGirl.create(:turkee_task,
55
+ :hit_num_assignments => 100,
56
+ :completed_assignments => 100)
57
+ end
18
58
 
19
- describe "#find_model" do
59
+ it "marks the turkee task as complete" do
60
+ @hit.should_receive(:dispose!).once
61
+ Survey.should_receive(:hit_complete).once
62
+ @turkee_task.set_complete?(@hit, [Survey])
63
+ @turkee_task.complete.should be_true
64
+ end
65
+ end
66
+
67
+ context "incomplete hits" do
68
+ before do
69
+ @turkee_task = FactoryGirl.create(:turkee_task,
70
+ :hit_num_assignments => 99,
71
+ :completed_assignments => 100)
72
+ end
20
73
 
74
+ it "keeps the turkee task as incomplete" do
75
+ @hit.should_not_receive(:dispose!)
76
+ Survey.should_not_receive(:hit_complete)
77
+ @turkee_task.set_complete?(@hit, [Survey]).should be_false
78
+ @turkee_task.complete.should be_false
79
+ end
80
+ end
81
+ end
82
+
83
+ describe ".set_expired?" do
84
+ context "unexpired hits" do
85
+ before do
86
+ @turkee_task = FactoryGirl.create(:turkee_task)
87
+ end
88
+
89
+ it "keeps the turkee task as unexpired" do
90
+ Survey.should_not_receive(:hit_expired)
91
+ @turkee_task.set_expired?([Survey])
92
+ @turkee_task.expired.should be_false
93
+ end
94
+ end
95
+
96
+ context "expired hits" do
97
+ before do
98
+ @turkee_task = FactoryGirl.create(:turkee_task,
99
+ :created_at => 2.days.ago,
100
+ :hit_lifetime => 1)
101
+ end
102
+
103
+ it "marks the turkee task as expired" do
104
+ Survey.should_receive(:hit_expired)
105
+ @turkee_task.set_expired?([Survey]).should be_true
106
+ @turkee_task.expired.should be_true
107
+ end
108
+ end
109
+ end
110
+
111
+ describe ".initiate_callback" do
112
+ before do
113
+ @turkee_task = FactoryGirl.create(:turkee_task)
114
+ end
115
+ it "calls hit_complete for the given callback model" do
116
+ Survey.should_receive(:hit_complete).once
117
+ @turkee_task.initiate_callback(:hit_complete, [Survey])
118
+ end
119
+
120
+ end
121
+
122
+ describe "#find_model" do
21
123
  it "should return a turkee_task mode " do
22
124
  returned_data = {:submit => 'Create', "test_task" => {:description => "desc"}}
23
125
  Turkee::TurkeeTask.find_model(returned_data).should == TestTask
@@ -27,9 +129,8 @@ describe Turkee::TurkeeTask do
27
129
  returned_data = {:submit => 'Create', "another_task_class" => {:description => "desc"}}
28
130
  Turkee::TurkeeTask.find_model(returned_data).should be_nil
29
131
  end
30
-
31
132
  end
32
-
133
+
33
134
  describe "#assignment_params" do
34
135
  it "should encode the params properly" do
35
136
  answers = {:test => "abc", :test2 => "this is a test"}
@@ -42,5 +143,4 @@ describe Turkee::TurkeeTask do
42
143
  Turkee::TurkeeTask.submitted?("Submitted").should == true
43
144
  end
44
145
  end
45
-
46
146
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turkee
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.1
4
+ version: 1.2.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-09 00:00:00.000000000 Z
12
+ date: 2013-01-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: lockfile
16
- requirement: &70217997125780 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70217997125780
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rails
27
- requirement: &70217997123680 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 3.1.1
33
38
  type: :runtime
34
39
  prerelease: false
35
- version_requirements: *70217997123680
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.1.1
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rturk
38
- requirement: &70217997120760 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,10 +53,15 @@ dependencies:
43
53
  version: 2.4.0
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *70217997120760
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.4.0
47
62
  - !ruby/object:Gem::Dependency
48
63
  name: mocha
49
- requirement: &70217997119040 !ruby/object:Gem::Requirement
64
+ requirement: !ruby/object:Gem::Requirement
50
65
  none: false
51
66
  requirements:
52
67
  - - ! '>='
@@ -54,10 +69,31 @@ dependencies:
54
69
  version: '0'
55
70
  type: :development
56
71
  prerelease: false
57
- version_requirements: *70217997119040
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
58
78
  - !ruby/object:Gem::Dependency
59
79
  name: sqlite3
60
- requirement: &70217997114560 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: spork
96
+ requirement: !ruby/object:Gem::Requirement
61
97
  none: false
62
98
  requirements:
63
99
  - - ! '>='
@@ -65,10 +101,15 @@ dependencies:
65
101
  version: '0'
66
102
  type: :development
67
103
  prerelease: false
68
- version_requirements: *70217997114560
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
69
110
  - !ruby/object:Gem::Dependency
70
111
  name: factory_girl
71
- requirement: &70217997110980 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
72
113
  none: false
73
114
  requirements:
74
115
  - - ! '>='
@@ -76,10 +117,15 @@ dependencies:
76
117
  version: 1.3.2
77
118
  type: :development
78
119
  prerelease: false
79
- version_requirements: *70217997110980
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.3.2
80
126
  - !ruby/object:Gem::Dependency
81
127
  name: rspec
82
- requirement: &70217997110120 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
83
129
  none: false
84
130
  requirements:
85
131
  - - ! '>='
@@ -87,7 +133,12 @@ dependencies:
87
133
  version: 2.5.0
88
134
  type: :development
89
135
  prerelease: false
90
- version_requirements: *70217997110120
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: 2.5.0
91
142
  description: Turkee will help you to create your Rails forms, post the HITs, and retrieve
92
143
  the user entered values from Mechanical Turk.
93
144
  email: jjones@aantix.com
@@ -105,6 +156,7 @@ files:
105
156
  - lib/generators/turkee/templates/turkee_migration.rb.erb
106
157
  - lib/generators/turkee/templates/add_imported_assignment_details.rb.erb
107
158
  - lib/generators/turkee/templates/add_hit_duration.rb.erb
159
+ - lib/generators/turkee/templates/add_expired.rb.erb
108
160
  - lib/generators/turkee/turkee_generator.rb
109
161
  - lib/tasks/turkee.rb
110
162
  - lib/turkee.rb
@@ -142,7 +194,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
142
194
  version: '0'
143
195
  requirements: []
144
196
  rubyforge_project:
145
- rubygems_version: 1.8.15
197
+ rubygems_version: 1.8.24
146
198
  signing_key:
147
199
  specification_version: 3
148
200
  summary: Turkee makes dealing with Amazon's Mechnical Turk a breeze.