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,19 @@
1
+ # http://docs.amazonwebservices.com/AWSMturkAPI/2008-08-02/ApiReference_DisposeHITOperation.html
2
+ module RTurk
3
+ class DisposeHIT < Operation
4
+
5
+ operation 'DisposeHIT'
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.DisposeHIT(*args, &blk)
16
+ RTurk::DisposeHIT.create(*args, &blk)
17
+ end
18
+
19
+ end
@@ -0,0 +1,18 @@
1
+ module RTurk
2
+ class ForceExpireHIT < Operation
3
+
4
+ operation 'ForceExpireHIT'
5
+ require_params :hit_id
6
+ attr_accessor :hit_id
7
+
8
+ def to_params
9
+ {'HITId' => self.hit_id}
10
+ end
11
+
12
+ end
13
+
14
+ def self.ForceExpireHIT(*args, &blk)
15
+ RTurk::ForceExpireHIT.create(*args, &blk)
16
+ end
17
+
18
+ end
@@ -0,0 +1,15 @@
1
+ module RTurk
2
+ class GetAccountBalance < Operation
3
+
4
+ operation 'GetAccountBalance'
5
+
6
+ def parse(xml)
7
+ RTurk::GetAccountBalanceResponse.new(xml)
8
+ end
9
+
10
+ end
11
+ def self.GetAccountBalance
12
+ RTurk::GetAccountBalance.create
13
+ end
14
+
15
+ end
@@ -0,0 +1,24 @@
1
+ module RTurk
2
+ class GetAssignmentsForHIT < Operation
3
+
4
+ operation 'GetAssignmentsForHIT'
5
+ require_params :hit_id
6
+
7
+ attr_accessor :hit_id, :page_size, :page_number
8
+
9
+ def parse(xml)
10
+ GetAssignmentsForHITResponse.new(xml)
11
+ end
12
+
13
+ def to_params
14
+ {'HITId' => hit_id,
15
+ 'PageSize' => (page_size || 100),
16
+ 'PageNumber' => page_number}
17
+ end
18
+
19
+ end
20
+ def self.GetAssignmentsForHIT(*args, &blk)
21
+ RTurk::GetAssignmentsForHIT.create(*args, &blk)
22
+ end
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ module RTurk
2
+ class GetHIT < Operation
3
+
4
+ operation 'GetHIT'
5
+ require_params :hit_id
6
+ attr_accessor :hit_id
7
+
8
+ def parse(xml)
9
+ RTurk::GetHITResponse.new(xml)
10
+ end
11
+
12
+ def to_params
13
+ {"HITId" => self.hit_id}
14
+ end
15
+
16
+ end
17
+ def self.GetHIT(*args, &blk)
18
+ RTurk::GetHIT.create(*args, &blk)
19
+ end
20
+
21
+ end
@@ -0,0 +1,23 @@
1
+ module RTurk
2
+ class GetReviewableHITs < Operation
3
+
4
+ operation 'GetReviewableHITs'
5
+ attr_accessor :page_size, :page_number
6
+
7
+ def parse(xml)
8
+ RTurk::GetReviewableHITsResponse.new(xml)
9
+ end
10
+
11
+ def to_params
12
+ {
13
+ 'PageSize' => (page_size || 100),
14
+ 'PageNumber' => (page_number || 1)
15
+ }
16
+ end
17
+
18
+ end
19
+ def self.GetReviewableHITs(*args, &blk)
20
+ RTurk::GetReviewableHITs.create(*args, &blk)
21
+ end
22
+
23
+ end
@@ -0,0 +1,26 @@
1
+ module RTurk
2
+
3
+ # == Grant Bonus operation
4
+ #
5
+ # Grants a worker a bonus
6
+ #
7
+
8
+ class GrantBonus < Operation
9
+
10
+ operation 'GrantBonus'
11
+ attr_accessor :assignment_id, :feedback, :worker_id, :amount, :currency
12
+ require_params :assignment_id, :worker_id, :amount, :feedback
13
+
14
+ def to_params
15
+ {'AssignmentId' => self.assignment_id,
16
+ 'BonusAmount.1.Amount' => self.amount,
17
+ 'BonusAmount.1.CurrencyCode' => (self.currency || 'USD'),
18
+ 'RequesterFeedback' => self.feedback}
19
+ end
20
+
21
+ end
22
+ def self.GrantBonus(*args)
23
+ RTurk::GrantBonus.create(*args)
24
+ end
25
+
26
+ end
@@ -0,0 +1,23 @@
1
+ module RTurk
2
+
3
+ # == Reject Assignment
4
+ #
5
+ # Operation to reject a workers assignment, requires a reason
6
+
7
+ class RejectAssignment < Operation
8
+
9
+ operation 'RejectAssignment'
10
+ attr_accessor :assignment_id, :feedback
11
+ require_params :assignment_id, :feedback
12
+
13
+ def to_params
14
+ {'AssignmentId' => self.assignment_id,
15
+ 'RequesterFeedback' => self.feedback}
16
+ end
17
+
18
+ end
19
+ def self.RejectAssignment(*args)
20
+ RTurk::RejectAssignment.create(*args)
21
+ end
22
+
23
+ end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'cgi'
3
2
  require 'digest/sha1'
