rturk 1.0.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +3 -0
- data/.yardoc +0 -0
- data/README.markdown +49 -29
- data/Rakefile +53 -0
- data/TODO.markdown +3 -0
- data/VERSION +1 -1
- data/examples/blank_slate.rb +21 -4
- data/examples/create_hit.rb +19 -0
- data/examples/newtweet.html +0 -1
- data/examples/review_answer.rb +18 -5
- data/lib/rturk.rb +33 -6
- data/lib/rturk/adapters/answers.rb +38 -0
- data/lib/rturk/adapters/assignment.rb +75 -0
- data/lib/rturk/adapters/hit.rb +97 -0
- data/lib/rturk/builders/qualification_builder.rb +68 -0
- data/lib/rturk/builders/qualifications_builder.rb +43 -0
- data/lib/rturk/builders/question_builder.rb +51 -0
- data/lib/rturk/errors.rb +5 -0
- data/lib/rturk/logger.rb +20 -0
- data/lib/rturk/macros.rb +35 -0
- data/lib/rturk/operation.rb +86 -0
- data/lib/rturk/operations/approve_assignment.rb +28 -0
- data/lib/rturk/operations/create_hit.rb +77 -0
- data/lib/rturk/operations/disable_hit.rb +19 -0
- data/lib/rturk/operations/dispose_hit.rb +19 -0
- data/lib/rturk/operations/force_expire_hit.rb +18 -0
- data/lib/rturk/operations/get_account_balance.rb +15 -0
- data/lib/rturk/operations/get_assignments_for_hit.rb +24 -0
- data/lib/rturk/operations/get_hit.rb +21 -0
- data/lib/rturk/operations/get_reviewable_hits.rb +23 -0
- data/lib/rturk/operations/grant_bonus.rb +26 -0
- data/lib/rturk/operations/reject_assignment.rb +23 -0
- data/lib/rturk/requester.rb +58 -76
- data/lib/rturk/response.rb +58 -0
- data/lib/rturk/responses/create_hit_response.rb +33 -0
- data/lib/rturk/responses/get_account_balance_response.rb +11 -0
- data/lib/rturk/responses/get_assignments_for_hit_response.rb +43 -0
- data/lib/rturk/responses/get_hit_response.rb +80 -0
- data/lib/rturk/responses/get_reviewable_hits_response.rb +33 -0
- data/lib/rturk/utilities.rb +19 -1
- data/lib/rturk/xml_utilities.rb +23 -0
- data/rturk.gemspec +143 -0
- data/spec/adapters/answers_spec.rb +27 -0
- data/spec/adapters/assignment_spec.rb +0 -0
- data/spec/adapters/hit_spec.rb +46 -0
- data/spec/builders/qualification_spec.rb +53 -0
- data/spec/builders/qualifications_spec.rb +30 -0
- data/spec/builders/question_spec.rb +46 -0
- data/spec/fake_responses/approve_assignment.xml +5 -0
- data/spec/fake_responses/create_hit.xml +12 -0
- data/spec/fake_responses/disable_hit.xml +5 -0
- data/spec/fake_responses/dispose_hit.xml +5 -0
- data/spec/fake_responses/force_expire_hit.xml +5 -0
- data/spec/fake_responses/get_account_balance.xml +10 -0
- data/spec/fake_responses/get_assignments.xml +35 -0
- data/spec/fake_responses/get_hit.xml +41 -0
- data/spec/fake_responses/get_reviewable_hits.xml +17 -0
- data/spec/fake_responses/grant_bonus.xml +5 -0
- data/spec/fake_responses/invalid_credentials.xml +12 -0
- data/spec/fake_responses/reject_assignment.xml +5 -0
- data/spec/operations/approve_assignment_spec.rb +27 -0
- data/spec/operations/create_hit_spec.rb +66 -0
- data/spec/operations/disable_hit_spec.rb +26 -0
- data/spec/operations/dispose_hit_spec.rb +26 -0
- data/spec/operations/force_expire_hit_spec.rb +26 -0
- data/spec/operations/get_account_balance_spec.rb +15 -0
- data/spec/operations/get_assignments_spec.rb +26 -0
- data/spec/operations/get_hit_spec.rb +18 -0
- data/spec/operations/get_reviewable_hits_spec.rb +0 -0
- data/spec/operations/grant_bonus_spec.rb +32 -0
- data/spec/operations/reject_assignment_spec.rb +26 -0
- data/spec/requester_spec.rb +7 -18
- data/spec/response_spec.rb +48 -0
- data/spec/rturk_spec.rb +27 -0
- data/spec/spec_helper.rb +28 -3
- data/spec/tmp +2 -0
- data/spec/xml_parse_spec.rb +32 -0
- metadata +94 -34
- data/examples/external_page.rb +0 -33
- data/lib/rturk/answer.rb +0 -20
- data/lib/rturk/custom_operations.rb +0 -80
- data/lib/rturk/external_question_builder.rb +0 -22
- data/spec/answer_spec.rb +0 -24
- data/spec/external_question_spec.rb +0 -27
@@ -0,0 +1,68 @@
|
|
1
|
+
module RTurk
|
2
|
+
|
3
|
+
class Qualification
|
4
|
+
|
5
|
+
# For more information about qualification requirements see:
|
6
|
+
# http://docs.amazonwebservices.com/AWSMturkAPI/2008-08-02/index.html?ApiReference_QualificationRequirementDataStructureArticle.html
|
7
|
+
#
|
8
|
+
|
9
|
+
COMPARATORS = {:gt => 'GreaterThan', :lt => 'LessThan', :gte => 'GreaterThanOrEqualTo',
|
10
|
+
:lte => 'LessThanOrEqualTo', :eql => 'EqualTo', :not => 'NotEqualTo', :exists => 'Exists'}
|
11
|
+
|
12
|
+
TYPES = {:approval_rate => '000000000000000000L0', :submission_rate => '00000000000000000000',
|
13
|
+
:abandoned_rate => '0000000000000000007', :return_rate => '000000000000000000E0',
|
14
|
+
:rejection_rate => '000000000000000000S0', :hits_approved => '00000000000000000040',
|
15
|
+
:adult => '00000000000000000060', :country => '00000000000000000071'}
|
16
|
+
|
17
|
+
attr_accessor :qualifier
|
18
|
+
|
19
|
+
# Builds the basic requirements for a qualification
|
20
|
+
# needs at the minimum
|
21
|
+
# type_id, :comparator => :value
|
22
|
+
# or
|
23
|
+
# type_id, true
|
24
|
+
# or
|
25
|
+
# type_id, :exists
|
26
|
+
#
|
27
|
+
def initialize(type, opts)
|
28
|
+
# If the value is a string, we can assume it's the country since,
|
29
|
+
# Amazon states that there can be only integer values and countries
|
30
|
+
self.qualifier = {}
|
31
|
+
if type.is_a?(String)
|
32
|
+
qualifier[:QualificationTypeId] = type
|
33
|
+
elsif type.is_a?(Symbol)
|
34
|
+
qualifier[:QualificationTypeId] = types[type]
|
35
|
+
end
|
36
|
+
if opts.is_a?(Hash)
|
37
|
+
qualifier[:Comparator] = COMPARATORS[opts.keys.first]
|
38
|
+
value = opts.values.first
|
39
|
+
if value.to_s.match(/[A-Z]./)
|
40
|
+
qualifier[:Country] = value
|
41
|
+
else
|
42
|
+
qualifier[:IntegerValue] = value
|
43
|
+
end
|
44
|
+
elsif opts == true || opts == false
|
45
|
+
qualifier[:IntegerValue] = opts == true ? 1 : 0
|
46
|
+
qualifier[:Comparator] = COMPARATORS[:eql]
|
47
|
+
end
|
48
|
+
qualifier
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_params
|
52
|
+
params = {}
|
53
|
+
params["QualificationTypeId"] = qualifier[:QualificationTypeId]
|
54
|
+
params["Comparator"] = qualifier[:Comparator]
|
55
|
+
params["IntegerValue"] = qualifier[:IntegerValue] if qualifier[:IntegerValue]
|
56
|
+
params["LocaleValue.Country"] = qualifier[:Country] if qualifier[:Country]
|
57
|
+
params["RequiredToPreview"] = qualifier[:RequiredToPreview] || 'true'
|
58
|
+
params
|
59
|
+
end
|
60
|
+
|
61
|
+
def types
|
62
|
+
# Could use this later to add other TYPES programatically
|
63
|
+
TYPES
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module RTurk
|
2
|
+
|
3
|
+
class Qualifications
|
4
|
+
|
5
|
+
# For more information about qualification requirements see:
|
6
|
+
# http://docs.amazonwebservices.com/AWSMturkAPI/2008-08-02/index.html?ApiReference_QualificationRequirementDataStructureArticle.html
|
7
|
+
#
|
8
|
+
|
9
|
+
def to_params
|
10
|
+
params = {}
|
11
|
+
qualifications.each_with_index do |qualification, i|
|
12
|
+
qualification.to_params.each_pair do |k,v|
|
13
|
+
params["QualificationRequirement.#{i+1}.#{k}"] = v
|
14
|
+
end
|
15
|
+
end
|
16
|
+
params
|
17
|
+
end
|
18
|
+
|
19
|
+
# Can use this to manually add custom requirement types
|
20
|
+
# Needs a type name(you can reference this later)
|
21
|
+
# and the operation as a hash: ':gt => 85'
|
22
|
+
# Example
|
23
|
+
# qualifications.add('EnglishSkillsRequirement', :gt => 66, :type_id => '1234567890123456789ABC')
|
24
|
+
#
|
25
|
+
def add(type, opts)
|
26
|
+
qualifications << RTurk::Qualification.new(type, opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
def qualifications
|
30
|
+
@qualifications ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def method_missing(method, *args)
|
35
|
+
if RTurk::Qualification::TYPES.include?(method)
|
36
|
+
self.add(method, *args)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module RTurk
|
5
|
+
class Question
|
6
|
+
|
7
|
+
attr_accessor :url, :url_params, :frame_height
|
8
|
+
|
9
|
+
def initialize(url, opts = {})
|
10
|
+
@url = url
|
11
|
+
self.frame_height = opts.delete(:frame_height) || 400
|
12
|
+
self.url_params = opts
|
13
|
+
end
|
14
|
+
|
15
|
+
def querystring
|
16
|
+
@url_params.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&')
|
17
|
+
end
|
18
|
+
|
19
|
+
def url
|
20
|
+
unless querystring.empty?
|
21
|
+
# slam the params onto url, if url already has params, add 'em with a &
|
22
|
+
@url.index('?') ? "#{@url}&#{querystring}" : "#{@url}?#{querystring}"
|
23
|
+
else
|
24
|
+
@url
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def params
|
29
|
+
@url_params
|
30
|
+
end
|
31
|
+
|
32
|
+
def params=(param_set)
|
33
|
+
@url_params = param_set
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_params
|
37
|
+
raise RTurk::MissingParameters, "needs a url to build an external question" unless @url
|
38
|
+
# TODO: update the xmlns schema... maybe
|
39
|
+
xml = <<-XML
|
40
|
+
<ExternalQuestion xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2006-07-14/ExternalQuestion.xsd">
|
41
|
+
<ExternalURL>#{url}</ExternalURL>
|
42
|
+
<FrameHeight>#{frame_height}</FrameHeight>
|
43
|
+
</ExternalQuestion>
|
44
|
+
XML
|
45
|
+
xml
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
data/lib/rturk/errors.rb
ADDED
data/lib/rturk/logger.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RTurk
|
4
|
+
class Logger
|
5
|
+
class << self
|
6
|
+
def logger=(logger_obj)
|
7
|
+
@logger = logger_obj
|
8
|
+
end
|
9
|
+
|
10
|
+
def logger
|
11
|
+
unless @logger
|
12
|
+
@logger = ::Logger.new(STDOUT)
|
13
|
+
@logger.level = ::Logger::INFO
|
14
|
+
end
|
15
|
+
@logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/rturk/macros.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module RTurk::Macros
|
2
|
+
|
3
|
+
# Attempt to expire hit, then approve assignments, and finally dispose of
|
4
|
+
def kill_hit(hit_id)
|
5
|
+
forceExpireHIT(:HITId => hit_id)
|
6
|
+
get_assignments_for_hit(hit_id).each do |assignment|
|
7
|
+
approveAssignment(:AssignmentId => assignment[:AssignmentId])
|
8
|
+
end
|
9
|
+
disposeHIT(:HITId => hit_id)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wipe out all HIT's associated with this account
|
13
|
+
def blank_slate
|
14
|
+
search_response = searchHITs(:PageSize => 100)
|
15
|
+
if search_results = search_response['SearchHITsResult']['HIT']
|
16
|
+
search_results.each do |hit|
|
17
|
+
kill_hit(hit['HITId'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def url_for_hit(hit_id)
|
23
|
+
url_for_hit_type(getHIT(:HITId => hit_id)[:HITTypeId])
|
24
|
+
end
|
25
|
+
|
26
|
+
def url_for_hit_type(hit_type_id)
|
27
|
+
if @host =~ /sandbox/
|
28
|
+
"http://workersandbox.mturk.com/mturk/preview?groupId=#{hit_type_id}" # Sandbox Url
|
29
|
+
else
|
30
|
+
"http://mturk.com/mturk/preview?groupId=#{hit_type_id}" # Production Url
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module RTurk
|
2
|
+
class Operation
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def default_params
|
7
|
+
@default_params ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def required_params
|
11
|
+
@required_params || []
|
12
|
+
end
|
13
|
+
|
14
|
+
def require_params(*args)
|
15
|
+
@required_params ||= []
|
16
|
+
@required_params.push(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def operation(op)
|
20
|
+
default_params.merge!('Operation' => op)
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(opts = {}, &blk)
|
24
|
+
hit = self.new(opts, &blk)
|
25
|
+
response = hit.request
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
########################
|
31
|
+
### Instance Methods ###
|
32
|
+
########################
|
33
|
+
|
34
|
+
def initialize(opts = {})
|
35
|
+
opts.each_pair do |k,v|
|
36
|
+
if self.respond_to?("#{k.to_sym}=")
|
37
|
+
self.send "#{k}=".to_sym, v
|
38
|
+
elsif v.is_a?(Array)
|
39
|
+
v.each do |a|
|
40
|
+
(self.send k.to_s).send a[0].to_sym, a[1]
|
41
|
+
end
|
42
|
+
elsif self.respond_to?(k.to_sym)
|
43
|
+
self.send k.to_sym, v
|
44
|
+
end
|
45
|
+
end
|
46
|
+
yield(self) if block_given?
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_params
|
51
|
+
self.class.default_params
|
52
|
+
end
|
53
|
+
|
54
|
+
def parse(xml)
|
55
|
+
# Override this in your operation if you like
|
56
|
+
RTurk::Response.new(xml)
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_params
|
60
|
+
{}# Override to include extra params
|
61
|
+
end
|
62
|
+
|
63
|
+
def request
|
64
|
+
if self.respond_to?(:validate)
|
65
|
+
validate
|
66
|
+
end
|
67
|
+
check_params
|
68
|
+
params = self.default_params
|
69
|
+
params = to_params.merge(params)
|
70
|
+
response = RTurk.Request(params)
|
71
|
+
parse(response)
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_params
|
75
|
+
self.class.required_params.each do |param|
|
76
|
+
if self.respond_to?(param)
|
77
|
+
raise MissingParameters, "Parameter '#{param.to_s}' cannot be blank" if self.send(param).nil?
|
78
|
+
else
|
79
|
+
raise MissingParameters, "The parameter '#{param.to_s}' was required and not available"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Operation to approve an assignment
|
2
|
+
#
|
3
|
+
# http://mechanicalturk.amazonaws.com/?Service=AWSMechanicalTurkRequester
|
4
|
+
# &AWSAccessKeyId=[the Requester's Access Key ID]
|
5
|
+
# &Version=2008-08-02
|
6
|
+
# &Operation=ApproveAssignment
|
7
|
+
# &Signature=[signature for this request]
|
8
|
+
# &Timestamp=[your system's local time]
|
9
|
+
# &AssignmentId=123RVWYBAZW00EXAMPLE456RVWYBAZW00EXAMPLE
|
10
|
+
|
11
|
+
module RTurk
|
12
|
+
class ApproveAssignment < Operation
|
13
|
+
|
14
|
+
operation 'ApproveAssignment'
|
15
|
+
attr_accessor :assignment_id, :feedback
|
16
|
+
require_params :assignment_id
|
17
|
+
|
18
|
+
def to_params
|
19
|
+
{'AssignmentId' => self.assignment_id,
|
20
|
+
'RequesterFeedback' => self.feedback}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
def self.ApproveAssignment(*args)
|
25
|
+
RTurk::ApproveAssignment.create(*args)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module RTurk
|
2
|
+
class CreateHIT < Operation
|
3
|
+
|
4
|
+
operation 'CreateHIT'
|
5
|
+
|
6
|
+
attr_accessor :title, :keywords, :description, :reward, :currency, :assignments
|
7
|
+
attr_accessor :lifetime, :duration, :auto_approval, :note, :hit_type_id
|
8
|
+
|
9
|
+
# @param [Symbol, Hash] qualification_key opts The unique qualification key
|
10
|
+
# @option opts [Hash] :comparator A comparator and value e.g. :gt => 80
|
11
|
+
# @option opts [Boolean] :boolean true or false
|
12
|
+
# @option opts [Symbol] :exists A comparator without a value
|
13
|
+
# @return [RTurk::Qualifications]
|
14
|
+
def qualifications
|
15
|
+
@qualifications ||= RTurk::Qualifications.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Gives us access to a question builder attached to this HIT
|
19
|
+
#
|
20
|
+
# @param [String, Hash] URL Params, if none is passed, simply returns the question
|
21
|
+
# @return [RTurk::Question] The question if instantiated or nil
|
22
|
+
def question(*args)
|
23
|
+
unless args.empty?
|
24
|
+
@question ||= RTurk::Question.new(*args)
|
25
|
+
else
|
26
|
+
@question
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns parameters specific to this instance
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
# Any class level default parameters get loaded in at
|
34
|
+
# the time of request
|
35
|
+
def to_params
|
36
|
+
params = map_params.merge(qualifications.to_params)
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse(response)
|
40
|
+
RTurk::CreateHITResponse.new(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
# More complicated validation run before request
|
44
|
+
#
|
45
|
+
def validate
|
46
|
+
if hit_type_id
|
47
|
+
unless question && lifetime
|
48
|
+
raise RTurk::MissingParameters, "When you specify a HitTypeID, you must incude a question and lifetime length"
|
49
|
+
end
|
50
|
+
else
|
51
|
+
unless title && reward && question && description
|
52
|
+
raise RTurk::MissingParameters, "You're missing some required parameters"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def map_params
|
60
|
+
{'Title'=>self.title,
|
61
|
+
'MaxAssignments' => (self.assignments || 1),
|
62
|
+
'LifetimeInSeconds'=> (self.lifetime || 3600),
|
63
|
+
'AssignmentDurationInSeconds' => (self.duration || 86400),
|
64
|
+
'Reward.Amount' => self.reward,
|
65
|
+
'Reward.CurrencyCode' => (self.currency || 'USD'),
|
66
|
+
'Keywords' => self.keywords,
|
67
|
+
'Description' => self.description,
|
68
|
+
'Question' => self.question.to_params,
|
69
|
+
'RequesterAnnotation' => note}
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
def self.CreateHIT(*args, &blk)
|
74
|
+
RTurk::CreateHIT.create(*args, &blk)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# http://docs.amazonwebservices.com/AWSMturkAPI/2008-08-02/ApiReference_DisposeHITOperation.html
|
2
|
+
module RTurk
|
3
|
+
class DisableHIT < Operation
|
4
|
+
|
5
|
+
operation 'DisableHIT'
|
6
|
+
require_params :hit_id
|
7
|
+
attr_accessor :hit_id
|
8
|
+
|
9
|
+
def to_params
|
10
|
+
{'HITId' => self.hit_id}
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.DisableHIT(*args, &blk)
|
16
|
+
RTurk::DisableHIT.create(*args, &blk)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|