turkee-mongoid 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. data/.document +5 -0
  2. data/.gitignore +27 -0
  3. data/.travis.yml +8 -0
  4. data/Gemfile +14 -0
  5. data/Gemfile.lock +136 -0
  6. data/Guardfile +12 -0
  7. data/LICENSE +188 -0
  8. data/README.rdoc +219 -0
  9. data/Rakefile +50 -0
  10. data/VERSION +1 -0
  11. data/lib/generators/turkee/templates/turkee.rb +8 -0
  12. data/lib/generators/turkee/turkee_generator.rb +13 -0
  13. data/lib/helpers/turkee_forms_helper.rb +69 -0
  14. data/lib/models/turkee_imported_assignment.rb +19 -0
  15. data/lib/models/turkee_study.rb +17 -0
  16. data/lib/models/turkee_task.rb +273 -0
  17. data/lib/tasks/turkee.rb +29 -0
  18. data/lib/turkee.rb +9 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  21. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  22. data/spec/dummy/app/models/survey.rb +6 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +20 -0
  24. data/spec/dummy/config.ru +4 -0
  25. data/spec/dummy/config/application.rb +21 -0
  26. data/spec/dummy/config/boot.rb +10 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +24 -0
  29. data/spec/dummy/config/environments/production.rb +51 -0
  30. data/spec/dummy/config/environments/test.rb +37 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +10 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/locales/en.yml +5 -0
  37. data/spec/dummy/config/mongoid.yml +21 -0
  38. data/spec/dummy/config/routes.rb +2 -0
  39. data/spec/dummy/db/seeds.rb +0 -0
  40. data/spec/dummy/public/404.html +26 -0
  41. data/spec/dummy/public/422.html +26 -0
  42. data/spec/dummy/public/500.html +26 -0
  43. data/spec/dummy/public/favicon.ico +0 -0
  44. data/spec/dummy/public/javascripts/application.js +2 -0
  45. data/spec/dummy/public/javascripts/controls.js +965 -0
  46. data/spec/dummy/public/javascripts/dragdrop.js +974 -0
  47. data/spec/dummy/public/javascripts/effects.js +1123 -0
  48. data/spec/dummy/public/javascripts/prototype.js +6001 -0
  49. data/spec/dummy/public/javascripts/rails.js +191 -0
  50. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  51. data/spec/dummy/public/stylesheets/scaffold.css +56 -0
  52. data/spec/dummy/script/rails +6 -0
  53. data/spec/factories/survey_factory.rb +7 -0
  54. data/spec/factories/turkee_task_factory.rb +21 -0
  55. data/spec/helpers/turkee_forms_helper_spec.rb +75 -0
  56. data/spec/models/turkee_study_spec.rb +19 -0
  57. data/spec/models/turkee_task_spec.rb +139 -0
  58. data/spec/spec.opts +1 -0
  59. data/spec/spec_helper.rb +49 -0
  60. data/turkee.gemspec +60 -0
  61. metadata +243 -0
