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.
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