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.
- data/.gitignore +3 -1
- data/Gemfile.lock +97 -88
- data/README.markdown +31 -27
- data/app/controllers/houdini/postbacks_controller.rb +7 -9
- data/config/routes.rb +2 -2
- data/houdini.gemspec +3 -2
- data/lib/houdini.rb +35 -7
- data/lib/houdini/model.rb +14 -49
- data/lib/houdini/postback_processor.rb +21 -0
- data/lib/houdini/task.rb +46 -10
- data/lib/houdini/task_manager.rb +20 -0
- data/lib/houdini/version.rb +1 -1
- data/spec/dummy/app/models/article.rb +12 -14
- data/spec/houdini/model_spec.rb +27 -0
- data/spec/houdini/postback_processor_spec.rb +44 -0
- data/spec/houdini/task_manager_spec.rb +26 -0
- data/spec/houdini/task_spec.rb +136 -0
- data/spec/houdini_spec.rb +25 -0
- data/spec/requests/integration_spec.rb +21 -19
- data/spec/spec_helper.rb +3 -3
- metadata +76 -118
- data/lib/houdini/base.rb +0 -35
- data/spec/controllers/houdini/postbacks_controller_spec.rb +0 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/spec/dummy/config/initializers/inflections.rb +0 -10
- data/spec/dummy/config/initializers/mime_types.rb +0 -5
- data/spec/dummy/public/404.html +0 -26
- data/spec/dummy/public/422.html +0 -26
- data/spec/dummy/public/500.html +0 -26
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +0 -2
- data/spec/dummy/public/javascripts/controls.js +0 -965
- data/spec/dummy/public/javascripts/dragdrop.js +0 -974
- data/spec/dummy/public/javascripts/effects.js +0 -1123
- data/spec/dummy/public/javascripts/prototype.js +0 -6001
- data/spec/dummy/public/javascripts/rails.js +0 -175
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/houdini_rails_spec.rb +0 -4
@@ -0,0 +1,21 @@
|
|
1
|
+
module Houdini
|
2
|
+
module PostbackProcessor
|
3
|
+
EnvironmentMismatchError = Class.new RuntimeError
|
4
|
+
APIKeyMistmatchError = Class.new RuntimeError
|
5
|
+
|
6
|
+
def self.process(class_name, model_id, params)
|
7
|
+
task_manager = params.delete(:task_manager) || TaskManager
|
8
|
+
|
9
|
+
if params[:environment] != Houdini.environment
|
10
|
+
raise EnvironmentMismatchError, "Environment received does not match Houdini.environment"
|
11
|
+
end
|
12
|
+
|
13
|
+
# # Houdini doesn't send the API key back
|
14
|
+
# if params[:api_key] != Houdini.api_key and params[:environment] == "production"
|
15
|
+
# raise APIKeyMistmatchError, "API key received doesn't match our API key."
|
16
|
+
# end
|
17
|
+
|
18
|
+
task_manager.process class_name, model_id, params[:blueprint], params[:output], params[:verbose_output]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/houdini/task.rb
CHANGED
@@ -1,15 +1,51 @@
|
|
1
1
|
module Houdini
|
2
2
|
class Task
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
11
|
-
@
|
12
|
-
@
|
3
|
+
attr :options
|
4
|
+
protected :options
|
5
|
+
|
6
|
+
def initialize(klass, blueprint, options={})
|
7
|
+
@class_name = klass.name
|
8
|
+
@blueprint = blueprint.to_s
|
9
|
+
@input = options[:input]
|
10
|
+
@after_submit = options[:after_submit]
|
11
|
+
@on_task_completion = options[:on_task_completion]
|
12
|
+
@finder = options[:finder] || :find
|
13
|
+
@id_method = options[:id_method] || :id
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(object_id, results, verbose_results)
|
17
|
+
# we have to re-constantize the class name because Rails reloads the classes in development
|
18
|
+
obj = self.class.callable @class_name.constantize, @finder, object_id
|
19
|
+
self.class.callable obj, @on_task_completion, results, verbose_results if @on_task_completion
|
20
|
+
end
|
21
|
+
|
22
|
+
def submit!(object, options={})
|
23
|
+
get_input = options[:input_transformer] || self.class.method(:get_input)
|
24
|
+
submit_task = options[:submit_task] || Houdini.method(:submit!)
|
25
|
+
|
26
|
+
id = self.class.callable object, @id_method
|
27
|
+
|
28
|
+
submit_task.call @blueprint, object.class.name, id, get_input.call(object, @input)
|
29
|
+
|
30
|
+
self.class.callable(object, @after_submit) if @after_submit
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.get_input(object, input)
|
34
|
+
input.inject({}) do |hash, (info_name, model_attribute)|
|
35
|
+
hash.merge info_name => callable(object, model_attribute){ model_attribute }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.callable(object, callable, *args)
|
40
|
+
if callable.is_a? Symbol
|
41
|
+
object.send callable, *args
|
42
|
+
elsif callable.respond_to? :call
|
43
|
+
object.instance_exec *args, &callable
|
44
|
+
elsif block_given?
|
45
|
+
yield callable, *args
|
46
|
+
else
|
47
|
+
raise "#{callable.inspect} is not a Symbol and does not respond to #call"
|
48
|
+
end
|
13
49
|
end
|
14
50
|
end
|
15
51
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Houdini
|
2
|
+
module TaskManager
|
3
|
+
def self.register(klass, blueprint, options, task_builder=Task)
|
4
|
+
@tasks ||= {}
|
5
|
+
@tasks[ [klass.name, blueprint.to_sym] ] = task_builder.new(klass, blueprint, options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.submit!(object, blueprint)
|
9
|
+
if @tasks
|
10
|
+
task = @tasks[ [object.class.name, blueprint.to_sym] ]
|
11
|
+
task.submit! object
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.process(class_name, id, blueprint, output, verbose_output)
|
16
|
+
task = @tasks[ [class_name, blueprint.to_sym] ]
|
17
|
+
task.process id, output, verbose_output
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/houdini/version.rb
CHANGED
@@ -2,24 +2,22 @@ class Article < ActiveRecord::Base
|
|
2
2
|
include Houdini::Model
|
3
3
|
|
4
4
|
houdini :edit_for_grammar,
|
5
|
-
:
|
6
|
-
|
7
|
-
'
|
5
|
+
:input => {
|
6
|
+
'input1' => :original_text,
|
7
|
+
'input2' => proc{ original_text },
|
8
|
+
'input3' => "some text"
|
8
9
|
},
|
9
|
-
:
|
10
|
-
:
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
def moderate_image
|
15
|
-
Houdini.perform!(:edit_for_grammar, self)
|
16
|
-
end
|
10
|
+
:on => :after_create,
|
11
|
+
:after_submit => :update_houdini_attributes,
|
12
|
+
:on_task_completion => :process_houdini_edited_text,
|
13
|
+
:finder => lambda{|id| last },
|
14
|
+
:id_method => lambda{ 'model-slug' }
|
17
15
|
|
18
16
|
def update_houdini_attributes
|
19
|
-
update_attribute(:houdini_request_sent_at,
|
17
|
+
update_attribute(:houdini_request_sent_at, Date.today.to_time)
|
20
18
|
end
|
21
19
|
|
22
|
-
def process_houdini_edited_text(
|
23
|
-
update_attribute(:edited_text,
|
20
|
+
def process_houdini_edited_text(output, verbose_output)
|
21
|
+
update_attribute(:edited_text, output[:edited_text])
|
24
22
|
end
|
25
23
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Houdini::Model do
|
4
|
+
let :test_model do
|
5
|
+
Class.new do
|
6
|
+
include Houdini::Model
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ".houdini" do
|
11
|
+
it "should define a method that submits the task" do
|
12
|
+
task_manager = double :task_manager
|
13
|
+
task_manager.should_receive(:register).with(test_model, :blueprint_name, :other_options => {})
|
14
|
+
test_model.houdini :blueprint_name, :task_manager => task_manager, :other_options => {}
|
15
|
+
|
16
|
+
test_model_instance = test_model.new
|
17
|
+
|
18
|
+
task_manager.should_receive(:submit!).with(test_model_instance, :blueprint_name)
|
19
|
+
test_model_instance.houdini_submit_blueprint_name!
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should attach callback methods" do
|
23
|
+
test_model.should_receive(:after_create).with(:houdini_submit_blueprint_name!)
|
24
|
+
test_model.houdini :blueprint_name, :on => :after_create
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Houdini::PostbackProcessor, ".process!" do
|
4
|
+
let(:houdini_settings) do
|
5
|
+
{ :environment => "production", :api_key => "MY_KEY", :app_url => "http://localhost:3000" }
|
6
|
+
end
|
7
|
+
context "when the environments dont match" do
|
8
|
+
before do
|
9
|
+
Houdini.setup "production", houdini_settings
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should raise an EnvironmentMismatchError" do
|
13
|
+
lambda{
|
14
|
+
Houdini::PostbackProcessor.process 'Class', 42, :environment => "sandbox"
|
15
|
+
}.should raise_error(Houdini::PostbackProcessor::EnvironmentMismatchError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when in production and given API key doesn't match" do
|
20
|
+
it "should raise an APIKeyMistmatchError" do
|
21
|
+
pending "Houdini doesn't send the API key back."
|
22
|
+
lambda{
|
23
|
+
Houdini::PostbackProcessor.process 'Class', 42, :environment => "production", :api_key => "ANOTHER_KEY"
|
24
|
+
}.should raise_error(Houdini::PostbackProcessor::APIKeyMistmatchError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "given a classname and id" do
|
29
|
+
it "should load the object, and have it process the postback, and return the result" do
|
30
|
+
task_manager = double :task_manager
|
31
|
+
output = double :output
|
32
|
+
verbose_output = double :verbose_output
|
33
|
+
|
34
|
+
task_manager.should_receive(:process).with('MyClass', 42, 'blueprint_name', output, verbose_output)
|
35
|
+
|
36
|
+
Houdini::PostbackProcessor.process 'MyClass', 42, houdini_settings.merge(
|
37
|
+
:blueprint => 'blueprint_name',
|
38
|
+
:output => output,
|
39
|
+
:verbose_output => verbose_output,
|
40
|
+
:task_manager => task_manager
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Houdini::TaskManager do
|
4
|
+
describe ".register, .submit!, and .process" do
|
5
|
+
class MyClass; end
|
6
|
+
|
7
|
+
it "should save it and let us call it again, when we give the right blueprint and class" do
|
8
|
+
options = double :options
|
9
|
+
task_builder = double :task_builder
|
10
|
+
task = double :task
|
11
|
+
obj = MyClass.new
|
12
|
+
houdini_output = double :houdini_output
|
13
|
+
verbose_houdini_output = double :verbose_houdini_output
|
14
|
+
processing_result = double :processing_result
|
15
|
+
|
16
|
+
task_builder.should_receive(:new).with(MyClass, :blueprint_name, options).and_return(task)
|
17
|
+
Houdini::TaskManager.register(MyClass, :blueprint_name, options, task_builder)
|
18
|
+
|
19
|
+
task.should_receive(:submit!).with(obj)
|
20
|
+
Houdini::TaskManager.submit!(obj, :blueprint_name)
|
21
|
+
|
22
|
+
task.should_receive(:process).with(42, houdini_output, verbose_houdini_output).and_return(processing_result)
|
23
|
+
Houdini::TaskManager.process('MyClass', 42, 'blueprint_name', houdini_output, verbose_houdini_output).should == processing_result
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Houdini::Task do
|
4
|
+
describe "submit!(obj)" do
|
5
|
+
class TestModel
|
6
|
+
def id; 42; end
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:model_object){ TestModel.new }
|
10
|
+
let(:input){ double :input }
|
11
|
+
it "should use the input transformer and submit it to houdini" do
|
12
|
+
task = Houdini::Task.new TestModel, :blueprint
|
13
|
+
submit_task = double(:callable)
|
14
|
+
submit_task.should_receive(:call).with('blueprint', "TestModel", 42, input)
|
15
|
+
task.submit! model_object, :input_transformer => proc{ input }, :submit_task => submit_task
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should call the after_submit callback, when given a symbol" do
|
19
|
+
task = Houdini::Task.new TestModel, :blueprint, :after_submit => :after_submit
|
20
|
+
model_object.should_receive :after_submit
|
21
|
+
task.submit! model_object, :input_transformer => proc{}, :submit_task => proc{}
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should call the after_submit callback, when given a block" do
|
25
|
+
task = Houdini::Task.new TestModel, :blueprint, :after_submit => proc{ after_submit }
|
26
|
+
model_object.should_receive :after_submit
|
27
|
+
task.submit! model_object, :input_transformer => proc{}, :submit_task => proc{}
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should call the :id you provided to get the id" do
|
31
|
+
task = Houdini::Task.new TestModel, :blueprint, :id_method => lambda{ 'model-id' }
|
32
|
+
submit_task = double(:callable)
|
33
|
+
submit_task.should_receive(:call).with('blueprint', "TestModel", 'model-id', nil)
|
34
|
+
task.submit! model_object, :input_transformer => proc{}, :submit_task => submit_task
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe ".callable" do
|
39
|
+
let(:obj){ double :obj, :some_method => result }
|
40
|
+
subject{ Houdini::Task.callable(obj, callable, *args) }
|
41
|
+
let(:result){ double :result }
|
42
|
+
let(:args){ [double(:arg1), double(:arg2)] }
|
43
|
+
|
44
|
+
context "given a symbol" do
|
45
|
+
let(:callable){ :some_method }
|
46
|
+
before{ obj.should_receive(:some_method).with(*args) }
|
47
|
+
it{ should == result }
|
48
|
+
end
|
49
|
+
|
50
|
+
context "given a proc" do
|
51
|
+
let(:callable){ lambda{|*args| some_method(*args) } }
|
52
|
+
before{ obj.should_receive(:some_method).with(*args) }
|
53
|
+
it{ should == result }
|
54
|
+
end
|
55
|
+
|
56
|
+
context "given something else" do
|
57
|
+
let(:callable){ 42 }
|
58
|
+
it "should raise an error" do
|
59
|
+
lambda{
|
60
|
+
Houdini::Task.callable(obj, callable)
|
61
|
+
}.should raise_error
|
62
|
+
end
|
63
|
+
|
64
|
+
context "and also given a block" do
|
65
|
+
it "should call the block with the callable and return the result" do
|
66
|
+
Houdini::Task.callable(double, 42){|c| c * 2 }.should == 84
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe ".get_input" do
|
73
|
+
subject{ Houdini::Task.get_input(input_object, input_hash) }
|
74
|
+
let(:input_object){ double :obj, :some_method => "some input", :some_method2 => "some more input" }
|
75
|
+
|
76
|
+
context "given an object and an instance hash" do
|
77
|
+
let(:input_hash){ { :input1 => :some_method, :input2 => :some_method2 } }
|
78
|
+
it "should build a hash based on the one given, and the values in the object" do
|
79
|
+
should == { :input1 => "some input", :input2 => "some more input" }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
context "given symbols" do
|
83
|
+
let(:input_hash){ { :input1=>:some_method } }
|
84
|
+
it "should get call the corresponding methods" do
|
85
|
+
should == { :input1 => "some input" }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
context "given lambdas" do
|
89
|
+
let(:input_hash){ { :input1=>lambda{some_method} } }
|
90
|
+
it "should use evaluate procs in the context of the given object" do
|
91
|
+
should == { :input1 => "some input" }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
context "given procs" do
|
95
|
+
let(:input_hash){ { :input1=>proc{some_method} } }
|
96
|
+
it "should use evaluate procs in the context of the given object" do
|
97
|
+
should == { :input1 => "some input" }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
context "given other values" do
|
101
|
+
let(:input_hash){ { :input1=>"some input", :input2=>42 } }
|
102
|
+
it "should return the value, if it's not callable" do
|
103
|
+
should == input_hash
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "#process" do
|
109
|
+
class TestClass
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should call the callback on the object, giving it the houdini output, and verbose output, and return the result" do
|
113
|
+
obj = double :obj
|
114
|
+
houdini_output = double :houdini_output
|
115
|
+
verbose_houdini_output = double :verbose_houdini_output
|
116
|
+
process_result = double :process_result
|
117
|
+
|
118
|
+
task = Houdini::Task.new TestClass, :blueprint_name, :on_task_completion => :after_houdini_completion, :finder => :custom_finder
|
119
|
+
|
120
|
+
TestClass.should_receive(:custom_finder).with(42).and_return(obj)
|
121
|
+
obj.should_receive(:after_houdini_completion).with(houdini_output, verbose_houdini_output).and_return(process_result)
|
122
|
+
|
123
|
+
task.process 42, houdini_output, verbose_houdini_output
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should reload the class from it's name, so that this can work in Rails dev mode" do
|
127
|
+
klass = double :klass, :name => 'TestClass'
|
128
|
+
|
129
|
+
task = Houdini::Task.new klass, :blueprint_name
|
130
|
+
|
131
|
+
TestClass.should_receive(:find)
|
132
|
+
|
133
|
+
task.process 42, double, double
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Houdini, ".submit!" do
|
4
|
+
it "should call .request with the right paramers" do
|
5
|
+
Houdini.setup "some_environment", :api_key =>"SOME-API-KEY", :app_url => "http://localhost:3000"
|
6
|
+
|
7
|
+
expected_params = {
|
8
|
+
:environment => "some_environment",
|
9
|
+
:api_key => "SOME-API-KEY",
|
10
|
+
:blueprint => :blueprint_name,
|
11
|
+
:postback_url => "http://localhost:3000/houdini/ClassName/42/postbacks",
|
12
|
+
:input => { :input1 => 1, :input2 => 'two' }
|
13
|
+
}
|
14
|
+
|
15
|
+
Houdini.should_receive(:request).with(expected_params)
|
16
|
+
|
17
|
+
Houdini.submit! :blueprint_name, "ClassName", 42, :input1 => 1, :input2 => 'two'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not send requests if environment is 'test'" do
|
21
|
+
Houdini.setup "test", :api_key => "SOME-API-KEY", :app_url => "http://localhost:3000"
|
22
|
+
Houdini.should_receive(:request).never
|
23
|
+
Houdini.submit! :blueprint_name, "ClassName", 42, :input1 => 1, :input2 => 'two'
|
24
|
+
end
|
25
|
+
end
|
@@ -1,35 +1,37 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
describe "
|
3
|
+
describe "End-to-end" do
|
4
4
|
before do
|
5
|
+
Houdini.setup 'production', :app_url => "http://my-app:3333/"
|
5
6
|
Article.delete_all
|
6
|
-
Houdini::Base.stub!(:request).and_return(["200", "{response:success}"]) # TODO: VCR
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should send task to Houdini and properly receive the postback" do
|
10
|
-
|
11
|
-
p.stub!(:id).and_return(1)
|
10
|
+
article = Article.new :original_text => 'This is incorect.'
|
12
11
|
|
13
12
|
params = {
|
14
|
-
"api_key"
|
15
|
-
"environment"
|
16
|
-
"postback_url" => "http://
|
17
|
-
"
|
18
|
-
"
|
19
|
-
|
20
|
-
"
|
13
|
+
"api_key" => Houdini.api_key,
|
14
|
+
"environment" => Houdini.environment,
|
15
|
+
"postback_url" => "http://my-app:3333/houdini/Article/model-slug/postbacks",
|
16
|
+
"blueprint" => "edit_for_grammar",
|
17
|
+
"input" => {
|
18
|
+
"input1" => "This is incorect.",
|
19
|
+
"input2" => "This is incorect.",
|
20
|
+
"input3" => "some text"
|
21
21
|
}
|
22
22
|
}.symbolize_keys
|
23
23
|
|
24
|
-
Houdini
|
24
|
+
Houdini.should_receive(:request).with(params)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
article.save!
|
27
|
+
article.reload
|
28
|
+
article.houdini_request_sent_at.should == Date.today.to_time
|
29
29
|
|
30
|
-
|
30
|
+
output_params = {"edited_text"=>"This is incorrect."}
|
31
31
|
|
32
|
-
|
33
|
-
|
32
|
+
post "houdini/Article/model-slug/postbacks", params.merge("id" => "000000000000", "status"=>"complete", "output" => output_params, "verbose_output"=> output_params).to_json
|
33
|
+
|
34
|
+
article.reload
|
35
|
+
article.edited_text.should == "This is incorrect."
|
34
36
|
end
|
35
|
-
end
|
37
|
+
end
|