rturk 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/.gitmodules +3 -0
  2. data/.yardoc +0 -0
  3. data/README.markdown +49 -29
  4. data/Rakefile +53 -0
  5. data/TODO.markdown +3 -0
  6. data/VERSION +1 -1
  7. data/examples/blank_slate.rb +21 -4
  8. data/examples/create_hit.rb +19 -0
  9. data/examples/newtweet.html +0 -1
  10. data/examples/review_answer.rb +18 -5
  11. data/lib/rturk.rb +33 -6
  12. data/lib/rturk/adapters/answers.rb +38 -0
  13. data/lib/rturk/adapters/assignment.rb +75 -0
  14. data/lib/rturk/adapters/hit.rb +97 -0
  15. data/lib/rturk/builders/qualification_builder.rb +68 -0
  16. data/lib/rturk/builders/qualifications_builder.rb +43 -0
  17. data/lib/rturk/builders/question_builder.rb +51 -0
  18. data/lib/rturk/errors.rb +5 -0
  19. data/lib/rturk/logger.rb +20 -0
  20. data/lib/rturk/macros.rb +35 -0
  21. data/lib/rturk/operation.rb +86 -0
  22. data/lib/rturk/operations/approve_assignment.rb +28 -0
  23. data/lib/rturk/operations/create_hit.rb +77 -0
  24. data/lib/rturk/operations/disable_hit.rb +19 -0
  25. data/lib/rturk/operations/dispose_hit.rb +19 -0
  26. data/lib/rturk/operations/force_expire_hit.rb +18 -0
  27. data/lib/rturk/operations/get_account_balance.rb +15 -0
  28. data/lib/rturk/operations/get_assignments_for_hit.rb +24 -0
  29. data/lib/rturk/operations/get_hit.rb +21 -0
  30. data/lib/rturk/operations/get_reviewable_hits.rb +23 -0
  31. data/lib/rturk/operations/grant_bonus.rb +26 -0
  32. data/lib/rturk/operations/reject_assignment.rb +23 -0
  33. data/lib/rturk/requester.rb +58 -76
  34. data/lib/rturk/response.rb +58 -0
  35. data/lib/rturk/responses/create_hit_response.rb +33 -0
  36. data/lib/rturk/responses/get_account_balance_response.rb +11 -0
  37. data/lib/rturk/responses/get_assignments_for_hit_response.rb +43 -0
  38. data/lib/rturk/responses/get_hit_response.rb +80 -0
  39. data/lib/rturk/responses/get_reviewable_hits_response.rb +33 -0
  40. data/lib/rturk/utilities.rb +19 -1
  41. data/lib/rturk/xml_utilities.rb +23 -0
  42. data/rturk.gemspec +143 -0
  43. data/spec/adapters/answers_spec.rb +27 -0
  44. data/spec/adapters/assignment_spec.rb +0 -0
  45. data/spec/adapters/hit_spec.rb +46 -0
  46. data/spec/builders/qualification_spec.rb +53 -0
  47. data/spec/builders/qualifications_spec.rb +30 -0
  48. data/spec/builders/question_spec.rb +46 -0
  49. data/spec/fake_responses/approve_assignment.xml +5 -0
  50. data/spec/fake_responses/create_hit.xml +12 -0
  51. data/spec/fake_responses/disable_hit.xml +5 -0
  52. data/spec/fake_responses/dispose_hit.xml +5 -0
  53. data/spec/fake_responses/force_expire_hit.xml +5 -0
  54. data/spec/fake_responses/get_account_balance.xml +10 -0
  55. data/spec/fake_responses/get_assignments.xml +35 -0
  56. data/spec/fake_responses/get_hit.xml +41 -0
  57. data/spec/fake_responses/get_reviewable_hits.xml +17 -0
  58. data/spec/fake_responses/grant_bonus.xml +5 -0
  59. data/spec/fake_responses/invalid_credentials.xml +12 -0
  60. data/spec/fake_responses/reject_assignment.xml +5 -0
  61. data/spec/operations/approve_assignment_spec.rb +27 -0
  62. data/spec/operations/create_hit_spec.rb +66 -0
  63. data/spec/operations/disable_hit_spec.rb +26 -0
  64. data/spec/operations/dispose_hit_spec.rb +26 -0
  65. data/spec/operations/force_expire_hit_spec.rb +26 -0
  66. data/spec/operations/get_account_balance_spec.rb +15 -0
  67. data/spec/operations/get_assignments_spec.rb +26 -0
  68. data/spec/operations/get_hit_spec.rb +18 -0
  69. data/spec/operations/get_reviewable_hits_spec.rb +0 -0
  70. data/spec/operations/grant_bonus_spec.rb +32 -0
  71. data/spec/operations/reject_assignment_spec.rb +26 -0
  72. data/spec/requester_spec.rb +7 -18
  73. data/spec/response_spec.rb +48 -0
  74. data/spec/rturk_spec.rb +27 -0
  75. data/spec/spec_helper.rb +28 -3
  76. data/spec/tmp +2 -0
  77. data/spec/xml_parse_spec.rb +32 -0
  78. metadata +94 -34
  79. data/examples/external_page.rb +0 -33
  80. data/lib/rturk/answer.rb +0 -20
  81. data/lib/rturk/custom_operations.rb +0 -80
  82. data/lib/rturk/external_question_builder.rb +0 -22
  83. data/spec/answer_spec.rb +0 -24
  84. 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
@@ -0,0 +1,5 @@
1
+ module RTurk
2
+ class RTurkError < StandardError; end;
3
+ class MissingParameters < RTurkError; end;
4
+ class InvalidRequest < RTurkError; end;
5
+ end
@@ -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
@@ -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