4
3
  require 'base64'
@@ -6,85 +5,68 @@ require 'time'
6
5
  require 'base64'
7
6
  require 'digest/sha1'
8
7
  require 'restclient'
9
- require 'xmlsimple'
10
8
 
11
9
  module RTurk
12
10
  class Requester
13
- include RTurk::Utilities
14
- include RTurk::CustomOperations
15
11
 
16
- SANDBOX = 'http://mechanicalturk.sandbox.amazonaws.com/'
17
- PRODUCTION = 'http://mechanicalturk.amazonaws.com/'
18
-
19
- attr_reader :access_key, :secret_key, :host
20
-
21
- def initialize(access_key, secret_key, opts ={})
22
- @access_key = access_key
23
- @secret_key = secret_key
24
- @host = opts[:sandbox] ? SANDBOX : PRODUCTION
25
- end
26
-
27
- def raw_request(params = {})
28
- params = stringify_keys(params)
29
- base_params = {
30
- 'Service'=>'AWSMechanicalTurkRequester',
31
- 'AWSAccessKeyId' => self.access_key,
32
- 'Timestamp' => Time.now.iso8601,
33
- 'Version' => '2008-08-02'
34
- }
35
-
36
- params.merge!(base_params)
37
- signature = sign(params['Service'], params['Operation'], params["Timestamp"])
38
- params['Signature'] = signature
39
- querystring = params.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&') # order doesn't matter for the actual request
40
-
41
- response = RestClient.get("#{self.host}?#{querystring}")
42
- end
43
-
44
- def request(params = {})
45
- response = self.raw_request(params)
46
- XmlSimple.xml_in(response.to_s, {'ForceArray' => false})
47
- end
48
-
49
- def environment
50
- @host.match(/sandbox/) ? 'sandbox' : 'production'
51
- end
52
-
53
- def method_missing(method, opts)
54
- method = method.to_s
55
- method = method[0,1].upcase + method[1,method.size-1]
56
- opts.merge!(:Operation => method)
57
- request(opts)
58
- end
59
-
60
- private
61
-
62
- def sign(service,method,time)
63
- msg = "#{service}#{method}#{time}"
64
- return hmac_sha1( self.secret_key, msg )
12
+ class << self
13
+ include RTurk::Utilities
14
+
15
+ # @param [Hash] params
16
+ # @option [String] 'Operation' The operation - Required
17
+ # @option [String] Any Pass any other params and they will be included in the request
18
+ #
19
+ def request(params = {})
20
+ params.delete_if {|k,v| v.nil? }
21
+ params = stringify_keys(params)
22
+ base_params = {
23
+ 'Service'=>'AWSMechanicalTurkRequester',
24
+ 'AWSAccessKeyId' => credentials.access_key,
25
+ 'Timestamp' => Time.now.iso8601,
26
+ 'Version' => '2008-08-02'
27
+ }
28
+
29
+ params.merge!(base_params)
30
+ signature = sign(credentials.secret_key, params['Service'], params['Operation'], params["Timestamp"])
31
+ params['Signature'] = signature
32
+ querystring = params.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&') # order doesn't matter for the actual request
33
+ RTurk.log.debug "Sending request:\n\t #{credentials.host}?#{querystring}"
34
+ RestClient.get("#{credentials.host}?#{querystring}")
35
+ end
36
+
37
+ private
38
+
39
+ def credentials
40
+ RTurk
41
+ end
42
+
43
+ def sign(secret_key, service,method,time)
44
+ msg = "#{service}#{method}#{time}"
45
+ return hmac_sha1(secret_key, msg )
46
+ end
47
+
48
+ def hmac_sha1(key, s)
49
+ ipad = [].fill(0x36, 0, 64)
50
+ opad = [].fill(0x5C, 0, 64)
51
+ key = key.unpack("C*")
52
+ key += [].fill(0, 0, 64-key.length) if key.length < 64
53
+
54
+ inner = []
55
+ 64.times { |i| inner.push(key[i] ^ ipad[i]) }
56
+ inner += s.unpack("C*")
57
+
58
+ outer = []
59
+ 64.times { |i| outer.push(key[i] ^ opad[i]) }
60
+ outer = outer.pack("c*")
61
+ outer += Digest::SHA1.digest(inner.pack("c*"))
62
+
63
+ return Base64::encode64(Digest::SHA1.digest(outer)).chomp
64
+ end
65
65
  end
