Skip to content
This repository was archived by the owner on Oct 19, 2018. It is now read-only.

Commit eef30bf

Browse files
committed
wip everything done except client side specs for execution_spec
1 parent 202045f commit eef30bf

File tree

6 files changed

+214
-38
lines changed

6 files changed

+214
-38
lines changed

lib/hyper-operation/api.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ def params
2323
@params
2424
end
2525

26-
def abort!(*args)
27-
Railway.abort!(args)
26+
def abort!(arg = nil)
27+
Railway.abort!(arg)
2828
end
2929

30-
def succeed!(*args)
31-
Railway.succeed!(args)
30+
def succeed!(arg = nil)
31+
Railway.succeed!(arg)
3232
end
3333

3434
def initialize

lib/hyper-operation/railway/params_wrapper.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class << self
2323

2424
def combine_arg_array(args)
2525
hash = args.inject({}.with_indifferent_access) do |h, arg|
26-
raise ArgumentError.new("All arguments must be hashes") unless arg.is_a?(Hash)
27-
h.merge!(arg)
26+
raise ArgumentError.new("All arguments must be hashes") unless arg.respond_to? :to_h
27+
h.merge!(arg.to_h)
2828
end
2929
end
3030

lib/hyper-operation/railway/run.rb

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
module Hyperloop
22
class Operation
3-
class Railway
43

5-
class Exit < StandardError
6-
attr_reader :state
7-
attr_reader :result
8-
def initialize(state, result)
9-
@state = state
10-
@result = result
11-
end
4+
class Exit < StandardError
5+
attr_reader :state
6+
attr_reader :result
7+
def initialize(state, result)
8+
@state = state
9+
@result = result
1210
end
11+
end
12+
13+
class Railway
1314

1415
def tracks
1516
self.class.tracks
@@ -20,74 +21,97 @@ def tracks
2021
@tracks ||= []
2122
end
2223

23-
def to_opts(args)
24-
args.first || {}
24+
def to_opts(tie, args, block)
25+
if args.count.zero?
26+
{ run: block }
27+
elsif args[0].is_a?(Hash)
28+
{
29+
scope: args[0][:class] ? :class : args[0][:scope],
30+
run: args[0][:class] || args[0][:run] || block
31+
}
32+
elsif args[0] == :class && block
33+
{ run: block, scope: :class }
34+
elsif args[0].is_a?(Class) && args[0] < Operation
35+
{ run: proc { args[0].run(params) } }
36+
else
37+
scope = args[1][:scope] if args[1].is_a? Hash
38+
{ run: args[0], scope: scope }
39+
end.merge(tie: instance_method(tie))
2540
end
2641

27-
[:step, :failed, :async].each do |meth|
28-
define_method :"add_#{meth}" do |*args, &block|
29-
tracks << [instance_method(meth), to_opts(args), block]
42+
[:step, :failed, :async].each do |tie|
43+
define_method :"add_#{tie}" do |*args, &block|
44+
tracks << to_opts(tie, args, block)
3045
end
3146
end
3247

33-
def abort!(args)
34-
raise Exit.new(:failed, args)
48+
def abort!(arg)
49+
raise Exit.new(:failed, arg)
3550
end
3651

37-
def succeed!(args)
38-
raise Exit.new(:success, args)
52+
def succeed!(arg)
53+
raise Exit.new(:success, arg)
3954
end
4055
end
4156

42-
def step(opts, block)
57+
def step(opts)
4358
if @last_result.is_a? Promise
4459
@last_result = @last_result.then do |*result|
4560
@last_result = result
46-
apply(opts, block)
61+
apply(opts, :in_promise)
4762
end
4863
elsif @state == :success
49-
apply(opts, block)
64+
apply(opts)
5065
end
5166
end
5267

53-
def failed(opts, block)
68+
def failed(opts)
5469
if @last_result.is_a? Promise
55-
@last_result = @last_result.fail do |*result|
56-
@last_result = result
57-
apply(opts, block)
70+
@last_result = @last_result.fail do |e|
71+
@last_result = e
72+
apply(opts, :in_promise)
73+
raise @last_result if @last_result.is_a? Exception
74+
raise e
5875
end
5976
elsif @state == :failed
60-
apply(opts, block)
77+
apply(opts)
6178
end
6279
end
6380

