diff --git a/Gemfile b/Gemfile index 2c49f59..ee7095f 100644 --- a/Gemfile +++ b/Gemfile @@ -6,6 +6,7 @@ group :test do gem "simplecov" gem "codeclimate-test-reporter" gem "byebug", platform: :mri + gem "dry-container" end group :tools do diff --git a/lib/dry/transaction/operation_resolver.rb b/lib/dry/transaction/operation_resolver.rb index 074df70..0589b93 100644 --- a/lib/dry/transaction/operation_resolver.rb +++ b/lib/dry/transaction/operation_resolver.rb @@ -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 diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index d0c00fa..9ca66ac 100644 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -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" => "jane@doe.com"} } it "calls the operations" do @@ -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 @@ -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" => "jane@doe.com") + + expect(Test::DB).to include([:name, :email]) + end + end + + context "all steps are local methods" do let(:transaction) do Class.new do @@ -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 @@ -252,9 +283,24 @@ def persist(input) let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} } 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 @@ -284,8 +330,22 @@ def persist(input) context "non-confirming raw step result" do let(:input) { {"name" => "Jane", "email" => "jane@doe.com"} } + 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