houdini 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|