houdini 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +3 -1
  2. data/Gemfile.lock +97 -88
  3. data/README.markdown +31 -27
  4. data/app/controllers/houdini/postbacks_controller.rb +7 -9
  5. data/config/routes.rb +2 -2
  6. data/houdini.gemspec +3 -2
  7. data/lib/houdini.rb +35 -7
  8. data/lib/houdini/model.rb +14 -49
  9. data/lib/houdini/postback_processor.rb +21 -0
  10. data/lib/houdini/task.rb +46 -10
  11. data/lib/houdini/task_manager.rb +20 -0
  12. data/lib/houdini/version.rb +1 -1
  13. data/spec/dummy/app/models/article.rb +12 -14
  14. data/spec/houdini/model_spec.rb +27 -0
  15. data/spec/houdini/postback_processor_spec.rb +44 -0
  16. data/spec/houdini/task_manager_spec.rb +26 -0
  17. data/spec/houdini/task_spec.rb +136 -0
  18. data/spec/houdini_spec.rb +25 -0
  19. data/spec/requests/integration_spec.rb +21 -19
  20. data/spec/spec_helper.rb +3 -3
  21. metadata +76 -118
  22. data/lib/houdini/base.rb +0 -35
  23. data/spec/controllers/houdini/postbacks_controller_spec.rb +0 -0
  24. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  25. data/spec/dummy/config/initializers/inflections.rb +0 -10
  26. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  27. data/spec/dummy/public/404.html +0 -26
  28. data/spec/dummy/public/422.html +0 -26
  29. data/spec/dummy/public/500.html +0 -26
  30. data/spec/dummy/public/favicon.ico +0 -0
  31. data/spec/dummy/public/javascripts/application.js +0 -2
  32. data/spec/dummy/public/javascripts/controls.js +0 -965
  33. data/spec/dummy/public/javascripts/dragdrop.js +0 -974
  34. data/spec/dummy/public/javascripts/effects.js +0 -1123
  35. data/spec/dummy/public/javascripts/prototype.js +0 -6001
  36. data/spec/dummy/public/javascripts/rails.js +0 -175
  37. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  38. data/spec/houdini_rails_spec.rb +0 -4
