From cf4b26692f2192e4a8ba61b010013d46f70ea82a Mon Sep 17 00:00:00 2001 From: Mark Percival Date: Wed, 14 Oct 2009 22:42:43 -0400 Subject: [PATCH] some of these files are leaving --- TODO.markdown | 3 + lib/rturk/base.rb | 0 lib/rturk/builders/qualifications_builder.rb | 98 ++++++++++++++++++++ lib/rturk/builders/question_builder.rb | 50 ++++++++++ lib/rturk/credentials.rb | 16 ++++ lib/rturk/logging.rb | 21 +++++ lib/rturk/operations/create_hit.rb | 61 ++++++++++++ lib/rturk/operations/get_answer.rb | 7 ++ lib/rturk/parsers/answer_parser.rb | 20 ++++ lib/rturk/responses/basic_response.rb | 20 ++++ lib/rturk/responses/create_hit_response.rb | 5 + spec/fake_responses/create_hit.xml | 12 +++ spec/hit_operation_spec.rb | 41 ++++++++ spec/hit_spec.yml | 17 ++++ spec/operation_spec.rb | 22 +++++ spec/what_i_want_spec.rb | 29 ++++++ 16 files changed, 422 insertions(+) create mode 100644 TODO.markdown create mode 100644 lib/rturk/base.rb create mode 100644 lib/rturk/builders/qualifications_builder.rb create mode 100644 lib/rturk/builders/question_builder.rb create mode 100644 lib/rturk/credentials.rb create mode 100644 lib/rturk/logging.rb create mode 100644 lib/rturk/operations/create_hit.rb create mode 100644 lib/rturk/operations/get_answer.rb create mode 100644 lib/rturk/parsers/answer_parser.rb create mode 100644 lib/rturk/responses/basic_response.rb create mode 100644 lib/rturk/responses/create_hit_response.rb create mode 100644 spec/fake_responses/create_hit.xml create mode 100644 spec/hit_operation_spec.rb create mode 100644 spec/hit_spec.yml create mode 100644 spec/operation_spec.rb create mode 100644 spec/what_i_want_spec.rb diff --git a/TODO.markdown b/TODO.markdown new file mode 100644 index 0000000..3c8ddc0 --- /dev/null +++ b/TODO.markdown @@ -0,0 +1,3 @@ +Fakeweb all requests, should be able to turn off +Better error handling and checking +Operations should include request? \ No newline at end of file diff --git a/lib/rturk/base.rb b/lib/rturk/base.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/rturk/builders/qualifications_builder.rb b/lib/rturk/builders/qualifications_builder.rb new file mode 100644 index 0000000..5f6b7ef --- /dev/null +++ b/lib/rturk/builders/qualifications_builder.rb @@ -0,0 +1,98 @@ +module RTurk + + class Qualifications + + # For more information about qualification requirements see: + # http://docs.amazonwebservices.com/AWSMturkAPI/2008-08-02/index.html?ApiReference_QualificationRequirementDataStructureArticle.html + # + + COMPARATORS = {:gt => 'GreaterThan', :lt => 'LessThan', :gte => 'GreaterThanOrEqualTo', + :lte => 'LessThanOrEqualTo', :eql => 'EqualTo', :not => 'NotEqualTo', :exists => 'Exists'} + + TYPES = {:approval_rate => '000000000000000000L0', :submission_rate => '00000000000000000000', + :abandoned_rate => '0000000000000000007', :return_rate => '000000000000000000E0', + :rejection_rate => '000000000000000000S0', :hits_approved => '00000000000000000040', + :adult => '00000000000000000060', :country => '00000000000000000071'} + + attr_accessor :requirements, :types + + def initialize + @requirements = [] + @types = {} + end + + + # Builds the basic requirements for a qualification + # needs at the minimum + # :type_id, :comparator => :value + # + def build (opts) + # If the value is a string, we can assume it's the country since, + # Amazon states that there can be only integer values and countries + operation = opts.reject{|k,v| !COMPARATORS.include?(k)} + comparator = COMPARATORS[operation.keys.first] + value = operation.values.first + params = {} + value = 1 if value == true # For boolean types eg. Adult + if value.to_s.match(/[A-Z]./) + params[:Country] = value + else + params[:IntegerValue] = value + end + params = params.merge({:QualificationTypeId => opts[:type_id], + :Comparator => comparator, :RequiredToPreview => opts[:required_to_preview]}) + end + + def to_aws_params + params = {} + @requirements.each_with_index do |qualifier, i| + params["QualificationRequirement.#{i+1}.QualificationTypeId"] = qualifier[:QualificationTypeId] + params["QualificationRequirement.#{i+1}.Comparator"] = qualifier[:Comparator] + params["QualificationRequirement.#{i+1}.IntegerValue"] = qualifier[:IntegerValue] if qualifier[:IntegerValue] + params["QualificationRequirement.#{i+1}.LocaleValue.Country"] = qualifier[:Country] if qualifier[:Country] + params["QualificationRequirement.#{i+1}.RequiredToPreview"] = qualifier[:RequiredToPreview] || 'true' + end + params + end + + # Can use this to manually add custom requirement types + # Needs a type name(you can reference this later) + # and the operation as a hash: ':gt => 85' + # Example + # qualifications.add('EnglishSkillsRequirement', :gt => 66, :type_id => '1234567890123456789ABC') + # + def add(opts) + @requirements << self.build(opts) + end + + # This lets you add a custom named type to the list + # Example + # qualifications.add_type(:custom_requirement, '1234567890123456789ABC') + # qualifications.custom_requirement(:gte => 55) + # + def add_type(name, type_id) + @types[name.to_sym] = type_id + end + + def method_missing(method, opts) + if opts == true || opts == false + # allows us to call booleans on a method + # e.g. qualifications.adult(true) + opts = {:eql => opts} + end + if types.include?(method) + opts.merge!({:type_id => types[method]}) + self.add(opts) + end + end + + def types + TYPES.merge(@types) + end + + end + + +end + + diff --git a/lib/rturk/builders/question_builder.rb b/lib/rturk/builders/question_builder.rb new file mode 100644 index 0000000..bd8a147 --- /dev/null +++ b/lib/rturk/builders/question_builder.rb @@ -0,0 +1,50 @@ +require 'cgi' + +module RTurk + class Question + + attr_accessor :url, :url_params, :frame_height + + def initialize(url = nil, opts = {}) + @url = url + self.frame_height = opts.delete(:frame_height) || 400 + self.url_params = opts + end + + def querystring + @url_params.collect { |key, value| [CGI.escape(key.to_s), CGI.escape(value.to_s)].join('=') }.join('&') + end + + def url + unless querystring.empty? + # slam the params onto url, if url already has params, add 'em with a & + @url.index('?') ? "#{@url}&#{querystring}" : "#{@url}?#{querystring}" + else + @url + end + end + + def params + @url_params + end + + def params=(param_set) + @url_params = param_set + end + + def to_aws_params + raise MissingURL, "needs a url to build an external question" unless @url + # TODO: update the xmlns schema... maybe + xml = <<-XML + + #{url} + #{frame_height} + + XML + xml + end + + end + + +end \ No newline at end of file diff --git a/lib/rturk/credentials.rb b/lib/rturk/credentials.rb new file mode 100644 index 0000000..d422a78 --- /dev/null +++ b/lib/rturk/credentials.rb @@ -0,0 +1,16 @@ +module RTurk + module Credentials + + SANDBOX = 'http://mechanicalturk.sandbox.amazonaws.com/' + PRODUCTION = 'http://mechanicalturk.amazonaws.com/' + + attr_reader :access_key, :secret_key, :host + + def initialize(access_key, secret_key, opts ={}) + @access_key = access_key + @secret_key = secret_key + @host = opts[:sandbox] ? SANDBOX : PRODUCTION + end + + end +end \ No newline at end of file diff --git a/lib/rturk/logging.rb b/lib/rturk/logging.rb new file mode 100644 index 0000000..9a22638 --- /dev/null +++ b/lib/rturk/logging.rb @@ -0,0 +1,21 @@ +require 'logger' + +module RTurk + module Logging + def logger=(logger_obj) + @logger = logger_obj + end + + def logger + unless @logger + @logger = Logger.new(STDOUT) + @logger.level = Logger::INFO + end + @logger + end + + def log_level=(level=Logger::INFO) + logger.level = level + end + end +end \ No newline at end of file diff --git a/lib/rturk/operations/create_hit.rb b/lib/rturk/operations/create_hit.rb new file mode 100644 index 0000000..4893c2c --- /dev/null +++ b/lib/rturk/operations/create_hit.rb @@ -0,0 +1,61 @@ +module RTurk + class CreateHit < Operation + # + # We perform the magic here to create a HIT with the minimum amount of fuss. + # You should be able to pass in a hash with all the setting(ala YAML) or + # do all the config in a block. + # + + attr_accessor :title, :keywords, :description, :reward, :currency, :assignments + attr_accessor :lifetime, :duration, :auto_approval, :note, :qualifications + + + def initialize(opts = {}) + opts.each_pair do |k,v| + if v.is_a? Hash + obj = self.send k.to_sym + v.each_pair do |key,val| + obj.send key.to_sym, val + end + elsif self.respond_to?("#{k.to_sym}=") + self.send "#{k}=".to_sym, v + elsif self.respond_to?(k.to_sym) + self.send k.to_sym, v + end + end + yield(self) if block_given? + end + + def qualification + @qualifications ||= RTurk::Qualifications.new + end + + def question + @question ||= RTurk::Question.new + end + + def to_aws_params + hit_params.merge(qualification.to_aws_params) + end + + def response(xml) + RTurk::CreateHitResponse.parse(xml) + end + + private + + def hit_params + {'Title'=>self.title, + 'MaxAssignments' => self.assignments, + 'LifetimeInSeconds'=> self.lifetime, + 'Reward.Amount' => self.reward, + 'Reward.CurrencyCode' => (self.currency || 'USD'), + 'Keywords' => self.keywords, + 'Description' => self.description, + 'RequesterAnnotation' => note} + end + + RTurk::Operation.register('create_hit', self) + + end +end diff --git a/lib/rturk/operations/get_answer.rb b/lib/rturk/operations/get_answer.rb new file mode 100644 index 0000000..8c61c32 --- /dev/null +++ b/lib/rturk/operations/get_answer.rb @@ -0,0 +1,7 @@ +module RTurk + class GetAnswer < Operation + def get_it + + end + end +end diff --git a/lib/rturk/parsers/answer_parser.rb b/lib/rturk/parsers/answer_parser.rb new file mode 100644 index 0000000..71c08ba --- /dev/null +++ b/lib/rturk/parsers/answer_parser.rb @@ -0,0 +1,20 @@ +module RTurk + class AnswerParser + + def self.parse(xml) + answer = XmlSimple.xml_in(xml, {'ForceArray' => false}) + response = {} + answers = answer['Answer'] + answers = Array.new(1) { answers } unless answers.instance_of? Array + answers.each do |a| + question = a['QuestionIdentifier'] + a.delete('QuestionIdentifier') + a.each_value do |v| + response[question] = v + end + end + response + end + + end +end \ No newline at end of file diff --git a/lib/rturk/responses/basic_response.rb b/lib/rturk/responses/basic_response.rb new file mode 100644 index 0000000..8a10c04 --- /dev/null +++ b/lib/rturk/responses/basic_response.rb @@ -0,0 +1,20 @@ +module RTurk + + class BasicResponse < Response + + def parsed_xml + @parsed_xml ||= XmlSimple.xml_in(@xml.to_s, {'ForceArray' => false}) + end + + def success? + !parsed_xml['HIT']['Request'].include?('Errors') + end + + def [](key) + parsed_xml[key] + end + + + end + +end diff --git a/lib/rturk/responses/create_hit_response.rb b/lib/rturk/responses/create_hit_response.rb new file mode 100644 index 0000000..8e53a48 --- /dev/null +++ b/lib/rturk/responses/create_hit_response.rb @@ -0,0 +1,5 @@ +module RTurk + class HitResponse < Response + + end +end \ No newline at end of file diff --git a/spec/fake_responses/create_hit.xml b/spec/fake_responses/create_hit.xml new file mode 100644 index 0000000..d611f4e --- /dev/null +++ b/spec/fake_responses/create_hit.xml @@ -0,0 +1,12 @@ + + + ece2785b-6292-4b12-a60e-4c34847a7916 + + + + True + + GBHZVQX3EHXZ2AYDY2T0 + NYVZTQ1QVKJZXCYZCZVZ + + \ No newline at end of file diff --git a/spec/hit_operation_spec.rb b/spec/hit_operation_spec.rb new file mode 100644 index 0000000..2610786 --- /dev/null +++ b/spec/hit_operation_spec.rb @@ -0,0 +1,41 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "The creation of a HIT operation" do + + before(:all) do + aws = YAML.load(File.open(File.join(SPEC_ROOT, 'mturk.yml'))) + @turk = RTurk::Requester.new(aws['AWSAccessKeyId'], aws['AWSAccessKey'], :sandbox => true) + end + + it "should allow us to create HIT's in a ruby'esque way" do + + hit = RTurk::Hit.new(:assignments => 5) do |hit| + hit.qualification.approval :gt => 90 + hit.qualification.country :not => 'PH' + hit.qualification.adult true + hit.title = "Look at some dirty pictures from 4Chan" + hit.question('http://mpercival.com', :chapter => 1) + hit.question.params[:chapter] = 2 #change the parameters + end + + hit.assignments.should eql 5 + hit.title.should eql "Look at some dirty pictures from 4Chan" + hit.question.url.should == "http://mpercival.com?chapter=2" + end + + it "should allow us to create HIT's with a hash from yaml" do + + hit = RTurk::Hit.new(:assignments => 5, :title => "Look at some dirty pictures from 4Chan", + :question => 'http://mpercival.com', :assignments => 5, + :qualification => + { + :approval => {:gt => 90}, + :adult => true, + :country => {:not => 'PH'} + }, + :description => "Get paid in nickels!") + hit.assignments.should eql 5 + hit.title.should eql "Look at some dirty pictures from 4Chan" + end + +end \ No newline at end of file diff --git a/spec/hit_spec.yml b/spec/hit_spec.yml new file mode 100644 index 0000000..94bb8d2 --- /dev/null +++ b/spec/hit_spec.yml @@ -0,0 +1,17 @@ +--- +:Description: Simply write a twitter update for me +:LifetimeInSeconds: 3600 +:RequesterAnnotation: OptionalNote +:Reward: + :Amount: 0.1 + :CurrencyCode: USD +:AssignmentDurationInSeconds: 3600 +:AutoApprovalDelayInSeconds: 3600 +:QualificationRequirement: +- :IntegerValue: 90 + :Comparator: GreaterThan + :RequiredToPreview: "false" + :QualificationTypeId: 000000000000000000L0 +:Title: Write a twitter update +:Keywords: twitter, blogging, writing, english +:MaxAssignments: 1 diff --git a/spec/operation_spec.rb b/spec/operation_spec.rb new file mode 100644 index 0000000..7976ac6 --- /dev/null +++ b/spec/operation_spec.rb @@ -0,0 +1,22 @@ +require File.dirname(__FILE__) + '/spec_helper' + + +describe RTurk::Operation do + + before(:all) do + class Mark < RTurk::Operation + + def is_awesome? + true + end + RTurk::Operation.register('mark', self) + end + + end + + it "should build a question" do + p RTurk::Operation.defined_operations + RTurk::Operation.defined_operations.include?('mark').should be_true + end + +end \ No newline at end of file diff --git a/spec/what_i_want_spec.rb b/spec/what_i_want_spec.rb new file mode 100644 index 0000000..5a0966d --- /dev/null +++ b/spec/what_i_want_spec.rb @@ -0,0 +1,29 @@ +require File.dirname(__FILE__) + '/spec_helper' + +describe "using mechanical turk with RTurk" do + + before(:all) do + aws = YAML.load(File.open(File.join(SPEC_ROOT, 'mturk.yml'))) + @turk = RTurk.setup(aws['AWSAccessKeyId'], aws['AWSAccessKey'], :sandbox => true) + end + + it "should let me create a hit" do + + response = RTurk::CreateHit.new(:title => "Look at some pictures from 4Chan") do |hit| + hit.assignments = 5 + hit.question.url = "http://mpercival.com" + hit.question.params = {:picture_set => 1234} + hit.reward = 0.05 + hit.qualifications.approval_rate.greater_than 80 + hit.qualifications.add(:country => 'PH') + hit.qualifications.add(:adult => true) + end + response.success?.should be_true + end + + + it "should let me delete all my hits" do + @turk.delete_all_hits + end + +end \ No newline at end of file