@@ -0,0 +1,191 @@
1
+ (function() {
2
+ // Technique from Juriy Zaytsev
3
+ // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
+ function isEventSupported(eventName) {
5
+ var el = document.createElement('div');
6
+ eventName = 'on' + eventName;
7
+ var isSupported = (eventName in el);
8
+ if (!isSupported) {
9
+ el.setAttribute(eventName, 'return;');
10
+ isSupported = typeof el[eventName] == 'function';
11
+ }
12
+ el = null;
13
+ return isSupported;
14
+ }
15
+
16
+ function isForm(element) {
17
+ return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
+ }
19
+
20
+ function isInput(element) {
21
+ if (Object.isElement(element)) {
22
+ var name = element.nodeName.toUpperCase()
23
+ return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
+ }
25
+ else return false
26
+ }
27
+
28
+ var submitBubbles = isEventSupported('submit'),
29
+ changeBubbles = isEventSupported('change')
30
+
31
+ if (!submitBubbles || !changeBubbles) {
32
+ // augment the Event.Handler class to observe custom events when needed
33
+ Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
+ function(init, element, eventName, selector, callback) {
35
+ init(element, eventName, selector, callback)
36
+ // is the handler being attached to an element that doesn't support this event?
37
+ if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
+ (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
+ // "submit" => "emulated:submit"
40
+ this.eventName = 'emulated:' + this.eventName
41
+ }
42
+ }
43
+ )
44
+ }
45
+
46
+ if (!submitBubbles) {
47
+ // discover forms on the page by observing focus events which always bubble
48
+ document.on('focusin', 'form', function(focusEvent, form) {
49
+ // special handler for the real "submit" event (one-time operation)
50
+ if (!form.retrieve('emulated:submit')) {
51
+ form.on('submit', function(submitEvent) {
52
+ var emulated = form.fire('emulated:submit', submitEvent, true)
53
+ // if custom event received preventDefault, cancel the real one too
54
+ if (emulated.returnValue === false) submitEvent.preventDefault()
55
+ })
56
+ form.store('emulated:submit', true)
57
+ }
58
+ })
59
+ }
60
+
61
+ if (!changeBubbles) {
62
+ // discover form inputs on the page
63
+ document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
+ // special handler for real "change" events
65
+ if (!input.retrieve('emulated:change')) {
66
+ input.on('change', function(changeEvent) {
67
+ input.fire('emulated:change', changeEvent, true)
68
+ })
69
+ input.store('emulated:change', true)
70
+ }
71
+ })
72
+ }
73
+
74
+ function handleRemote(element) {
75
+ var method, url, params;
76
+
77
+ var event = element.fire("ajax:before");
78
+ if (event.stopped) return false;
79
+
80
+ if (element.tagName.toLowerCase() === 'form') {
81
+ method = element.readAttribute('method') || 'post';
82
+ url = element.readAttribute('action');
83
+ params = element.serialize();
84
+ } else {
85
+ method = element.readAttribute('data-method') || 'get';
86
+ url = element.readAttribute('href');
87
+ params = {};
88
+ }
89
+
90
+ new Ajax.Request(url, {
91
+ method: method,
92
+ parameters: params,
93
+ evalScripts: true,
94
+
95
+ onComplete: function(request) { element.fire("ajax:complete", request); },
96
+ onSuccess: function(request) { element.fire("ajax:success", request); },
97
+ onFailure: function(request) { element.fire("ajax:failure", request); }
98
+ });
99
+
100
+ element.fire("ajax:after");
101
+ }
102
+
103
+ function handleMethod(element) {
104
+ var method = element.readAttribute('data-method'),
105
+ url = element.readAttribute('href'),
106
+ csrf_param = $$('meta[name=csrf-param]')[0],
107
+ csrf_token = $$('meta[name=csrf-token]')[0];
108
+
109
+ var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
+ element.parentNode.insert(form);
111
+
112
+ if (method !== 'post') {
113
+ var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
+ form.insert(field);
115
+ }
116
+
117
+ if (csrf_param) {
118
+ var param = csrf_param.readAttribute('content'),
119
+ token = csrf_token.readAttribute('content'),
120
+ field = new Element('input', { type: 'hidden', name: param, value: token });
121
+ form.insert(field);
122
+ }
123
+
124
+ form.submit();
125
+ }
126
+
127
+
128
+ document.on("click", "*[data-confirm]", function(event, element) {
129
+ var message = element.readAttribute('data-confirm');
130
+ if (!confirm(message)) event.stop();
131
+ });
132
+
133
+ document.on("click", "a[data-remote]", function(event, element) {
134
+ if (event.stopped) return;
135
+ handleRemote(element);
136
+ event.stop();
137
+ });
138
+
139
+ document.on("click", "a[data-method]", function(event, element) {
140
+ if (event.stopped) return;
141
+ handleMethod(element);
142
+ event.stop();
143
+ });
144
+
145
+ document.on("submit", function(event) {
146
+ var element = event.findElement(),
147
+ message = element.readAttribute('data-confirm');
148
+ if (message && !confirm(message)) {
149
+ event.stop();
150
+ return false;
151
+ }
152
+
153
+ var inputs = element.select("input[type=submit][data-disable-with]");
154
+ inputs.each(function(input) {
155
+ input.disabled = true;
156
+ input.writeAttribute('data-original-value', input.value);
157
+ input.value = input.readAttribute('data-disable-with');
158
+ });
159
+
160
+ var element = event.findElement("form[data-remote]");
161
+ if (element) {
162
+ handleRemote(element);
163
+ event.stop();
164
+ }
165
+ });
166
+
167
+ document.on("ajax:after", "form", function(event, element) {
168
+ var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
+ inputs.each(function(input) {
170
+ input.value = input.readAttribute('data-original-value');
171
+ input.removeAttribute('data-original-value');
172
+ input.disabled = false;
173
+ });
174
+ });
175
+
176
+ Ajax.Responders.register({
177
+ onCreate: function(request) {
178
+ var csrf_meta_tag = $$('meta[name=csrf-token]')[0];
179
+
180
+ if (csrf_meta_tag) {
181
+ var header = 'X-CSRF-Token',
182
+ token = csrf_meta_tag.readAttribute('content');
183
+
184
+ if (!request.options.requestHeaders) {
185
+ request.options.requestHeaders = {};
186
+ }
187
+ request.options.requestHeaders[header] = token;
188
+ }
189
+ }
190
+ });
191
+ })();
File without changes
@@ -0,0 +1,56 @@
1
+ body { background-color: #fff; color: #333; }
2
+
3
+ body, p, ol, ul, td {
4
+ font-family: verdana, arial, helvetica, sans-serif;
5
+ font-size: 13px;
6
+ line-height: 18px;
7
+ }
8
+
9
+ pre {
10
+ background-color: #eee;
11
+ padding: 10px;
12
+ font-size: 11px;
13
+ }
14
+
15
+ a { color: #000; }
16
+ a:visited { color: #666; }
17
+ a:hover { color: #fff; background-color:#000; }
18
+
19
+ div.field, div.actions {
20
+ margin-bottom: 10px;
21
+ }
22
+
23
+ #notice {
24
+ color: green;
25
+ }
26
+
27
+ .field_with_errors {
28
+ padding: 2px;
29
+ background-color: red;
30
+ display: table;
31
+ }
32
+
33
+ #error_explanation {
34
+ width: 450px;
35
+ border: 2px solid red;
36
+ padding: 7px;
37
+ padding-bottom: 0;
38
+ margin-bottom: 20px;
39
+ background-color: #f0f0f0;
40
+ }
41
+
42
+ #error_explanation h2 {
43
+ text-align: left;
44
+ font-weight: bold;
45
+ padding: 5px 5px 5px 15px;
46
+ font-size: 12px;
47
+ margin: -7px;
48
+ margin-bottom: 0px;
49
+ background-color: #c00;
50
+ color: #fff;
51
+ }
52
+
53
+ #error_explanation ul li {
54
+ font-size: 12px;
55
+ list-style: square;
56
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :survey do |s|
3
+ s.answer "The answer"
4
+ s.created_at Time.now
5
+ s.updated_at Time.now
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ require 'turkee'
2
+ FactoryGirl.define do
3
+ factory :turkee_task, :class => Turkee::TurkeeTask do |s|
4
+ s.hit_url "http://workersandbox.mturk.com/mturk/preview?groupId=248SVGULF395SZ65OC6S6NYNJDXAO5"
5
+ s.sandbox true
6
+ s.task_type "TestTask"
7
+ s.hit_title "Test Title"
8
+ s.hit_description "Test Desc"
9
+ s.hit_id "123"
10
+ s.hit_reward 0.05
11
+ s.completed_assignments 0
12
+ s.hit_num_assignments 100
13
+ s.hit_lifetime 1
14
+ s.hit_duration 1
15
+ s.form_url "http://localhost/test_task/new"
16
+ s.complete false
17
+ s.expired false
18
+ s.created_at Time.now
19
+ s.updated_at Time.now
20
+ end
21
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Turkee::TurkeeFormHelper, :type => :helper do
4
+ describe "mturk_url" do
5
+ it "should return sandbox" do
6
+ RTurk.stub(:sandbox?).and_return true
7
+ mturk_url.should match /workersandbox.mturk.com/
8
+ end
9
+ it "should return production url" do
10
+ RTurk.stub(:sandbox?).and_return false
11
+ mturk_url.should match /www.mturk.com/
12
+ end
13
+ end
14
+
15
+ describe "turkee_study" do
16
+ before do
17
+ @task = create(:turkee_task)
18
+ RTurk.stub(:sandbox?).and_return true
19
+
20
+ helper.stub(:params).and_return({:assignmentId => '123456', :workerId => '987654'})
21
+ @study_form = helper.turkee_study
22
+ end
23
+
24
+ it "includes the description, textarea" do
25
+ @study_form.should match(/Test Desc/)
26
+ @study_form.should match(/feedback/)
27
+ @study_form.should match(/textarea/)
28
+ @study_form.should match(/workersandbox.mturk.com/)
29
+ end
30
+
31
+ it "the form points to the sandbox" do
32
+ @study_form = turkee_study
33
+ @study_form.should match(/workersandbox.mturk.com/)
34
+ end
35
+
36
+ it "still includes the necessary assignmentId and workerId" do
37
+ @study_form.should match(/assignmentId/)
38
+ @study_form.should match(/workerId/)
39
+ end
40
+
41
+ end
42
+
43
+ describe "turkee_form_for" do
44
+ before do
45
+ @survey = create(:survey)
46
+ RTurk.stub(:sandbox?).and_return true
47
+
48
+ params = {:assignmentId => '123456', :workerId => '987654'}
49
+ @survey_form = helper.turkee_form_for(@survey, params) do |f|
50
+ f.text_field :answer
51
+ end
52
+ end
53
+
54
+ it "displays the passed in assignmentId and workerId " do
55
+ @survey_form.should match(/answer/)
56
+ @survey_form.should match(/type=\"text\"/)
57
+ @survey_form.should match(/assignmentId/)
58
+ @survey_form.should match(/workerId/)
59
+ end
60
+
61
+ it "the intial turkee_form_for saved the assignmentId and workerId to a cookie for later retrieval" do
62
+ helper.cookies['assignmentId'].should == '123456'
63
+ helper.cookies['workerId'].should == '987654'
64
+
65
+ # Subsequent requests should still return form fields for assignmentId
66
+ survey_form = helper.turkee_form_for(@survey, {}) do |f|
67
+ f.text_field :answer
68
+ end
69
+ survey_form.should match(/123456/)
70
+ survey_form.should match(/987654/)
71
+ survey_form.should match(/assignmentId/)
72
+ survey_form.should match(/workerId/)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Turkee::TurkeeStudy do
4
+
5
+ describe "approve?" do
6
+ before do
7
+ @study = Turkee::TurkeeStudy.new(:feedback => "I really loved the site. I will tell my mom about it.",
8
+ :gold_response => 'the')
9
+ end
10
+ it "approves the task" do
11
+ @study.approve?.should be_true
12
+ end
13
+
14
+ it "rejects the task" do
15
+ @study.gold_response = "loved"
16
+ @study.approve?.should be_false
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe Turkee::TurkeeTask do
4
+
5
+ class TestTask
6
+ include Mongoid::Document
7
+
8
+ def self.abstract_class
9
+ true
10
+ end
11
+ attr_accessor :description
12
+ end
13
+
14
+ describe ".completed_assignments?" do
15
+ it "is not complete" do
16
+ turkee_task = FactoryGirl.create(:turkee_task)
17
+ turkee_task.send("completed_assignments?").should be_false
18
+ end
19
+
20
+ it "is complete" do
21
+ turkee_task = FactoryGirl.create(:turkee_task, :completed_assignments => 100)
22
+ turkee_task.send("completed_assignments?").should be_true
23
+ end
24
+ end
25
+
26
+ describe ".expired?" do
27
+ it "is not expired" do
28
+ turkee_task = FactoryGirl.create(:turkee_task)
29
+ turkee_task.send("expired?").should be_false
30
+ end
31
+
32
+ it "is expired" do
33
+ turkee_task = FactoryGirl.create(:turkee_task, :created_at => Time.now - 2.days, :hit_lifetime => 1)
34
+ turkee_task.send("expired?").should be_true
35
+
36
+ turkee_task = FactoryGirl.create(:turkee_task, :created_at => Time.now - 1.day, :hit_lifetime => 1)
37
+ turkee_task.send("expired?").should be_true
38
+ end
39
+ end
40
+
41
+ describe ".set_complete?" do
42
+ before do
43
+ @hit = RTurk::Hit.new(123)
44
+ end
45
+ context "completed hits" do
46
+ before do
47
+ @turkee_task = FactoryGirl.create(:turkee_task,
48
+ :hit_num_assignments => 100,
49
+ :completed_assignments => 100)
50
+ end
51
+
52
+ it "marks the turkee task as complete" do
53
+ @hit.should_receive(:dispose!).once
54
+ Survey.should_receive(:hit_complete).once
55
+ @turkee_task.set_complete?(@hit, [Survey])
56
+ @turkee_task.complete.should be_true
57
+ end
58
+ end
59
+
60
+ context "incomplete hits" do
61
+ before do
62
+ @turkee_task = FactoryGirl.create(:turkee_task,
63
+ :hit_num_assignments => 99,
64
+ :completed_assignments => 100)
65
+ end
66
+
67
+ it "keeps the turkee task as incomplete" do
68
+ @hit.should_not_receive(:dispose!)
69
+ Survey.should_not_receive(:hit_complete)
70
+ @turkee_task.set_complete?(@hit, [Survey]).should be_false
71
+ @turkee_task.complete.should be_false
72
+ end
73
+ end
74
+ end
75
+
76
+ describe ".set_expired?" do
77
+ context "unexpired hits" do
78
+ before do
79
+ @turkee_task = FactoryGirl.create(:turkee_task)
80
+ end
81
+
82
+ it "keeps the turkee task as unexpired" do
83
+ Survey.should_not_receive(:hit_expired)
84
+ @turkee_task.set_expired?([Survey])
85
+ @turkee_task.expired.should be_false
86
+ end
87
+ end
88
+
89
+ context "expired hits" do
90
+ before do
91
+ @turkee_task = FactoryGirl.create(:turkee_task,
92
+ :created_at => 2.days.ago,
93
+ :hit_lifetime => 1)
94
+ end
95
+
96
+ it "marks the turkee task as expired" do
97
+ Survey.should_receive(:hit_expired)
98
+ @turkee_task.set_expired?([Survey]).should be_true
99
+ @turkee_task.expired.should be_true
100
+ end
101
+ end
102
+ end
103
+
104
+ describe ".initiate_callback" do
105
+ before do
106
+ @turkee_task = FactoryGirl.create(:turkee_task)
107
+ end
108
+ it "calls hit_complete for the given callback model" do
109
+ Survey.should_receive(:hit_complete).once
110
+ @turkee_task.initiate_callback(:hit_complete, [Survey])
111
+ end
112
+
113
+ end
114
+
115
+ describe "#find_model" do
116
+ it "should return a turkee_task model" do
117
+ returned_data = {:submit => 'Create', "test_task" => {:description => "desc"}}
118
+ Turkee::TurkeeTask.find_model(returned_data).should == TestTask
119
+ end
120
+
121
+ it "should return a nil" do
122
+ returned_data = {:submit => 'Create', "another_task_class" => {:description => "desc"}}
123
+ Turkee::TurkeeTask.find_model(returned_data).should be_nil
124
+ end
125
+ end
126
+
127
+ describe "#assignment_params" do
128
+ it "should encode the params properly" do
129
+ answers = {:test => "abc", :test2 => "this is a test"}
130
+ Turkee::TurkeeTask.assignment_params(answers).should == "test2=this+is+a+test&test=abc"
131
+ end
132
+ end
133
+
134
+ describe "#submitted" do
135
+ it "should return true for a submitted status" do
136
+ Turkee::TurkeeTask.submitted?("Submitted").should == true
137
+ end
138
+ end
139
+ end