houdini 0.2.4 → 0.3.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 (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