64-
def async(opts, block)
65-
apply(opts, block) if @state != :failed
81+
def async(opts)
82+
apply(opts) if @state != :failed
6683
end
6784

68-
def apply(opts, block)
85+
def apply(opts, in_promise = nil)
6986
if opts[:scope] == :class
7087
args = [@operation, *@last_result]
7188
instance = @operation.class
7289
else
7390
args = @last_result
7491
instance = @operation
7592
end
93+
block = opts[:run]
94+
block = instance.method(block) if block.is_a? Symbol
7695
@last_result =
7796
if block.arity.zero?
78-
instance.instance_eval(&block)
97+
instance.instance_exec(&block)
7998
elsif args.is_a?(Array) && block.arity == args.count
8099
instance.instance_exec(*args, &block)
81100
else
82101
instance.instance_exec(args, &block)
83102
end
103+
return @last_result unless @last_result.is_a? Promise
104+
raise @last_result.error if @last_result.rejected?
105+
@last_result = @last_result.value if @last_result.resolved?
106+
@last_result
84107
rescue Exit => e
85108
@state = e.state
86-
@last_result = e.result
109+
@last_result = (e.state != :failed || e.result.is_a?(Exception)) ? e.result : e
87110
raise e
88111
rescue Exception => e
89112
@state = :failed
90113
@last_result = e
114+
raise e if in_promise
91115
end
92116

93117
def run
@@ -98,7 +122,7 @@ def run
98122
else
99123
@state = :success
100124
end
101-
tracks.each { |tie, opts, block| tie.bind(self).call(opts, block) }
125+
tracks.each { |opts| opts[:tie].bind(self).call(opts) }
102126
rescue Exit
103127
end
104128

lib/hyper-operation/railway/validations.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def process_validations
4545
add_validation_error(i, "param validation #{i+1} aborted")
4646
end
4747
@state = :failed
48-
@last_result = e.result unless e.result.empty?
4948
return # break does not work in Opal
5049
rescue AccessViolation => e
5150
add_validation_error(i, e)

