rturk 1.0.5 → 2.0.0
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.
- 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
|