66
-
67
-
68
-
69
- def hmac_sha1(key, s)
70
- ipad = [].fill(0x36, 0, 64)
71
- opad = [].fill(0x5C, 0, 64)
72
- key = key.unpack("C*")
73
- key += [].fill(0, 0, 64-key.length) if key.length < 64
74
-
75
- inner = []
76
- 64.times { |i| inner.push(key[i] ^ ipad[i]) }
77
- inner += s.unpack("C*")
78
-
79
- outer = []
80
- 64.times { |i| outer.push(key[i] ^ opad[i]) }
81
- outer = outer.pack("c*")
82
- outer += Digest::SHA1.digest(inner.pack("c*"))
83
-
84
- return Base64::encode64(Digest::SHA1.digest(outer)).chomp
85
- end
86
-
87
-
88
66
  end
89
- end
67
+
68
+ def self.Request(*args)
69
+ RTurk::Requester.request(*args)
70
+ end
90
71
 
72
+ end
@@ -0,0 +1,58 @@
1
+ require 'nokogiri'
2
+
3
+ module RTurk
4
+ class Response
5
+ include RTurk::XmlUtilities
6
+
7
+ #
8
+ # In some cases we want more than just a hash parsed from the returned
9
+ # XML. This class is our response object, and it can be extended for more
10
+ # functionality.
11
+ #
12
+
13
+ attr_reader :xml, :raw_xml
14
+
15
+ def initialize(response)
16
+ @raw_xml = response
17
+ @xml = Nokogiri::XML(@raw_xml)
18
+ raise_errors
19
+ end
20
+
21
+ def success?
22
+ @xml.xpath('//Request/IsValid').inner_text.strip == "True"
23
+ end
24
+
25
+ def errors
26
+ errors = []
27
+ @xml.xpath('//Errors').each do |error|
28
+ errors << {:code => error.xpath('Error/Code').inner_text,
29
+ :message => error.xpath('Error/Message').inner_text}
30
+ end
31
+ errors
32
+ end
33
+
34
+ def humanized_errors
35
+ string = self.errors.inject('') { |str, error|
36
+ str + "#{error[:code]}: #{error[:message]}"
37
+ }
38
+ string
39
+ end
40
+
41
+ def raise_errors
42
+ raise InvalidRequest, self.humanized_errors unless self.success?
43
+ end
44
+
45
+ def [](element_name)
46
+ self.elements[element_name]
47
+ end
48
+
49
+ def xpath(*args)
50
+ self.xml.xpath(*args)
51
+ end
52
+
53
+ def elements
54
+ xml_to_hash(@xml)
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,33 @@
1
+ # Parses out the CreateHIT response
2
+ #
3
+ # Example response:
4
+ # <CreateHITResponse>
5
+ # <OperationRequest>
6
+ # <RequestId>ece2785b-6292-4b12-a60e-4c34847a7916</RequestId>
7
+ # </OperationRequest>
8
+ # <HIT>
9
+ # <Request>
10
+ # <IsValid>True</IsValid>
11
+ # </Request>
12
+ # <HITId>GBHZVQX3EHXZ2AYDY2T0</HITId>
13
+ # <HITTypeId>NYVZTQ1QVKJZXCYZCZVZ</HITTypeId>
14
+ # </HIT>
15
+ # </CreateHITResponse>
16
+
17
+ module RTurk
18
+ class CreateHITResponse < Response
19
+
20
+ def hit_id
21
+ @xml.xpath('//HITId').inner_text
22
+ end
23
+
24
+ def hit_type_id
25
+ @xml.xpath('//HITTypeId').inner_text
26
+ end
27
+
28
+ def hit
29
+ RTurk::Hit.new(self.hit_id, self.hit_type_id)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ module RTurk
2
+
3
+ class GetAccountBalanceResponse < Response
4
+
5
+ def amount
6
+ @xml.xpath('//AvailableBalance[1]/Amount').inner_text.to_f
7
+ end
8
+
9
+ end
10
+
11
+ end
@@ -0,0 +1,43 @@
1
+ # <GetAssignmentsForHITResult>
2
+ # <Request>
3
+ # <IsValid>True</IsValid>
4
+ # </Request>
5
+ # <NumResults>1</NumResults>
6
+ # <TotalNumResults>1</TotalNumResults>
7
+ # <PageNumber>1</PageNumber>
8
+ # <Assignment>
9
+ # <AssignmentId>GYFTRHZ5J3DZREY48WNZE38ZR9RR1ZPMXGWE7WE0</AssignmentId>
10
+ # <WorkerId>AD20WXZZP9XXK</WorkerId>
11
+ # <HITId>GYFTRHZ5J3DZREY48WNZ</HITId>
12
+ # <AssignmentStatus>Approved</AssignmentStatus>
13
+ # <AutoApprovalTime>2009-08-12T19:21:54Z</AutoApprovalTime>
14
+ # <AcceptTime>2009-07-13T19:21:40Z</AcceptTime>
15
+ # <SubmitTime>2009-07-13T19:21:54Z</SubmitTime>
16
+ # <ApprovalTime>2009-07-13T19:27:54Z</ApprovalTime>
17
+ # <Answer>
18
+ # <?xml version="1.0" encoding="UTF-8"?>
19
+ # <QuestionFormAnswers xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionFormAnswers.xsd">
20
+ # <Answer>
21
+ # <QuestionIdentifier>Question100</QuestionIdentifier>
22
+ # <FreeText>Move to X.</FreeText>
23
+ # </Answer>
24
+ # </QuestionFormAnswers>
25
+ # </Answer>
26
+ # </Assignment>
27
+ # </GetAssignmentsForHITResult>
28
+
29
+ module RTurk
30
+
31
+ class GetAssignmentsForHITResponse < Response
32
+
33
+ def assignments
34
+ assignments = []
35
+ @xml.xpath('//Assignment').each do |assignment_xml|
36
+ assignments << RTurk::Assignment.new(assignment_xml)
37
+ end
38
+ assignments
39
+ end
40
+
41
+ end
42
+
43
+ end