spec/hyper-operation/execution_spec.rb

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,157 @@ def self.promise
5656
end
5757
expect(MyOperation(i: 1).tap { MyOperation.promise.resolve(2) }.value).to eq 2
5858
end
59+
60+
it "will switch to the failure track on an error" do
61+
MyOperation.class_eval do
62+
def self.promise
63+
@promise ||= Promise.new
64+
end
65+
param :i
66+
step { self.class.promise }
67+
step { |n| params.i + n }
68+
failed { raise 'i am a' }
69+
step { MyOperation.dont_call_me }
70+
failed { |s| raise "#{s} failure" }
71+
end
72+
expect(MyOperation).not_to receive(:dont_call_me)
73+
expect(MyOperation(i: 1).tap { MyOperation.promise.resolve('x') }.error.to_s).to eq 'i am a failure'
74+
end
75+
76+
it "will begin on the failure track if there are validation errors" do
77+
MyOperation.class_eval do
78+
def self.promise
79+
@promise ||= Promise.new
80+
end
81+
param :i
82+
step { MyOperation.dont_call_me }
83+
step { |n| params.i + n }
84+
failed { |s| raise "#{s}! Looks like i am still a" }
85+
step { MyOperation.dont_call_me }
86+
failed { |s| "#{s} failure!" }
87+
end
88+
expect(MyOperation).not_to receive(:dont_call_me)
89+
expect(MyOperation().tap { MyOperation.promise.resolve('x') }.error.to_s).to eq 'I is required! Looks like i am still a failure!'
90+
end
91+
92+
it "succeed! will skip to the end" do
93+
MyOperation.class_eval do
94+
step { succeed! "I succeeded at last!"}
95+
step { MyOperation.dont_call_me }
96+
failed { MyOperation.dont_call_me}
97+
98+
end
99+
expect(MyOperation).not_to receive(:dont_call_me)
100+
expect(MyOperation().value).to eq 'I succeeded at last!'
101+
end
102+
103+
it "succeed! will skip to the end and succeed even on the failure track" do
104+
MyOperation.class_eval do
105+
step { fail }
106+
failed { succeed! "I still can succeed!"}
107+
step { MyOperation.dont_call_me }
108+
failed { MyOperation.dont_call_me}
109+
end
110+
expect(MyOperation).not_to receive(:dont_call_me)
111+
expect(MyOperation().value).to eq 'I still can succeed!'
112+
expect(MyOperation()).to be_resolved
113+
end
114+
115+
it "abort! will skip to the end with a failure" do
116+
MyOperation.class_eval do
117+
step { abort! "Pride cometh before the fall!"}
118+
step { MyOperation.dont_call_me }
119+
failed { MyOperation.dont_call_me}
120+
end
121+
expect(MyOperation).not_to receive(:dont_call_me)
122+
expect(MyOperation().error.result).to eq 'Pride cometh before the fall!'
123+
end
124+
125+
it "if abort! is given an exception it will return that exception" do
126+
MyOperation.class_eval do
127+
step { abort! Exception.new("okay okay okay")}
128+
step { MyOperation.dont_call_me }
129+
failed { MyOperation.dont_call_me}
130+
end
131+
expect(MyOperation).not_to receive(:dont_call_me)
132+
expect(MyOperation().error.to_s).to eq 'okay okay okay'
133+
end
134+
135+
it "can chain an exception after returning" do
136+
MyOperation.class_eval do
137+
step { abort! Exception.new("okay okay okay")}
138+
step { MyOperation.dont_call_me }
139+
failed { MyOperation.dont_call_me}
140+
end
141+
expect(MyOperation).not_to receive(:dont_call_me)
142+
expect(MyOperation().fail { |e| raise "pow" }.error.to_s).to eq 'pow'
143+
end
144+
145+
it "can define the step, async and failed callbacks many ways" do
146+
stub_const 'SayHello', Class.new(Hyperloop::Operation)
147+
SayHello.class_eval do
148+
param :xxx
149+
step { MyOperation.say_hello if params.xxx == 123 }
150+
end
151+
MyOperation.class_eval do
152+
param :xxx
153+
def say_hello()
154+
MyOperation.say_hello
155+
end
156+
step { say_hello }
157+
step :say_hello
158+
step -> () { say_hello }
159+
step proc { say_hello }
160+
step SayHello # your params will be passed along to SayHello
161+
async { say_hello }
162+
async :say_hello
163+
async -> () { say_hello }
164+
async Proc.new { say_hello }
165+
async SayHello # your params will be passed along to SayHello
166+
step { fail }
167+
failed { say_hello }
168+
failed :say_hello
169+
failed -> () { say_hello }
170+
failed Proc.new { say_hello }
171+
failed SayHello # your params will be passed along to SayHello
172+
failed { succeed! }
173+
end
174+
expect(MyOperation).to receive(:say_hello).exactly(15).times
175+
expect(MyOperation(xxx: 123)).to be_resolved
176+
end
177+
178+
it "can define class level callbacks" do
179+
MyOperation.class_eval do
180+
step(:class) { say_hello }
181+
step class: :say_hello
182+
step class: -> () { say_hello }
183+
step class: proc { say_hello }
184+
async(:class) { say_hello }
185+
async class: :say_hello
186+
async class: -> () { say_hello }
187+
async class: Proc.new { say_hello }
188+
step { fail }
189+
failed(:class) { say_hello }
190+
failed class: :say_hello
191+
failed class: -> () { say_hello }
192+
failed class: Proc.new { say_hello }
193+
failed { succeed! }
194+
end
195+
expect(MyOperation).to receive(:say_hello).exactly(12).times
196+
expect(MyOperation.run).to be_resolved
197+
end
198+
199+
it 'can provide options different ways' do
200+
MyOperation.class_eval do
201+
def say_instance_hello()
202+
MyOperation.say_hello
203+
end
204+
step(scope: :class) { say_hello }
205+
step scope: :class, run: proc { say_hello }
206+
step run: :say_instance_hello
207+
step :say_hello, scope: :class
208+
end
209+
expect(MyOperation).to receive(:say_hello).exactly(4).times
210+
expect(MyOperation.run).to be_resolved
211+
end
59212
end

spec/test_app/development_db_name

0 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)