data/.gitignore CHANGED
@@ -4,4 +4,6 @@ log/*.log
4
4
  pkg/
5
5
  spec/dummy/db/*.sqlite3
6
6
  spec/dummy/log/*.log
7
- spec/dummy/tmp/
7
+ spec/dummy/tmp/
8
+ *.swp
9
+ *.swo
data/Gemfile.lock CHANGED
@@ -1,115 +1,123 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- houdini (0.2.4)
5
- rails (~> 3.0.0)
4
+ houdini (0.3.0)
5
+ rails (> 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: http://rubygems.org/
9
9
  specs:
10
- abstract (1.0.0)
11
- actionmailer (3.0.5)
12
- actionpack (= 3.0.5)
13
- mail (~> 2.2.15)
14
- actionpack (3.0.5)
15
- activemodel (= 3.0.5)
16
- activesupport (= 3.0.5)
17
- builder (~> 2.1.2)
18
- erubis (~> 2.6.6)
19
- i18n (~> 0.4)
20
- rack (~> 1.2.1)
21
- rack-mount (~> 0.6.13)
22
- rack-test (~> 0.5.7)
23
- tzinfo (~> 0.3.23)
24
- activemodel (3.0.5)
25
- activesupport (= 3.0.5)
26
- builder (~> 2.1.2)
27
- i18n (~> 0.4)
28
- activerecord (3.0.5)
29
- activemodel (= 3.0.5)
30
- activesupport (= 3.0.5)
31
- arel (~> 2.0.2)
32
- tzinfo (~> 0.3.23)
33
- activeresource (3.0.5)
34
- activemodel (= 3.0.5)
35
- activesupport (= 3.0.5)
36
- activesupport (3.0.5)
37
- arel (2.0.9)
38
- builder (2.1.2)
39
- capybara (0.4.1.2)
40
- celerity (>= 0.7.9)
41
- culerity (>= 0.2.4)
10
+ actionmailer (3.2.1)
11
+ actionpack (= 3.2.1)
12
+ mail (~> 2.4.0)
13
+ actionpack (3.2.1)
14
+ activemodel (= 3.2.1)
15
+ activesupport (= 3.2.1)
16
+ builder (~> 3.0.0)
17
+ erubis (~> 2.7.0)
18
+ journey (~> 1.0.1)
19
+ rack (~> 1.4.0)
20
+ rack-cache (~> 1.1)
21
+ rack-test (~> 0.6.1)
22
+ sprockets (~> 2.1.2)
23
+ activemodel (3.2.1)
24
+ activesupport (= 3.2.1)
25
+ builder (~> 3.0.0)
26
+ activerecord (3.2.1)
27
+ activemodel (= 3.2.1)
28
+ activesupport (= 3.2.1)
29
+ arel (~> 3.0.0)
30
+ tzinfo (~> 0.3.29)
31
+ activeresource (3.2.1)
32
+ activemodel (= 3.2.1)
33
+ activesupport (= 3.2.1)
34
+ activesupport (3.2.1)
35
+ i18n (~> 0.6)
36
+ multi_json (~> 1.0)
37
+ arel (3.0.2)
38
+ builder (3.0.0)
39
+ capybara (1.1.2)
42
40
  mime-types (>= 1.16)
43
41
  nokogiri (>= 1.3.3)
44
42
  rack (>= 1.0.0)
45
43
  rack-test (>= 0.5.4)
46
- selenium-webdriver (>= 0.0.27)
47
- xpath (~> 0.1.3)
48
- celerity (0.8.8)
49
- childprocess (0.1.8)
44
+ selenium-webdriver (~> 2.0)
45
+ xpath (~> 0.1.4)
46
+ childprocess (0.3.0)
50
47
  ffi (~> 1.0.6)
51
- culerity (0.2.15)
52
- diff-lcs (1.1.2)
53
- erubis (2.6.6)
54
- abstract (>= 1.0.0)
55
- ffi (1.0.7)
56
- rake (>= 0.8.7)
57
- i18n (0.5.0)
58
- json_pure (1.5.1)
59
- mail (2.2.15)
60
- activesupport (>= 2.3.6)
48
+ diff-lcs (1.1.3)
49
+ erubis (2.7.0)
50
+ ffi (1.0.11)
51
+ hike (1.2.1)
52
+ i18n (0.6.0)
53
+ journey (1.0.1)
54
+ json (1.6.5)
55
+ mail (2.4.4)
61
56
  i18n (>= 0.4.0)
62
57
  mime-types (~> 1.16)
63
58
  treetop (~> 1.4.8)
64
- mime-types (1.16)
65
- nokogiri (1.4.4)
66
- polyglot (0.3.1)
67
- rack (1.2.2)
68
- rack-mount (0.6.14)
69
- rack (>= 1.0.0)
70
- rack-test (0.5.7)
59
+ mime-types (1.17.2)
60
+ multi_json (1.0.4)
61
+ nokogiri (1.5.0)
62
+ polyglot (0.3.3)
63
+ rack (1.4.1)
64
+ rack-cache (1.1)
65
+ rack (>= 0.4)
66
+ rack-ssl (1.3.2)
67
+ rack
68
+ rack-test (0.6.1)
71
69
  rack (>= 1.0)
72
- rails (3.0.5)
73
- actionmailer (= 3.0.5)
74
- actionpack (= 3.0.5)
75
- activerecord (= 3.0.5)
76
- activeresource (= 3.0.5)
77
- activesupport (= 3.0.5)
70
+ rails (3.2.1)
71
+ actionmailer (= 3.2.1)
72
+ actionpack (= 3.2.1)
73
+ activerecord (= 3.2.1)
74
+ activeresource (= 3.2.1)
75
+ activesupport (= 3.2.1)
78
76
  bundler (~> 1.0)
79
- railties (= 3.0.5)
80
- railties (3.0.5)
81
- actionpack (= 3.0.5)
82
- activesupport (= 3.0.5)
77
+ railties (= 3.2.1)
78
+ railties (3.2.1)
79
+ actionpack (= 3.2.1)
80
+ activesupport (= 3.2.1)
81
+ rack-ssl (~> 1.3.2)
83
82
  rake (>= 0.8.7)
84
- thor (~> 0.14.4)
83
+ rdoc (~> 3.4)
84
+ thor (~> 0.14.6)
85
85
  rake (0.8.7)
86
- rspec (2.5.0)
87
- rspec-core (~> 2.5.0)
88
- rspec-expectations (~> 2.5.0)
89
- rspec-mocks (~> 2.5.0)
90
- rspec-core (2.5.1)
91
- rspec-expectations (2.5.0)
86
+ rdoc (3.12)
87
+ json (~> 1.4)
88
+ rspec (2.8.0)
89
+ rspec-core (~> 2.8.0)
90
+ rspec-expectations (~> 2.8.0)
91
+ rspec-mocks (~> 2.8.0)
92
+ rspec-core (2.8.0)
93
+ rspec-expectations (2.8.0)
92
94
  diff-lcs (~> 1.1.2)
93
- rspec-mocks (2.5.0)
94
- rspec-rails (2.5.0)
95
- actionpack (~> 3.0)
96
- activesupport (~> 3.0)
97
- railties (~> 3.0)
98
- rspec (~> 2.5.0)
99
- rubyzip (0.9.4)
100
- selenium-webdriver (0.1.4)
101
- childprocess (>= 0.1.7)
102
- ffi (>= 1.0.7)
103
- json_pure
95
+ rspec-mocks (2.8.0)
96
+ rspec-rails (2.8.1)
97
+ actionpack (>= 3.0)
98
+ activesupport (>= 3.0)
99
+ railties (>= 3.0)
100
+ rspec (~> 2.8.0)
101
+ rubyzip (0.9.5)
102
+ selenium-webdriver (2.18.0)
103
+ childprocess (>= 0.2.5)
104
+ ffi (~> 1.0.9)
105
+ multi_json (~> 1.0.4)
104
106
  rubyzip
105
- sqlite3 (1.3.3)
107
+ sprockets (2.1.2)
108
+ hike (~> 1.2)
109
+ rack (~> 1.0)
110
+ tilt (~> 1.1, != 1.3.0)
111
+ sqlite3 (1.3.5)
106
112
  sqlite3-ruby (1.3.3)
107
113
  sqlite3 (>= 1.3.3)
108
114
  thor (0.14.6)
109
- treetop (1.4.9)
115
+ tilt (1.3.3)
116
+ treetop (1.4.10)
117
+ polyglot
110
118
  polyglot (>= 0.3.1)
111
- tzinfo (0.3.25)
112
- xpath (0.1.3)
119
+ tzinfo (0.3.33)
120
+ xpath (0.1.4)
113
121
  nokogiri (~> 1.3)
114
122
 
115
123
  PLATFORMS
@@ -118,5 +126,6 @@ PLATFORMS
118
126
  DEPENDENCIES
119
127
  capybara (>= 0.4.1)
120
128
  houdini!
121
- rspec-rails (>= 2.5.0)
129
+ rake (~> 0.8.7)
130
+ rspec-rails (>= 2.8.1)
122
131
  sqlite3-ruby
data/README.markdown CHANGED
@@ -4,56 +4,60 @@ This ruby gem is a Rails Engine for using the Houdini Mechanical Turk API. It pr
4
4
 
5
5
  Check out the [Houdini Documentation](http://houdiniapi.com/documentation) for more info about the API.
6
6
 
7
- # Installation (Rails 3.0.x)
7
+ # Installation (Rails 3.x)
8
8
 
9
9
  Add the gem to your Gemfile
10
10
 
11
11
  gem 'houdini'
12
12
 
13
- Configure a few constants in config/application.rb
13
+ Configure a few constants in config/initializers/houdini.rb
14
14
 
15
- config.after_initialize do
16
- Houdini.setup(:sandbox, :api_key => 'YOUR_API_KEY', :app_host => 'https://your-app-domain.com')
17
- end
15
+ Houdini.setup :sandbox, :api_key => 'YOUR_API_KEY', :app_host => 'https://your-app-domain.com'
18
16
 
19
17
  You may want to configure Houdini differently for each of you environments.
20
18
 
21
19
  # Example Usage
22
20
 
23
- Create a task design at https://admin.houdiniapi.com
21
+ Request a beta account at http://houdiniapi.com to gain access to the Houdini Blueprint Editor.
24
22
 
25
23
  Setup Houdini in your ActiveRecord model:
26
24
 
27
25
  class Post < ActiveRecord::Base
28
26
  include Houdini::Model
29
27
 
30
- houdini :image_moderation, # short_name of task_design
31
- :task_info => {
32
- :image_url => "http://example.com/image12345.jpg"
28
+ houdini :image_moderation,
29
+ :input => {
30
+ :image_url => :image_url, # call the input_url method for
31
+ :image_caption => lambda{ self.caption.titleize }, # use a lambda, called in the model's context
32
+ :image_size => "100x100" # just send this string
33
33
  },
34
- :version => 1,
35
- :after_submit => :update_houdini_attributes,
34
+ :on => :after_create,
36
35
  :on_task_completion => :process_image_moderation_answer
37
36
 
38
- after_create :moderate_image
39
-
40
- def moderate_image
41
- Houdini.perform!(:image_moderation, self)
42
- end
43
-
44
- def update_houdini_attributes
45
- update_attribute(:houdini_request_sent_at, Time.now)
46
- end
47
-
48
37
  def process_image_moderation_answer(params)
49
- update_attribute(:flagged => params[:category] == 'flagged')
38
+ update_attribute :flagged => params[:category] == 'flagged'
50
39
  end
51
40
  end
52
41
 
42
+ # Usage
43
+
44
+ `houdini(blueprint, options)`
45
+
46
+ * `blueprint` - The name of the Houdini blueprint to use. Must be symbol or string.
47
+ * `options` - Hash of options to use.
48
+
49
+ ## Options
50
+ * `:input` - Rqeuired. Hash: any task specific info needed to populate your blueprint. Keys must match the blueprint's required input, and values must a `Symbol` of the method to call, a lambdas/procs to be called in the model's context, or just a value to send along.
51
+ * `:on_task_completion` - Method that should be called when the answer is posted back to your app. Can by a symbol or a lambda/proc. The method will be called with a hash of the returned output from Houdini.
52
+ * `:on` - Name of a callback to use in order to trigger the submission to Houdini. Must be a symbol/string. If you don't want to use a callback, call the model instance's `houdini_submit_#{blueprint}!` method, where `blueprint` is the first argument you provided for the `houdini` method.
53
+ * `:after_submit` - Method that should be called after submitting the task to Houdini. Can by a symbol or a lambda/proc.
54
+ * `:id_method` - Method to get an identifier for the object. It is `id` by default, but you may want to use `to_param` with your app. Can be a symbol or lambda/proc. Use this in conjunction with `:finder`.
55
+ * `:finder` - Method by which to find the model by an identifier. It is `find` by default. Can be a symbol or lambda/proc. Use this in conjunction with `:id_method`.
56
+
57
+ ## Credits
58
+
59
+ * Thanks to Mike Nicholaides (https://github.com/nicholaides) for a huge refactoring to bring the gem up to date with the new version of Houdini.
53
60
 
54
- Post.houdini class method options:
61
+ ## License
55
62
 
56
- * :task_info - Any task specific info needed to populate your template you created when you created the task design.
57
- * :version - Version of the task design to use
58
- * :after_submit - Method that should be called after submitting the task to Houdini.
59
- * :on_task_completion - Method that should be called when the answer is posted back to your app.
63
+ MIT License. Copyright 2012 Houdini Inc. http://houdiniapi.com
@@ -1,12 +1,10 @@
1
1
  class Houdini::PostbacksController < ApplicationController
2
- protect_from_forgery :except => [:create]
2
+ skip_before_filter :protect_from_forgery
3
+
3
4
  def create
4
- object_class = params[:object_class].classify.constantize
5
- object = object_class.find(params[:object_id])
6
- if object.process_postback(params[:task_name], HashWithIndifferentAccess.new(ActiveSupport::JSON.decode(request.raw_post)))
7
- render :json => {:success => true}
8
- else
9
- render :json => {:success => false}, :status => 422
10
- end
5
+ task_results = HashWithIndifferentAccess.new ActiveSupport::JSON.decode(request.raw_post)
6
+
7
+ Houdini::PostbackProcessor.process params[:object_class], params[:object_id], task_results
8
+ render :json => { :success => true }
11
9
  end
12
- end
10
+ end
data/config/routes.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  Rails.application.routes.draw do
2
- scope "houdini/:object_class/:object_id/:task_name" do
2
+ scope "houdini/:object_class/:object_id" do
3
3
  resources :postbacks,
4
4
  :as => 'houdini_postbacks',
5
5
  :controller => 'houdini/postbacks',
6
6
  :only => [:create]
7
7
  end
8
- end
8
+ end
data/houdini.gemspec CHANGED
@@ -12,10 +12,11 @@ Gem::Specification.new do |s|
12
12
  s.description = %q{Rails 3 Engine for using the Houdini Mechanical Turk API}
13
13
  s.homepage = %q{http://github.com/chrisconley/houdini-gem}
14
14
 
15
- s.add_runtime_dependency "rails", "~> 3.0.0"
16
- s.add_development_dependency "rspec-rails", ">= 2.5.0"
15
+ s.add_runtime_dependency "rails", "> 3.0.0"
16
+ s.add_development_dependency "rspec-rails", ">= 2.8.1"
17
17
  s.add_development_dependency "capybara", ">= 0.4.1"
18
18
  s.add_development_dependency "sqlite3-ruby"
19
+ s.add_development_dependency "rake", "~> 0.8.7"
19
20
 
20
21
  s.files = `git ls-files`.split("\n")
21
22
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
data/lib/houdini.rb CHANGED
@@ -1,19 +1,15 @@
1
1
  require 'net/https'
2
2
  require 'uri'
3
3
 
4
- require 'houdini/base'
5
4
  require 'houdini/model'
6
5
  require 'houdini/task'
6
+ require 'houdini/task_manager'
7
+ require 'houdini/postback_processor'
7
8
 
8
9
  require 'houdini/engine'
9
10
 
10
-
11
11
  module Houdini
12
12
  mattr_accessor :environment, :api_key, :app_url, :app_uri, :app_host
13
- # Convenience methods
14
- def self.perform!(task_name, object)
15
- object.send_to_houdini(task_name)
16
- end
17
13
 
18
14
  def self.setup(environment, options)
19
15
  self.environment = environment.to_s
@@ -21,4 +17,36 @@ module Houdini
21
17
  self.app_url = options[:app_host] || options[:app_url]
22
18
  self.app_uri = URI.parse(self.app_url)
23
19
  end
24
- end
20
+
21
+ RequestError = Class.new(NameError)
22
+ HostError = Class.new(NameError)
23
+
24
+ HOST = 'v1.houdiniapi.com'
25
+
26
+ def self.submit!(blueprint, class_name, object_id, input_params)
27
+ unless environment.to_s == 'test'
28
+ request(
29
+ :environment => environment,
30
+ :api_key => api_key,
31
+ :blueprint => blueprint,
32
+ :input => input_params,
33
+ :postback_url => "#{app_uri.scheme}://#{app_uri.host}:#{app_uri.port}/houdini/#{class_name}/#{object_id}/postbacks"
34
+ )
35
+ end
36
+ end
37
+
38
+ def self.request(params)
39
+ # TODO: this should validate sooner
40
+ raise HostError, "Houdini.app_url should specify http:// or https://" unless app_url.match(/^https?\:\/\//)
41
+
42
+ url = File.join("https://", HOST, "tasks.json")
43
+ uri = URI.parse(url)
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = true
46
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
47
+ response, body = http.post(uri.path, params.to_json)
48
+ if response.code != "200"
49
+ raise RequestError, "The request to houdini failed with code #{response.code}: #{body}"
50
+ end
51
+ end
52
+ end
data/lib/houdini/model.rb CHANGED
@@ -1,62 +1,27 @@
1
1
  module Houdini
2
2
  module Model
3
- # def self.included(base)
4
- # base.extend ClassMethods
5
- # base.include Rails.application.routes.url_helpers
6
- # end
7
-
8
3
  extend ActiveSupport::Concern
9
4
 
10
- included do
11
- include Rails.application.routes.url_helpers
12
- extend ClassMethods
13
- end
14
-
15
5
  module ClassMethods
16
- def houdini(name, options)
17
- houdini_tasks[name.to_sym] = Houdini::Task.new(name.to_sym, options)
18
- end
6
+ def houdini(blueprint, options={})
7
+ task_manager = options.delete(:task_manager) || TaskManager
8
+ callback = options.delete(:on)
19
9
 
20
- def houdini_tasks
21
- @houdini_tasks ||= {}
22
- end
10
+ task_manager.register self, blueprint.to_sym, options
23
11
 
24
- end
12
+ submit_method_name = "houdini_submit_#{blueprint}!".to_sym
25
13
 
26
- def send_to_houdini(task_name)
27
- houdini_task = self.class.houdini_tasks[task_name.to_sym]
28
- params = {
29
- :environment => Houdini.environment,
30
- :api_key => Houdini.api_key,
31
- :task_design => houdini_task.short_name,
32
- :task_design_version => houdini_task.version,
33
- :postback_url => houdini_postbacks_url(self.class.name, self.id, houdini_task.short_name, {
34
- :protocol => Houdini.app_uri.scheme,
35
- :host => Houdini.app_uri.host,
36
- :port => Houdini.app_uri.port
37
- })
38
- }
14
+ # Using a module so that you can use override/modify the method via `super`
15
+ m = Module.new do
16
+ define_method submit_method_name do
17
+ task_manager.submit! self, blueprint
18
+ end
19
+ end
20
+ include m
39
21
 
40
- params[:task_info] = houdini_task.task_info.inject({}) do |hash, (info_name, model_attribute)|
41
- hash[info_name] = model_attribute
42
- hash[info_name] = model_attribute.call if model_attribute.respond_to?(:call)
43
- hash[info_name] = self.send(model_attribute) if self.respond_to?(model_attribute)
44
- hash
22
+ # attach the submit method via the callback
23
+ send callback, submit_method_name if callback
45
24
  end
46
-
47
- result = Houdini::Base.request(params)
48
-
49
- call_after_submit(task_name)
50
- end
51
-
52
- def process_postback(task_name, answer)
53
- houdini_task = self.class.houdini_tasks[task_name.to_sym]
54
- self.send(houdini_task.on_task_completion, answer)
55
- end
56
-
57
- def call_after_submit(task_name)
58
- houdini_task = self.class.houdini_tasks[task_name.to_sym]
59
- self.send(houdini_task.after_submit) if houdini_task.after_submit
60
25
  end
61
26
  end
62
27
  end