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