├── VERSION ├── bindev ├── crowdflower.rb └── cl_skel.rb ├── .document ├── .gitignore ├── CONTRIBUTORS ├── lib ├── crowdflower.rb └── crowdflower │ ├── order.rb │ ├── judgment.rb │ ├── unit.rb │ ├── worker.rb │ ├── job.rb │ └── base.rb ├── test ├── sample.csv └── integration_tests.rb ├── HISTORY.md ├── LICENSE ├── README.md ├── Rakefile └── crowdflower.gemspec /VERSION: -------------------------------------------------------------------------------- 1 | 0.5.3 2 | -------------------------------------------------------------------------------- /bindev/crowdflower.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | README.rdoc 2 | lib/**/*.rb 3 | bin/* 4 | features/**/*.feature 5 | LICENSE 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw? 2 | .DS_Store 3 | coverage 4 | rdoc 5 | pkg 6 | tags/.loadpath 7 | /.project 8 | /.loadpath 9 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | * Chris Van Pelt (vanpelt) 2 | * Brian P O'Rourke (bpo) 3 | * Mark Sands (marksands) 4 | * Mark Erdmann (markerdmann) 5 | -------------------------------------------------------------------------------- /lib/crowdflower.rb: -------------------------------------------------------------------------------- 1 | require 'httparty' 2 | 3 | %w{base job unit judgment order worker}.each do |file| 4 | require File.dirname(__FILE__) +"/crowdflower/" + file 5 | end 6 | -------------------------------------------------------------------------------- /lib/crowdflower/order.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | class Order < Base 3 | attr_reader :job 4 | 5 | def initialize(job) 6 | super job.connection 7 | @job = job 8 | connect 9 | end 10 | 11 | def resource_uri 12 | "/jobs/#{@job.id}/orders.json" 13 | end 14 | 15 | def debit(units_count = 1, channels = ["amt"]) 16 | connection.post(resource_uri, {:query => {:debit => {:units_count => units_count}, :channels => channels}}) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/sample.csv: -------------------------------------------------------------------------------- 1 | "created_at","from_user","text" 2 | "Sat, 10 Oct 2009 01:00:07 +0000","sogowave","CrowdFlower easily connects you with thousands of people online, around the clock. http://ow.ly/rpaT" 3 | "Sat, 10 Oct 2009 01:00:05 +0000","SogoPR","CrowdFlower easily connects you with thousands of people online, around the clock. http://ow.ly/rpaS" 4 | "Fri, 09 Oct 2009 15:00:07 +0000","SIIA_Software","...and don't forget: @CloudTrigger @businessobjects @longjump @CrowdFlower http://bit.ly/397y72" 5 | "Fri, 02 Oct 2009 19:49:50 +0000","julzie","RT @rev2 CrowdFlower - Labor as a Service in a New Concept | Rev2.org http://retwt.me/v80H - GO LaaS!!" 6 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## 0.5.1 (2011-02-03) 2 | * Added individual judgment rejection to API. 3 | 4 | ## 0.5.0 (2011-02-01) 5 | * Worker API added for managing workers (flagging, approving, etc.) 6 | 7 | ## 0.4.6 (2010-12-17) 8 | * Multiple API keys can be used simultaneously 9 | 10 | ## 0.4.0 (2010-03-25) 11 | * Allow key configuration from YAML 12 | * ruby-crowdflower is now just 'crowdflower' 13 | * Unit#create now creates non-gold units by default 14 | * Cleaned and removed the httparty monkey patches 15 | * Job#create now creates an empty job with a title 16 | 17 | ## 0.2.2 (2010-02-03) 18 | * Simple Job#update addition 19 | 20 | ## 0.2.1 (2010-01-24) 21 | * Added latest attribute to judgments 22 | * Fixed judgment class 23 | * Added judgment for a unit 24 | 25 | ## 0.2.0 (2009-10-13) 26 | 27 | * Integration tests for CrowdFlower servers 28 | * No more API redirects 29 | * Webhook capability added 30 | 31 | ## 0.1.0 (2009-09-14) 32 | 33 | * First release 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 Dolores Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruby-crowdflower 2 | 3 | A toolkit for interacting with CrowdFlower via the REST API. 4 | 5 | This is alpha software. Have fun! 6 | 7 | Example Usage 8 | ------------- 9 | 10 | Specifiy either your api key or a yaml file containing the key: 11 | 12 | CrowdFlower.connect!( 'CrowdFlower.yml' ) 13 | 14 | Upload a CSV file for a job: 15 | 16 | CrowdFlower::Job.upload( File.dirname(__FILE__) + "/data.csv", "text/csv" ) 17 | 18 | Copy an existing job into a new one: 19 | 20 | new_job = job.copy( :all_units => true ) 21 | 22 | Check the status of a job: 23 | 24 | job.status["tainted_judgments"] 25 | 26 | 27 | 28 | Contributing 29 | ------------ 30 | 31 | 1. Fork ruby-crowdflower 32 | 2. Create a topic branch - `git checkout -b my_branch` 33 | 3. Make your feature addition or bug fix and add tests for it. 34 | 4. Commit, but do not mess with the rakefile, version, or history. 35 | 5. Push to your branch - `git push origin my_branch` 36 | 6. Create an Issue with a link to your branch 37 | 38 | Copyright 39 | --------- 40 | 41 | Copyright © 2010 [Dolores Labs](http://www.doloreslabs.com/). See LICENSE for details. 42 | -------------------------------------------------------------------------------- /lib/crowdflower/judgment.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | class Judgment < Base 3 | attr_reader :job 4 | 5 | def initialize(job) 6 | super job.connection 7 | @job = job 8 | connect 9 | end 10 | 11 | def resource_uri 12 | "/jobs/#{@job.id}/judgments" 13 | end 14 | 15 | #Pull every judgment 16 | def all(page = 1, limit = 100, latest = true) 17 | opts = CrowdFlower.version == 2 ? {:unseen => latest} : {:latest => latest} 18 | get(resource_uri, {:query => {:limit => limit, :page => page}.merge(opts)}) 19 | end 20 | 21 | def get(id) 22 | get("#{resource_uri}/#{id}") 23 | end 24 | 25 | # Reject an individual Judgment. 26 | # 27 | # *Admin-only && MTurk-only* 28 | # 29 | # @param [String,Integer] id The CrowdFlower id for the judgment to reject. 30 | def reject( id ) 31 | put( "#{resource_uri}/#{id}/reject", :headers => { "Content-Length" => 0 } ) 32 | end 33 | 34 | protected 35 | 36 | def put( *args ) 37 | connection.put *args 38 | end 39 | 40 | def get( *args ) 41 | connection.get *args 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/crowdflower/unit.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | class Unit < Base 3 | attr_reader :job 4 | 5 | def initialize(job) 6 | super job.connection 7 | @job = job 8 | connect 9 | end 10 | 11 | def resource_uri 12 | "/jobs/#{@job.id}/units" 13 | end 14 | 15 | def all(page = 1, limit = 1000) 16 | connection.get(resource_uri, {:query => {:limit => limit, :page => page}}) 17 | end 18 | 19 | def get(id) 20 | connection.get("#{resource_uri}/#{id}") 21 | end 22 | 23 | def ping 24 | connection.get("#{resource_uri}/ping") 25 | end 26 | 27 | def judgments(id) 28 | connection.get("#{resource_uri}/#{id}/judgments") 29 | end 30 | 31 | def create(data, gold = false) 32 | connection.post(resource_uri, {:query => {:unit => {:data => data.to_json, :golden => gold}}}) 33 | end 34 | 35 | def copy(unit_id, job_id, data = {}) 36 | connection.get("#{resource_uri}/#{unit_id}/copy", {:query => {:unit => {:job_id => job_id, :data => data}}}) 37 | end 38 | 39 | def split(on, with = " ") 40 | connection.get("#{resource_uri}/split", {:query => {:on => on, :with => with}}) 41 | end 42 | 43 | def cancel(unit_id) 44 | connection.post("#{resource_uri}/#{unit_id}/cancel.json") 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | require 'jeweler' 6 | Jeweler::Tasks.new do |gem| 7 | gem.name = "crowdflower" 8 | gem.summary = %Q{a toolkit for the CrowdFlower API} 9 | gem.description = <<-EOF 10 | A toolkit for interacting with CrowdFlower via the REST API. 11 | 12 | This is alpha software. Have fun! 13 | 14 | EOF 15 | gem.email = "brian@doloreslabs.com" 16 | gem.homepage = "http://github.com/dolores/ruby-crowdflower" 17 | gem.authors = ["Brian P O'Rourke", "Chris Van Pelt"] 18 | gem.add_dependency 'httparty', '>= 0.4.3' 19 | end 20 | 21 | rescue LoadError 22 | puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" 23 | end 24 | 25 | task :default => :build 26 | 27 | task :refresh_builder => [:build] do 28 | cp "pkg/crowdflower-#{File.read("VERSION").strip}.gem", "../builder/gems/cache/" 29 | rm_rf "../builder/gems/gems/crowdflower-#{File.read("VERSION").strip}/" 30 | `cd ../builder && bin/thor merb:gem:redeploy` 31 | end 32 | 33 | require 'rake/rdoctask' 34 | Rake::RDocTask.new do |rdoc| 35 | if File.exist?('VERSION.yml') 36 | config = YAML.load(File.read('VERSION.yml')) 37 | version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}" 38 | else 39 | version = "" 40 | end 41 | 42 | rdoc.rdoc_dir = 'rdoc' 43 | rdoc.title = "crowdflower #{version}" 44 | rdoc.rdoc_files.include('README*') 45 | rdoc.rdoc_files.include('lib/**/*.rb') 46 | end 47 | -------------------------------------------------------------------------------- /lib/crowdflower/worker.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | class Worker < Base 3 | attr_reader :job 4 | 5 | def initialize( job ) 6 | super job.connection 7 | @job = job 8 | connect 9 | end 10 | 11 | def resource_uri 12 | "/jobs/#{@job.id}/workers" 13 | end 14 | 15 | def bonus( worker_id, amount, reason=nil ) 16 | params = { :amount => amount } 17 | params.merge!( :reason => reason ) if reason 18 | connection.post( "#{resource_uri}/#{worker_id}/bonus", :query => params ) 19 | end 20 | 21 | def approve( worker_id ) 22 | connection.put( "#{resource_uri}/#{worker_id}/approve", :headers => { "Content-Length" => "0" } ) 23 | end 24 | 25 | def reject( worker_id ) 26 | connection.put( "#{resource_uri}/#{worker_id}/reject", :headers => { "Content-Length" => "0" } ) 27 | end 28 | 29 | def ban( worker_id ) 30 | connection.put( "#{resource_uri}/#{worker_id}/ban", :headers => { "Content-Length" => "0" } ) 31 | end 32 | 33 | def deban( worker_id ) 34 | connection.put( "#{resource_uri}/#{worker_id}/deban", :headers => { "Content-Length" => "0" } ) 35 | end 36 | 37 | def notify( worker_id, subject, message ) 38 | params = { 39 | :subject => subject, 40 | :message => message 41 | } 42 | connection.post( "#{resource_uri}/#{worker_id}/notify", :query => params, :headers => { "Content-Length" => "0" } ) 43 | end 44 | 45 | def flag( worker_id, reason ) 46 | params = reason ? { :reason => reason } : nil 47 | connection.put( "#{resource_uri}/#{worker_id}/flag", :query => params, :headers => { "Content-Length" => "0" } ) 48 | end 49 | 50 | def deflag( worker_id ) 51 | connection.put( "#{resource_uri}/#{worker_id}/deflag", :headers => { "Content-Length" => "0" } ) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /crowdflower.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | 6 | Gem::Specification.new do |s| 7 | s.name = %q{crowdflower} 8 | s.version = "0.5.4" 9 | 10 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 11 | s.authors = ["Brian P O'Rourke", "Chris Van Pelt"] 12 | s.date = %q{2011-02-13} 13 | s.description = %q{A toolkit for interacting with CrowdFlower via the REST API. 14 | 15 | This is alpha software. Have fun! 16 | 17 | } 18 | s.email = %q{brian@doloreslabs.com} 19 | s.extra_rdoc_files = [ 20 | "LICENSE", 21 | "README.md" 22 | ] 23 | s.files = [ 24 | ".document", 25 | "CONTRIBUTORS", 26 | "HISTORY.md", 27 | "LICENSE", 28 | "README.md", 29 | "Rakefile", 30 | "VERSION", 31 | "bindev/cl_skel.rb", 32 | "bindev/crowdflower.rb", 33 | "crowdflower.gemspec", 34 | "lib/crowdflower.rb", 35 | "lib/crowdflower/base.rb", 36 | "lib/crowdflower/job.rb", 37 | "lib/crowdflower/judgment.rb", 38 | "lib/crowdflower/order.rb", 39 | "lib/crowdflower/unit.rb", 40 | "lib/crowdflower/worker.rb", 41 | "test/integration_tests.rb", 42 | "test/sample.csv" 43 | ] 44 | s.homepage = %q{http://github.com/dolores/ruby-crowdflower} 45 | s.require_paths = ["lib"] 46 | s.rubygems_version = %q{1.5.0} 47 | s.summary = %q{a toolkit for the CrowdFlower API} 48 | s.test_files = [ 49 | "test/integration_tests.rb" 50 | ] 51 | 52 | if s.respond_to? :specification_version then 53 | s.specification_version = 3 54 | 55 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 56 | s.add_runtime_dependency(%q, [">= 0.4.3"]) 57 | else 58 | s.add_dependency(%q, [">= 0.4.3"]) 59 | end 60 | else 61 | s.add_dependency(%q, [">= 0.4.3"]) 62 | end 63 | end 64 | 65 | -------------------------------------------------------------------------------- /lib/crowdflower/job.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | class Job < Base 3 | attr_reader :id 4 | 5 | def initialize(job_id, connection = nil) 6 | super connection 7 | @id = job_id 8 | connect 9 | end 10 | 11 | def resource_uri 12 | Job.resource_uri 13 | end 14 | 15 | def self.resource_uri 16 | "/jobs" 17 | end 18 | 19 | def self.all 20 | connect 21 | get(resource_uri) 22 | end 23 | 24 | def self.upload(file, content_type, job = nil) 25 | connect 26 | 27 | job_uri = job ? "/#{job.kind_of?(Job) ? job.id : job}" : "" 28 | conn = job.kind_of?(Job) ? job.connection : self.connection 29 | upload_uri = "#{resource_uri}/#{job_uri}/upload".squeeze("/") 30 | res = conn.post(upload_uri, 31 | :body => File.read(file), 32 | :headers => {"content-type" => content_type}) 33 | 34 | verify_response res 35 | job.kind_of?(Job) ? job : self.new(res["id"], conn) 36 | end 37 | 38 | # Creates a new empty Job in CrowdFlower. 39 | def self.create(title) 40 | connect 41 | res = self.post("#{resource_uri}.json", :query => {:job => {:title => title } }, :headers => { "Content-Length" => "0" } ) 42 | verify_response res 43 | self.new(res["id"]) 44 | end 45 | 46 | def get(id = nil) 47 | connection.get("#{resource_uri}/#{@id || id}") 48 | end 49 | 50 | # Returns an accessor for all the Units associated with this job. 51 | # This enables you to do things like: 52 | # 53 | # * job.units.all 54 | # * job.units.get [id] 55 | # * job.units.create [data] 56 | def units 57 | Unit.new(self) 58 | end 59 | 60 | # Copies a job and optionally gold or all units. 61 | # Parameters 62 | # opts: Hash 63 | # gold: when set to true copies gold units 64 | # all_units: when set to true copies all units 65 | def copy(opts = {}) 66 | res = connection.get("#{resource_uri}/#{@id}/copy", {:query => opts}) 67 | self.class.verify_response res 68 | self.class.new(res["id"]) 69 | end 70 | 71 | def status 72 | connection.get("#{resource_uri}/#{@id}/ping") 73 | end 74 | 75 | def upload(file, content_type) 76 | self.class.upload(file, content_type, self) 77 | end 78 | 79 | def legend 80 | connection.get("#{resource_uri}/#{@id}/legend") 81 | end 82 | 83 | def download_csv(type = :full, filename = nil) 84 | filename ||= "#{type.to_s[0].chr}#{@id}.csv" 85 | res = connection.get("#{resource_uri}/#{@id}.csv", {:format => :csv, :query => {:type => type}}) 86 | self.class.verify_response res 87 | puts "Status... #{res.code.inspect}" 88 | if res.code == 202 89 | puts "CSV Generating... Trying again in 10 seconds." 90 | Kernel.sleep 10 91 | download_csv(type, filename) 92 | else 93 | puts "CSV written to: #{File.expand_path(filename)}" 94 | File.open(filename, "w") {|f| f.puts res.body } 95 | end 96 | end 97 | 98 | def pause 99 | connection.get("#{resource_uri}/#{@id}/pause") 100 | end 101 | 102 | def resume 103 | connection.get("#{resource_uri}/#{@id}/resume") 104 | end 105 | 106 | def cancel 107 | connection.get("#{resource_uri}/#{@id}/cancel") 108 | end 109 | 110 | def update(data) 111 | response = connection.put("#{resource_uri}/#{@id}.json", {:body => { :job => data }, :headers => { "Content-Length" => "0" } } ) 112 | self.class.verify_response(response) 113 | response 114 | end 115 | 116 | def delete 117 | connection.delete("#{resource_uri}/#{@id}.json") 118 | end 119 | 120 | def channels 121 | connection.get("#{resource_uri}/#{@id}/channels") 122 | end 123 | 124 | def enable_channels(channels) 125 | connection.post("#{resource_uri}/#{@id}/channels", {:body => { :channels => channels } } ) 126 | end 127 | 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /bindev/cl_skel.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # == Synopsis 4 | # Use this command-line utility to create and update CrowdFlower jobs. 5 | # 6 | # == Examples 7 | # Create a new job and populate it with data from a twitter feed. 8 | # crowdflower -n -u 'http://search.twitter.com/search?q=milksteak' 9 | # 10 | # Other examples: 11 | # crowdflower -q bar.doc 12 | # crowdflower --verbose foo.html 13 | # 14 | # == Usage 15 | # crowdflower [options] source_file 16 | # 17 | # For help use: crowdflower -h 18 | # 19 | # == Options 20 | # -h, --help Displays help message 21 | # -v, --version Display the version, then exit 22 | # -q, --quiet Output as little as possible, overrides verbose 23 | # -V, --verbose Verbose output 24 | # -n, --new Create a new job 25 | # -u, --uri Accepts an RSS, Atom, XML, or JSON data feed 26 | # -f, --file Accepts .csv, .tsv, .xls, .xlsx, and .ods 27 | # TO DO - add additional options 28 | # 29 | # == Author 30 | # Mark Erdmann 31 | # 32 | # == Copyright 33 | # Copyright (c) 2007 CrowdFlower. Licensed under the MIT License: 34 | # http://www.opensource.org/licenses/mit-license.php 35 | 36 | 37 | # TO DO - update Synopsis, Examples, etc 38 | # TO DO - change license if necessary 39 | 40 | 41 | 42 | require 'optparse' 43 | require 'rdoc/usage' 44 | require 'ostruct' 45 | require 'date' 46 | 47 | 48 | class App 49 | VERSION = '0.0.1' 50 | 51 | attr_reader :options 52 | 53 | def initialize(arguments, stdin) 54 | @arguments = arguments 55 | @stdin = stdin 56 | 57 | # Set defaults 58 | @options = OpenStruct.new 59 | @options.verbose = false 60 | @options.quiet = false 61 | # TO DO - add additional defaults 62 | end 63 | 64 | # Parse options, check arguments, then process the command 65 | def run 66 | 67 | if parsed_options? && arguments_valid? 68 | 69 | puts "Start at #{DateTime.now}\n\n" if @options.verbose 70 | 71 | output_options if @options.verbose # [Optional] 72 | 73 | process_arguments 74 | process_command 75 | 76 | puts "\nFinished at #{DateTime.now}" if @options.verbose 77 | 78 | else 79 | output_usage 80 | end 81 | 82 | end 83 | 84 | protected 85 | 86 | def parsed_options? 87 | 88 | # Specify options 89 | opts = OptionParser.new 90 | opts.on('-v', '--version') { output_version ; exit 0 } 91 | opts.on('-h', '--help') { output_help } 92 | opts.on('-V', '--verbose') { @options.verbose = true } 93 | opts.on('-q', '--quiet') { @options.quiet = true } 94 | opts.on('-n', '--new') { @options.newjob = true } 95 | opts.on('-u', '--uri') { @options.uri = } 96 | # TO DO - add additional options 97 | 98 | opts.parse!(@arguments) rescue return false 99 | 100 | process_options 101 | true 102 | end 103 | 104 | # Performs post-parse processing on options 105 | def process_options 106 | @options.verbose = false if @options.quiet 107 | end 108 | 109 | def output_options 110 | puts "Options:\n" 111 | 112 | @options.marshal_dump.each do |name, val| 113 | puts " #{name} = #{val}" 114 | end 115 | end 116 | 117 | # True if required arguments were provided 118 | def arguments_valid? 119 | # TO DO - implement your real logic here 120 | true if @arguments.length == 1 121 | end 122 | 123 | # Setup the arguments 124 | def process_arguments 125 | # TO DO - place in local vars, etc 126 | end 127 | 128 | def output_help 129 | output_version 130 | RDoc::usage() #exits app 131 | end 132 | 133 | def output_usage 134 | RDoc::usage('usage') # gets usage from comments above 135 | end 136 | 137 | def output_version 138 | puts "#{File.basename(__FILE__)} version #{VERSION}" 139 | end 140 | 141 | def process_command 142 | # TO DO - do whatever this app does 143 | 144 | #process_standard_input # [Optional] 145 | end 146 | 147 | def process_standard_input 148 | input = @stdin.read 149 | # TO DO - process input 150 | 151 | # [Optional] 152 | # @stdin.each do |line| 153 | # # TO DO - process each line 154 | #end 155 | end 156 | end 157 | 158 | 159 | # TO DO - Add your Modules, Classes, etc 160 | 161 | 162 | # Create and run the application 163 | app = App.new(ARGV, STDIN) 164 | app.run 165 | -------------------------------------------------------------------------------- /test/integration_tests.rb: -------------------------------------------------------------------------------- 1 | $: << File.dirname(__FILE__) + "/../lib" 2 | 3 | require 'rubygems' 4 | require 'crowdflower' 5 | require 'json' 6 | 7 | API_KEY = ENV["API_KEY"] 8 | 9 | unless API_KEY && API_KEY.size > 5 10 | puts < true 118 | 119 | say "-- Waiting for CrowdFlower to finish copying the job." 120 | # You could also register a webhook to have CrowdFlower notify your 121 | # server. 122 | wait_until { job2.get["units_count"] == 8 } 123 | 124 | say "Checking the status of the job." 125 | assert job.status["tainted_judgments"] == 0 126 | 127 | say "Registering a webhook." 128 | job.update :webhook_uri => "http://localhost:8080/crowdflower" 129 | 130 | say "Adding title, instructions, and problem to the job." 131 | job.update({:title => 'testtt', 132 | :instructions => 'testttt fdsf sfds fsdfs fesfsdf', 133 | :cml => ''}) 134 | 135 | say "Checking channels" 136 | assert !job.channels['available_channels'].empty? 137 | assert job.channels['enabled_channels'].empty? 138 | job.enable_channels ['amt'] 139 | assert job.channels['enabled_channels'] == ['amt'] 140 | job.enable_channels ['mob'] 141 | assert job.channels['enabled_channels'].sort == ['amt', 'mob'] 142 | job.enable_channels ['mob'] 143 | assert job.channels['enabled_channels'].sort == ['amt', 'mob'] 144 | 145 | 146 | say "Ordering the job." 147 | order = CrowdFlower::Order.new(job) 148 | unit_count = 8 149 | order.debit(8) 150 | wait_until { job.get["state"].casecmp('running') == 0} 151 | 152 | say "Canceling the unit." 153 | unit_id = job.units.all.to_a[0][0] 154 | unit = CrowdFlower::Unit.new(job) 155 | wait_until { unit.get(unit_id)['state'] == 'judgable' } 156 | puts unit.cancel(unit_id).inspect 157 | assert unit.get(unit_id)['state'] == 'canceled' 158 | 159 | say "Webhook test needs to be written." 160 | #job.test_webhook 161 | 162 | say ">-< Tests complete. >-<" 163 | -------------------------------------------------------------------------------- /lib/crowdflower/base.rb: -------------------------------------------------------------------------------- 1 | module CrowdFlower 2 | @@key = nil 3 | @@domain = nil 4 | 5 | class UsageError < StandardError ; end 6 | 7 | class APIError < StandardError 8 | attr_reader :details 9 | 10 | def initialize(hash) 11 | @details = hash 12 | super @details["message"] 13 | end 14 | end 15 | 16 | # a convenience method for backward compatibility 17 | def self.connect!(key, development = false, version = 1) 18 | Base.connect!(key, development, version) 19 | end 20 | 21 | def self.connect_domain!(key, domain_base, version = 1) 22 | Base.connect_domain!(key, domain_base, version) 23 | end 24 | 25 | def self.connect_config!(opts) 26 | Base.connect_config!(opts) 27 | end 28 | 29 | # an object that stores connection details; does actual http talking 30 | class Connection 31 | include HTTParty 32 | headers "accept" => "application/json" 33 | format :json 34 | 35 | attr_reader :key, :domain, :version, :domain_base, :ssl_port, :public_port 36 | 37 | def initialize(key, domain_base, version, ssl_port = 443, public_port = 80) 38 | @domain_base = domain_base 39 | @version = version 40 | @domain = "#{@domain_base}/v#{version}" 41 | @key = key 42 | @ssl_port = ssl_port 43 | @public_port = public_port 44 | begin # pass yaml file 45 | key = YAML.load_file(key) 46 | @key = key[:key] || key["key"] 47 | rescue # pass key 48 | @key = key 49 | end 50 | end 51 | 52 | # get, post, put and delete methods 53 | def method_missing(method_id, *args) 54 | if [:get, :post, :put, :delete].include?(method_id) 55 | path, options = *args 56 | options ||= {} 57 | options[:query] = (default_params.merge(options[:query] || {})) 58 | options[:headers] = (self.class.default_options[:headers].merge(options[:headers] || {})) 59 | self.class.send(method_id, url(path), options) 60 | else 61 | super 62 | end 63 | end 64 | 65 | # Returns the base crowdflower domain from the api url's domain. 66 | # 67 | # @api public 68 | # @return [String] the base crowdflower domain 69 | # @example 70 | # CrowdFlower::Connection.new("asdf", "https://api.crowdflower.com").crowdflower_base #=> "crowdflower.com" 71 | def crowdflower_base 72 | uri = URI.parse(domain_base) 73 | "#{uri.host.gsub("api.", "")}" 74 | end 75 | 76 | # Returns the url to reach crowdflower regularly through a browser 77 | # 78 | # @api public 79 | # @return [String] the url to reach crowdflower in a browser 80 | # @example 81 | # CrowdFlower::Connection.new("asdf", "https://api.crowdflower.com").public_url #=> "crowdflower.com:80" 82 | def public_url 83 | "#{crowdflower_base}:#{public_port}" 84 | end 85 | 86 | private 87 | def url(path) 88 | "#{@domain}/#{path}" 89 | end 90 | 91 | def default_params 92 | {'key' => @key} 93 | end 94 | end 95 | 96 | 97 | # Base class for Crowdflower api entities. 98 | # Now instead of one global configuration, each instance could maintain its own connection. 99 | # If no connection is available for an instance it will look for default class-level or global 100 | # connection settings, see Base#connection method. 101 | # You can set class-specific connection with Base.connect! call 102 | # for example: 103 | # Job.connect!(api_key, development, version). 104 | # It is possible to use several api keys simultaneously either by providing each entity instance 105 | # with a specific Connection object or by creating entity subclasses that have their own default connections 106 | # for example: 107 | # CrowdFlower.connect!(default_api_key, development, version) 108 | # (JobSubclass = Class.new(Job)).connect!(custom_api_key, development, version) 109 | # job1 = Job.create('created with default api key') 110 | # job2 = JobSubclass.create('created with custom api key') 111 | class Base 112 | 113 | def initialize(new_connection = nil) 114 | @connection = new_connection 115 | end 116 | 117 | def connection 118 | @connection or self.class.connection 119 | end 120 | 121 | def self.connection 122 | default_connection or (self != Base and superclass.connection) 123 | end 124 | 125 | class << self 126 | attr_accessor :default_connection 127 | end 128 | 129 | def self.connect!(key, development = false, version = 1) 130 | domain_base = development ? "https://api.localdev.crowdflower.com:8443" : "https://api.crowdflower.com" 131 | connect_domain!(key, domain_base, version) 132 | end 133 | 134 | def self.connect_domain!(key, domain_base, version = 1) 135 | self.default_connection = Connection.new(key, domain_base, version) 136 | end 137 | 138 | def self.connect_config!(opts) 139 | extract_option = lambda do |arr| 140 | arr.map { |k| opts[k] || opts[k.to_s] }.compact.first 141 | end 142 | key = extract_option.call([:key, :api_key]) 143 | domain_base = extract_option.call([:domain_base, :domain]) 144 | version = extract_option.call([:version]) || 1 145 | ssl_port = extract_option.call([:ssl_port]) || 443 146 | public_port = extract_option.call([:public_port]) || 80 147 | self.default_connection = Connection.new(key, domain_base, version, ssl_port, public_port) 148 | end 149 | 150 | def self.get(*args); connection.get(*args); end 151 | def self.post(*args); connection.post(*args); end 152 | def self.put(*args); connection.put(*args); end 153 | def self.delete(*args); connection.delete(*args); end 154 | 155 | def connect 156 | unless connection 157 | raise UsageError, "Please establish a connection using 'CrowdFlower.connect!'" 158 | end 159 | end 160 | 161 | def self.connect 162 | unless connection 163 | raise UsageError, "Please establish a connection using 'CrowdFlower.connect!'" 164 | end 165 | end 166 | 167 | def self.verify_response(response) 168 | if response["errors"] 169 | raise CrowdFlower::APIError.new(response["errors"]) 170 | elsif response.response.kind_of? Net::HTTPUnauthorized 171 | raise CrowdFlower::APIError.new('message' => response.to_s) 172 | end 173 | end 174 | 175 | end 176 | 177 | end 178 | --------------------------------------------------------------------------------