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

Check if an operation is registered in the container before getting it (Issue #61) #64

Merged
merged 3 commits into from
Jul 10, 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
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ group :test do
gem "simplecov"
gem "codeclimate-test-reporter"
gem "byebug", platform: :mri
gem "dry-container"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't required as it's already part of the gemspec.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out @jcmfernandes, but I'm thinking we don't actually need it in the gemspec, since dry-container is a "soft" dependency. I'll probably be removing the gemspec dependency before the next release, so I'm happy for this to stay here :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point @timriley, indeed, it makes more sense to have it here. As you stated, dry-container isn't a runtime dependency.

end

group :tools do
Expand Down
4 changes: 3 additions & 1 deletion lib/dry/transaction/operation_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ def initialize(container)
module_exec(container) do |ops_container|
define_method :initialize do |**kwargs|
operation_kwargs = self.class.steps.select(&:operation_name).map { |step|
operation = kwargs.fetch(step.step_name) { ops_container and ops_container[step.operation_name] }
operation = kwargs.fetch(step.step_name) {
ops_container and ops_container.key?(step.operation_name) and ops_container[step.operation_name]
}

[step.step_name, operation]
}.to_h
Expand Down
108 changes: 84 additions & 24 deletions spec/integration/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
RSpec.describe "Transactions" do
let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::Container)
map :process
step :verify
try :validate, catch: Test::NotValidError
tee :persist
end.new(**dependencies)
}

let(:dependencies) { {} }

before do
Test::NotValidError = Class.new(StandardError)
Test::DB = []
Test::Container = {
process: -> input { {name: input["name"], email: input["email"]} },
verify: -> input { Right(input) },
validate: -> input { input[:email].nil? ? raise(Test::NotValidError, "email required") : input },
persist: -> input { Test::DB << input and true },
}
class Test::Container
extend Dry::Container::Mixin
register :process, -> input { {name: input["name"], email: input["email"]} }
register :verify, -> input { Dry::Monads::Right(input) }
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 :process
step :verify
try :validate, catch: Test::NotValidError
tee :persist
end.new(**dependencies)
}
let(:input) { {"name" => "Jane", "email" => "[email protected]"} }

it "calls the operations" do
Expand Down Expand Up @@ -63,12 +63,11 @@

context "different step names" do
before do
module Test
ContainerNames = {
process_step: -> input { {name: input["name"], email: input["email"]} },
verify_step: -> input { Dry::Monads.Right(input) },
persist_step: -> input { Test::DB << input and true },
}
class Test::ContainerNames
extend Dry::Container::Mixin
register :process_step, -> input { {name: input["name"], email: input["email"]} }
register :verify_step, -> input { Dry::Monads::Right(input) }
register :persist_step, -> input { Test::DB << input and true }
end
end

Expand Down Expand Up @@ -156,6 +155,29 @@ def verify(input)
end
end

context "local step definition not in container" do
let(:transaction) do
Class.new do
include Dry::Transaction(container: Test::Container)

map :process, with: :process
step :verify_only_local
tee :persist, with: :persist

def verify_only_local(input)
Right(input.keys)
end
end.new
end

it "execute step only defined as local method" do
transaction.call("name" => "Jane", "email" => "[email protected]")

expect(Test::DB).to include([:name, :email])
end
end


context "all steps are local methods" do
let(:transaction) do
Class.new do
Expand Down Expand Up @@ -186,6 +208,15 @@ def persist(input)
end

context "failed in a try step" do
let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::Container)
map :process
step :verify
try :validate, catch: Test::NotValidError
tee :persist
end.new(**dependencies)
}
let(:input) { {"name" => "Jane"} }

it "does not run subsequent operations" do
Expand Down Expand Up @@ -252,9 +283,24 @@ def persist(input)
let(:input) { {"name" => "Jane", "email" => "[email protected]"} }

before do
Test::Container[:verify] = -> input { Left("raw failure") }
class Test::ContainerRaw
extend Dry::Container::Mixin
register :process_step, -> input { {name: input["name"], email: input["email"]} }
register :verify_step, -> input { Dry::Monads::Left("raw failure") }
register :persist_step, -> input { Test::DB << input and true }
end
end

let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::ContainerRaw)

map :process, with: :process_step
step :verify, with: :verify_step
tee :persist, with: :persist_step
end.new(**dependencies)
}

it "does not run subsequent operations" do
transaction.call(input)
expect(Test::DB).to be_empty
Expand Down Expand Up @@ -284,8 +330,22 @@ def persist(input)
context "non-confirming raw step result" do
let(:input) { {"name" => "Jane", "email" => "[email protected]"} }

let(:transaction) {
Class.new do
include Dry::Transaction(container: Test::ContainerRaw)
map :process
step :verify
tee :persist
end.new(**dependencies)
}

before do
Test::Container[:verify] = -> input { "failure" }
class Test::ContainerRaw
extend Dry::Container::Mixin
register :process, -> input { {name: input["name"], email: input["email"]} }
register :verify, -> input { "failure" }
register :persist, -> input { Test::DB << input and true }
end
end

it "raises an exception" do
Expand Down