Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support calling steps with no input arguments (take 2) #69

Merged
merged 2 commits into from
Aug 4, 2017
Merged
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
2 changes: 1 addition & 1 deletion lib/dry/transaction/instance_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(steps: (self.class.steps), listeners: nil, **operations)
subscribe(listeners) unless listeners.nil?
end

def call(input, &block)
def call(input = nil, &block)
assert_step_arity

result = steps.inject(Dry::Monads.Right(input), :bind)
Expand Down
15 changes: 14 additions & 1 deletion lib/dry/transaction/step.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,21 @@ def call(input)
}
end

def call_operation(*input)
if arity >= 1
operation.call(*input)
else
operation.call
end
end

def arity
operation.is_a?(Proc) ? operation.arity : operation.method(:call).arity
case operation
when Proc, Method
operation.arity
else
operation.method(:call).arity
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/dry/transaction/step_adapters/map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Map
include Dry::Monads::Either::Mixin

def call(step, input, *args)
Right(step.operation.call(input, *args))
Right(step.call_operation(input, *args))
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/dry/transaction/step_adapters/raw.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Raw
include Dry::Monads::Either::Mixin

def call(step, input, *args)
result = step.operation.call(input, *args)
result = step.call_operation(input, *args)

unless result.is_a?(Dry::Monads::Either)
raise ArgumentError, "step +#{step.step_name}+ must return an Either object"
Expand Down
2 changes: 1 addition & 1 deletion lib/dry/transaction/step_adapters/tee.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Tee
include Dry::Monads::Either::Mixin

def call(step, input, *args)
step.operation.call(input, *args)
step.call_operation(input, *args)
Right(input)
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/dry/transaction/step_adapters/try.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ def call(step, input, *args)
raise ArgumentError, "+try+ steps require one or more exception classes provided via +catch:+"
end

Right(step.operation.call(input, *args))
result = step.call_operation(input, *args)
Right(result)
rescue *Array(step.options[:catch]) => e
e = step.options[:raise].new(e.message) if step.options[:raise]
Left(e)
Expand Down
101 changes: 101 additions & 0 deletions spec/integration/transaction_without_steps_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
RSpec.describe "Transactions steps without arguments" do
let(:dependencies) { {} }

before do
Test::NotValidError = Class.new(StandardError)
Test::DB = [{"name" => "Jane", "email" => "[email protected]"}]
Test::Http = Class.new do
def self.get
"pong"
end

def self.post(value)
Test::DB << value
end
end
class Test::Container
extend Dry::Container::Mixin
register :fetch_data, -> { Test::DB.delete_at(0) }, call: false
register :call_outside, -> { Test::Http.get }, call: false
register :external_store, -> input { Test::Http.post(input) }
register :process, -> input { { name: input["name"], email: input["email"] } }
register :validate, -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input }
register :persist, -> input { Test::DB << input and true }
end
end

context "successful" do
let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::Container)
map :fetch_data
map :process
try :validate, catch: Test::NotValidError
tee :persist
end.new(**dependencies)
}

it "calls the operations" do
transaction.call
expect(Test::DB).to include(name: "Jane", email: "[email protected]")
end

it "returns a success" do
expect(transaction.call()).to be_a Dry::Monads::Either::Right
end

it "wraps the result of the final operation" do
expect(transaction.call().value).to eq(name: "Jane", email: "[email protected]")
end

it "supports matching on success" do
results = []

transaction.call() do |m|
m.success do |value|
results << "success for #{value[:email]}"
end

m.failure { }
end

expect(results.first).to eq "success for [email protected]"
end
end

context "using multiple tee step operators" do
let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::Container)
tee :call_outside
map :fetch_data
map :process
try :validate, catch: Test::NotValidError
tee :external_store
end.new(**dependencies)
}

it "calls the operations" do
transaction.call
expect(Test::DB).to include(name: "Jane", email: "[email protected]")
end
end

context "not needing arguments in the middle of the transaction" do
let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::Container)
map :process
try :validate, catch: Test::NotValidError
tee :call_outside
tee :external_store
end.new(**dependencies)
}
let(:input) { {"name" => "Jane", "email" => "[email protected]"} }

it "calls the operations" do
transaction.call(input)
expect(Test::DB).to include(name: "Jane", email: "[email protected]")
end
end
end