Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions lib/crack_pipe/action/exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def call(action, context, track = :default)
def action(action, context, track = :default)
action.class.steps.each_with_object([]) do |s, results|
next unless track == s.track

results!(results, action, s, context).last.tap do |r|
action.after_flow_control(r)
context = r[:context]
Expand Down Expand Up @@ -53,12 +54,10 @@ def halt(output, success = nil)
def step(action, step, context)
kwargs = kwargs_with_context(action, context)

return :skipped unless should_exec?(step.exec_if, action, context, kwargs)

output = catch(:signal) do
if (e = step.exec).is_a?(Symbol)
action.public_send(e, context, **kwargs)
else
e.call(context, **kwargs)
end
exec_with_args(step.exec, action, context, kwargs)
end

action.after_step(output)
Expand All @@ -70,14 +69,34 @@ def success_with_step?(action, step, output)

private

def callable?(e)
e.is_a?(Symbol) || e.respond_to?(:call)
end

def should_exec?(exec_if, action, context, kwargs)
return exec_with_args(exec_if, action, context.dup, kwargs) if callable?(exec_if)

exec_if
end

def exec_with_args(e, action, context, kwargs)
if e.is_a?(Symbol)
action.public_send(e, context, **kwargs)
else
e.call(context, **kwargs)
end
end

def kwargs_with_context(action, context)
return context if action.kwargs_overrides.empty?

context.merge(action.kwargs_overrides)
end

def results!(results, action, step, context)
o = step(action, step, context)
return results.concat(o.history) if o.is_a?(Result)

results << flow_control_hash(action, step, context, o)
end
end
Expand Down
7 changes: 5 additions & 2 deletions lib/crack_pipe/action/step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
module CrackPipe
class Action
class Step
attr_reader :exec, :track
attr_reader :exec, :exec_if, :track

def initialize(exec = nil, always_pass: false, track: :default, **, &blk)
def initialize(exec = nil, always_pass: false, track: :default, **opts, &blk)
if block_given?
raise ArgumentError, '`exec` must be `nil` with a block' unless
exec.nil?

exec = blk
end

@always_pass = always_pass
@exec = instantiate_action(exec)
@exec_if = opts.key?(:if) ? opts[:if] : true
@track = track
end

Expand All @@ -28,6 +30,7 @@ def always_pass?
# `step SomeAction.new` when nesting actions.
def instantiate_action(obj)
return obj.new if obj.is_a?(Class) && obj < Action

obj
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/crack_pipe/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

module CrackPipe
MAJOR = 0
MINOR = 2
TINY = 4
MINOR = 3
TINY = 0
VERSION = [MAJOR, MINOR, TINY].join('.').freeze

def self.version
Expand Down
91 changes: 81 additions & 10 deletions spec/action_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,85 @@ def after(ctx, value:, **)
value.to_s.upcase
end

def after_fail(ctx, **)
def after_fail(_ctx, **)
:custom_error_code_02
end
end
end

let(:action_with_skips) do
klass = skipped_action

Class.new(Action) do
step :before
step klass, if: ->(ctx, **) { ctx[:run_all_steps] }
step :always_run, if: true
step :never_run, if: false
step :conditional_method, if: :run_all_steps?

def before(ctx, **)
ctx[:before] = true
end

def conditional_method(ctx, **)
ctx[:conditional_method] = "exec'd"
end

def always_run(ctx, **)
ctx[:always_run] = true
end

def never_run(ctx, **)
ctx[:never_run] = true
end

def run_all_steps?(_, run_all_steps:, **)
run_all_steps
end
end
end

let(:skipped_action) do
Class.new(Action) do
step :maybe_hit_1
step :maybe_hit_2

def maybe_hit_1(ctx, **)
ctx[:maybe_hit_1] = true
end

def maybe_hit_2(ctx, **)
ctx[:maybe_hit_2] = true
end
end
end

it 'conditionally skips steps' do
r = action_with_skips.call(run_all_steps: false)
r.history.size.must_equal(5)
assert r.success?
assert r[:before]
assert r[:always_run]
refute r.context.key?(:never_run)
refute r.context.key?(:maybe_hit_1)
refute r.context.key?(:maybe_hit_2)
refute r.context.key?(:conditional_method)
r.output.must_equal(:skipped)
r.history.select { |h| h[:output] == :skipped }.size.must_equal(3)

r = action_with_skips.call(run_all_steps: true)
r.history.size.must_equal(6)
assert r.success?
assert r[:always_run]
assert r[:maybe_hit_1]
assert r[:maybe_hit_2]
refute r.context.key?(:never_run)
r[:conditional_method].must_equal("exec'd")
r.output.must_equal("exec'd")
end

it 'results in a success with a truthy value' do
r = action.(value: 'x')
r = action.call(value: 'x')
r.history.size.must_equal(3)
r.history.select { |h| h[:next] == :default }.size.must_equal(3)

Expand All @@ -69,7 +140,7 @@ def after_fail(ctx, **)
end

it 'results in a failure and uses the fail track with a falsy value' do
r = action.(value: false)
r = action.call(value: false)
r.history.size.must_equal(4)
r.history.select { |h| h[:next] == :fail }.size.must_equal(3)

Expand All @@ -80,35 +151,35 @@ def after_fail(ctx, **)
end

it 'short circuits execution with `pass!`' do
r = action.new.(value: :short_circuit)
r = action.new.call(value: :short_circuit)
r.history.size.must_equal(2)

assert r.success?
r.output.must_equal(:short_circuit)
end

it 'short circuits execution with `fail!`' do
r = action.(value: :short_circuit!)
r = action.call(value: :short_circuit!)
assert r.failure?
r.output.must_equal(:short_circuit!)
end

it 'nests one action in another' do
r = nesting_action.(value: 'x')
r = nesting_action.call(value: 'x')
r.history.size.must_equal(5)

r.output.must_equal('X')
r[:after].must_equal(true)
r[:before].must_equal(true)
r[:value_class].must_equal('String')

r = nesting_action.(value: false)
r = nesting_action.call(value: false)
r.history.size.must_equal(6)

r.output.must_equal(:custom_error_code_02)
r[:before].must_equal(true)

r = nesting_action.(value: :short_circuit!)
r = nesting_action.call(value: :short_circuit!)
assert r.failure?
r.output.must_equal(:short_circuit!)
end
Expand Down Expand Up @@ -145,7 +216,7 @@ def after_step(output)
end
end

r = a.({})
r = a.call({})
r.history[0][:output].must_equal(1)
r.history[1][:output].must_equal('two')
end
Expand All @@ -170,7 +241,7 @@ def after_flow_control(flow_control_hash)
end
end

r = a.({})
r = a.call({})
r.history[0][:context][:one].must_equal('one')
r.output.must_equal('one!')
end
Expand Down