diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6911b03..0b535d7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,14 +2,14 @@ name: Release on: push: - branches: [ master ] + branches: [master] jobs: test: runs-on: ubuntu-latest strategy: matrix: - ruby-version: [ '3.0', '3.2', 'jruby' ] + ruby-version: ['3.1', '3.2', '3.4', 'jruby-9.4.12.1'] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/specs.yml b/.github/workflows/specs.yml index b75569d..2a04dce 100644 --- a/.github/workflows/specs.yml +++ b/.github/workflows/specs.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - ruby-version: ['3.0', '3.2', 'jruby'] + ruby-version: ['3.1', '3.2', '3.4', 'jruby-9.4.12.1'] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af7d2a..d3e077b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Opera Changelog +### 0.4.0 - May 22, 2025 + +- Stop handling exceptions +- Stop supporting ruby 3.0 + ### 0.3.5 - February 24, 2025 - Simplify instrumentation configuration diff --git a/Gemfile b/Gemfile index d3deee8..31a42b6 100644 --- a/Gemfile +++ b/Gemfile @@ -3,11 +3,11 @@ source 'https://rubygems.org' # Specify your gem's dependencies in opera.gemspec gemspec -gem 'rake', '~> 12.0' -gem 'rspec', '~> 3.0' +gem 'rake', '~> 13.2' +gem 'rspec', '~> 3.13' group :test, :development do gem 'dry-validation' - gem 'pry-byebug', require: false, platform: :ruby - gem 'pry-nav', require: false, platform: :jruby + gem 'pry' + gem 'pry-byebug', require: false, platforms: :ruby end diff --git a/Gemfile.lock b/Gemfile.lock index 206d20b..9b55fb7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,70 +1,73 @@ PATH remote: . specs: - opera (0.3.5) + opera (0.4.0) GEM remote: https://rubygems.org/ specs: - bigdecimal (3.1.8) - byebug (11.1.3) + bigdecimal (3.1.9) + byebug (12.0.0) coderay (1.1.3) - concurrent-ruby (1.3.4) - diff-lcs (1.5.1) - dry-configurable (1.2.0) - dry-core (~> 1.0, < 2) + concurrent-ruby (1.3.5) + diff-lcs (1.6.2) + dry-configurable (1.3.0) + dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-core (1.0.1) + dry-core (1.1.0) concurrent-ruby (~> 1.0) + logger zeitwerk (~> 2.6) - dry-inflector (1.1.0) - dry-initializer (3.1.1) - dry-logic (1.5.0) + dry-inflector (1.2.0) + dry-initializer (3.2.0) + dry-logic (1.6.0) + bigdecimal concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) + dry-core (~> 1.1) zeitwerk (~> 2.6) - dry-schema (1.13.4) + dry-schema (1.14.1) concurrent-ruby (~> 1.0) dry-configurable (~> 1.0, >= 1.0.1) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-logic (>= 1.4, < 2) - dry-types (>= 1.7, < 2) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-logic (~> 1.5) + dry-types (~> 1.8) zeitwerk (~> 2.6) - dry-types (1.7.2) + dry-types (1.8.2) bigdecimal (~> 3.0) concurrent-ruby (~> 1.0) dry-core (~> 1.0) dry-inflector (~> 1.0) dry-logic (~> 1.4) zeitwerk (~> 2.6) - dry-validation (1.10.0) + dry-validation (1.11.1) concurrent-ruby (~> 1.0) - dry-core (~> 1.0, < 2) - dry-initializer (~> 3.0) - dry-schema (>= 1.12, < 2) + dry-core (~> 1.1) + dry-initializer (~> 3.2) + dry-schema (~> 1.14) zeitwerk (~> 2.6) + logger (1.7.0) method_source (1.1.0) - pry (0.14.2) + pry (0.15.2) coderay (~> 1.1) method_source (~> 1.0) - pry-byebug (3.10.1) - byebug (~> 11.0) - pry (>= 0.13, < 0.15) - rake (12.3.3) + pry-byebug (3.11.0) + byebug (~> 12.0) + pry (>= 0.13, < 0.16) + rake (13.2.1) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) - rspec-core (3.13.1) + rspec-core (3.13.3) rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) + rspec-expectations (3.13.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.1) + rspec-mocks (3.13.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-support (3.13.1) + rspec-support (3.13.3) zeitwerk (2.6.18) PLATFORMS @@ -73,10 +76,10 @@ PLATFORMS DEPENDENCIES dry-validation opera! + pry pry-byebug - pry-nav - rake (~> 12.0) - rspec (~> 3.0) + rake (~> 13.2) + rspec (~> 3.13) BUNDLED WITH - 2.2.32 + 2.3.3 diff --git a/README.md b/README.md index bf0718b..76130fe 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Note. If you are using Ruby 2.x please use Opera 0.2.x ## Configuration Opera is built to be used with or without Rails. -Simply initialise the configuration and chose what method you want to use to report errors and what library you want to use to implement transactions +Simply initialize the configuration and choose a custom logger and which library to use for implementing transactions. ```ruby Opera::Operation::Config.configure do |config| @@ -136,28 +136,6 @@ class A < Opera::Operation::Base end ``` -### Debugging - -When you want to easily debug exceptions you can add this -to your dummy.rb: - -``` -Rails.application.configure do - config.x.reporter = Logger.new(STDERR) -end -``` - -This should display exceptions captured inside operations. - -You can also do it in Opera::Operation configuration block: - -``` -Opera::Operation::Config.configure do |config| - config.transaction_class = ActiveRecord::Base - config.reporter = Logger.new(STDERR) -end -``` - ### Content [Basic operation](#user-content-basic-operation) @@ -165,8 +143,6 @@ end [Example operation with old validations](#user-content-example-operation-with-old-validations) -[Example with step that raises exception](#user-content-example-with-step-that-raises-exception) - [Failing transaction](#user-content-failing-transaction) [Passing transaction](#user-content-passing-transaction) @@ -195,7 +171,7 @@ class Profile::Create < Opera::Operation::Base attr_accessor :profile end # DEPRECATED - # dependencies_reader :current_account, :mailer + # dependencies_reader :current_account, :mailer dependencies do attr_reader :current_account, :mailer end @@ -237,7 +213,7 @@ Profile::Create.call(params: { current_account: Account.find(1) }) -##}> +##}> ``` #### Call with INVALID parameters - missing first_name @@ -250,7 +226,7 @@ Profile::Create.call(params: { current_account: Account.find(1) }) -#["is missing"]}, @exceptions={}, @information={}, @executions=[:profile_schema]> +#["is missing"]}, @information={}, @executions=[:profile_schema]> ``` #### Call with MISSING dependencies @@ -263,7 +239,7 @@ Profile::Create.call(params: { current_account: Account.find(1) }) -##}> +##}> ``` ### Example with sanitizing parameters @@ -322,7 +298,7 @@ Profile::Create.call(params: { }) # NOTE: Last name is missing in output model -##}> +##}> ``` ### Example operation with old validations @@ -393,7 +369,7 @@ Profile::Create.call(params: { current_account: Account.find(1) }) -##}> +##}> ``` #### Call with INVALID parameters @@ -406,74 +382,7 @@ Profile::Create.call(params: { current_account: Account.find(1) }) -#["can't be blank"]}, @exceptions={}, @information={:missing_validations=>"Please check dry validations"}, @executions=[:build_record, :old_validation]> -``` - -### Example with step that raises exception - -```ruby -class Profile::Create < Opera::Operation::Base - # DEPRECATED - # context_accessor :profile - context do - attr_accessor :profile - end - # DEPRECATED - # dependencies_reader :current_account, :mailer - dependencies do - attr_reader :current_account, :mailer - end - - validate :profile_schema - - step :build_record - step :exception - step :create - step :send_email - step :output - - def profile_schema - Dry::Validation.Schema do - required(:first_name).filled - end.call(params) - end - - def build_record - self.profile = current_account.profiles.build(params) - self.profile.force_name_validation = true - end - - def exception - raise StandardError, 'Example' - end - - def create - self.profile = profile.save - end - - def send_email - return true unless mailer - - mailer.send_mail(profile: profile) - end - - def output - result.output(model: profile) - end -end -``` - -##### Call with step throwing exception - -```ruby -result = Profile::Create.call(params: { - first_name: :foo, - last_name: :bar -}, dependencies: { - current_account: Account.find(1) -}) - -#["Example"]}, @information={}, @executions=[:profile_schema, :build_record, :exception]> +#["can't be blank"]}, @information={:missing_validations=>"Please check dry validations"}, @executions=[:build_record, :old_validation]> ``` ### Example with step that finishes execution @@ -536,7 +445,7 @@ result = Profile::Create.call(params: { current_account: Account.find(1) }) -# +# ``` ### Failing transaction @@ -609,7 +518,7 @@ D, [2020-08-14T16:13:30.946466 #2504] DEBUG -- : Account Load (0.5ms) SELECT D, [2020-08-14T16:13:30.960254 #2504] DEBUG -- : (0.2ms) BEGIN D, [2020-08-14T16:13:30.983981 #2504] DEBUG -- : SQL (0.7ms) INSERT INTO "profiles" ("first_name", "last_name", "created_at", "updated_at", "account_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["first_name", "foo"], ["last_name", "bar"], ["created_at", "2020-08-14 16:13:30.982289"], ["updated_at", "2020-08-14 16:13:30.982289"], ["account_id", 1]] D, [2020-08-14T16:13:30.986233 #2504] DEBUG -- : (0.2ms) ROLLBACK -#["unknown attribute 'example_attr' for Profile."], "Profile::Create#transaction"=>["Opera::Operation::Base::RollbackTransactionError"]}, @information={}, @executions=[:profile_schema, :create, :update]> +D, [2020-08-14T16:13:30.988231 #2504] DEBUG -- : unknown attribute 'example_attr' for Profile. (ActiveModel::UnknownAttributeError) ``` ### Passing transaction @@ -682,7 +591,7 @@ D, [2020-08-17T12:10:44.856964 #2741] DEBUG -- : (0.2ms) BEGIN D, [2020-08-17T12:10:44.881332 #2741] DEBUG -- : SQL (0.7ms) INSERT INTO "profiles" ("first_name", "last_name", "created_at", "updated_at", "account_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["first_name", "foo"], ["last_name", "bar"], ["created_at", "2020-08-17 12:10:44.879684"], ["updated_at", "2020-08-17 12:10:44.879684"], ["account_id", 1]] D, [2020-08-17T12:10:44.886168 #2741] DEBUG -- : SQL (0.6ms) UPDATE "profiles" SET "updated_at" = $1 WHERE "profiles"."id" = $2 [["updated_at", "2020-08-16 12:10:44.883164"], ["id", 47]] D, [2020-08-17T12:10:44.898132 #2741] DEBUG -- : (10.3ms) COMMIT -##}> +##}> ``` ### Benchmark @@ -747,7 +656,7 @@ Profile::Create.call(params: { }, dependencies: { current_account: Account.find(1) }) -#0.300013706088066e-05, :total=>0.0}, slow_section: {:real=>1.800013706088066e-05, :total=>0.0}}, @executions=[:profile_schema, :create, :update, :send_email, :output], @output={:model=>#}> +#0.300013706088066e-05, :total=>0.0}, slow_section: {:real=>1.800013706088066e-05, :total=>0.0}}, @executions=[:profile_schema, :create, :update, :send_email, :output], @output={:model=>#}> ``` ### Success @@ -817,7 +726,7 @@ Profile::Create.call(params: { }, dependencies: { current_account: Account.find(1) }) -#["Missing dependency"]}, @exceptions={}, @information={}, @executions=[:profile_schema, :populate, :create, :update, :send_email, :output], @output={:model=>#}> +#["Missing dependency"]}, @information={}, @executions=[:profile_schema, :populate, :create, :update, :send_email, :output], @output={:model=>#}> ``` ### Finish If @@ -886,7 +795,7 @@ Profile::Create.call(params: { create_only: true, current_account: Account.find(1) }) -# +# ``` ### Inner Operation @@ -938,7 +847,7 @@ Profile::Create.call(params: { }, dependencies: { current_account: Account.find(1) }) -#{:id=>1, :user_id=>1, :linkedin_uid=>nil, ...}}> +#{:id=>1, :user_id=>1, :linkedin_uid=>nil, ...}}> ``` ### Inner Operations @@ -976,7 +885,7 @@ end ```ruby Profile::CreateMultiple.call(params: { number: 3 }) -#[[:validate, :create], [:validate, :create], [:validate, :create], [:validate, :create]]}, :output], @output=[{:model=>"Profile 1"}, {:model=>"Profile 7"}, {:model=>"Profile 69"}, {:model=>"Profile 92"}]> +#[[:validate, :create], [:validate, :create], [:validate, :create], [:validate, :create]]}, :output], @output=[{:model=>"Profile 1"}, {:model=>"Profile 7"}, {:model=>"Profile 69"}, {:model=>"Profile 92"}]> ``` ## Opera::Operation::Result - Instance Methods @@ -989,15 +898,13 @@ Opera::Operation::Result.new(output: 'success') ``` > - - success? - [true, false] - Return true if no errors and no exceptions - - failure? - [true, false] - Return true if any error or exception + - success? - [true, false] - Return true if no errors + - failure? - [true, false] - Return true if any error - output - [Anything] - Return Anything - output=(Anything) - Sets content of operation output - output! - Return Anything if Success, raise exception if Failure - add_error(key, value) - Adds new error message - add_errors(Hash) - Adds multiple error messages - - add_exception(method, message, classname: nil) - Adds new exception - - add_exceptions(Hash) - Adds multiple exceptions - add_information(Hash) - Adss new information - Useful informations for developers ## Opera::Operation::Base - Instance Methods @@ -1135,16 +1042,15 @@ end - step(Symbol) - single instruction - return [Truthly] - continue operation execution - return [False] - stops operation execution - - raise Exception - exception gets captured and stops operation execution - operation(Symbol) - single instruction - requires to return Opera::Operation::Result object - - return [Opera::Operation::Result] - stops operation STEPS execution if any error, exception + - return [Opera::Operation::Result] - stops operation STEPS execution if failure - validate(Symbol) - single dry-validations - requires to return Dry::Validation::Result object - return [Dry::Validation::Result] - stops operation STEPS execution if any error but continue with other validations - transaction(*Symbols) - list of instructions to be wrapped in transaction - return [Truthly] - continue operation execution - - return [False|Exception] - stops operation execution and breaks transaction/do rollback + - return [False] - stops operation execution and breaks transaction/do rollback - call(params: Hash, dependencies: Hash?) - - return [Opera::Operation::Result] - never raises an exception + - return [Opera::Operation::Result] ## Development diff --git a/lib/opera/operation/instructions/executors/operation.rb b/lib/opera/operation/instructions/executors/operation.rb index 64ffd36..203eeba 100644 --- a/lib/opera/operation/instructions/executors/operation.rb +++ b/lib/opera/operation/instructions/executors/operation.rb @@ -14,7 +14,6 @@ def call(instruction) add_instruction_output(instruction, operation_result.output) else result.add_errors(operation_result.errors) - result.add_exceptions(operation_result.exceptions) end execution = result.executions.pop diff --git a/lib/opera/operation/instructions/executors/operations.rb b/lib/opera/operation/instructions/executors/operations.rb index 8a5b856..22423d0 100644 --- a/lib/opera/operation/instructions/executors/operations.rb +++ b/lib/opera/operation/instructions/executors/operations.rb @@ -12,8 +12,6 @@ def call(instruction) instruction[:kind] = :step operations_results = super - return if result.exceptions.any? - case operations_results when Array operations_results.each do |operation_result| @@ -38,7 +36,6 @@ def call(instruction) def add_failures(failures) failures.each do |failure| result.add_errors(failure.errors) - result.add_exceptions(failure.exceptions) end end diff --git a/lib/opera/operation/instructions/executors/step.rb b/lib/opera/operation/instructions/executors/step.rb index 0e1ea00..dbf770b 100644 --- a/lib/opera/operation/instructions/executors/step.rb +++ b/lib/opera/operation/instructions/executors/step.rb @@ -12,10 +12,6 @@ def call(instruction) operation.result.add_execution(method) unless production_mode? operation.send(method) end - rescue StandardError => exception - reporter&.error(exception) - operation.result.add_exception(method, "#{exception.message}, for #{operation.inspect}", classname: operation.class.name) - operation.result end end end diff --git a/lib/opera/operation/instructions/executors/validate.rb b/lib/opera/operation/instructions/executors/validate.rb index 9097bb4..7f9f2b8 100644 --- a/lib/opera/operation/instructions/executors/validate.rb +++ b/lib/opera/operation/instructions/executors/validate.rb @@ -13,23 +13,20 @@ def break_condition def evaluate_instruction(instruction) instruction[:kind] = :step - dry_result = super + validation_result = super - case dry_result + case validation_result when Opera::Operation::Result - add_instruction_output(instruction, dry_result.output) + add_instruction_output(instruction, validation_result.output) - unless dry_result.success? - result.add_errors(dry_result.errors) - result.add_exceptions(dry_result.exceptions) - end + result.add_errors(validation_result.errors) unless validation_result.success? when Dry::Validation::Result - add_instruction_output(instruction, dry_result.to_h) + add_instruction_output(instruction, validation_result.to_h) - result.add_errors(dry_result.errors) unless dry_result.success? + result.add_errors(validation_result.errors) unless validation_result.success? else - exception_message = "#{dry_result.class} is not expected object. Please check: #{dry_result.inspect}" - result.add_exception(instruction[:method], exception_message, classname: operation.class.name) + raise TypeError, "#{validation_result.class} is not a valid result for 'validate' step. " \ + "Please check output of '#{instruction[:method]}' step in #{operation.class.name}" end end end diff --git a/lib/opera/operation/result.rb b/lib/opera/operation/result.rb index 67125a3..9d6b5c9 100644 --- a/lib/opera/operation/result.rb +++ b/lib/opera/operation/result.rb @@ -3,10 +3,16 @@ module Opera module Operation class Result - class OutputError < StandardError; end + class OutputError < StandardError + attr_reader :errors + + def initialize(msg, errors = {}) + @errors = errors + super(msg) + end + end attr_reader :errors, # Acumulator of errors in validation + steps - :exceptions, # Acumulator of exceptions in steps :information, # Temporal object to store related information :executions # Stacktrace or Pipe of the methods evaludated @@ -14,14 +20,13 @@ class OutputError < StandardError; end def initialize(output: nil, errors: {}) @errors = errors - @exceptions = {} @information = {} @executions = [] @output = output end def failure? - errors.any? || exceptions.any? + errors.any? end def success? @@ -29,11 +34,11 @@ def success? end def failures - errors.merge(exceptions) + errors end def output! - raise OutputError, 'Cannot retrieve output from a Failure.' if failure? + raise OutputError.new('Cannot retrieve output from a Failure.', errors) if failure? output end @@ -60,18 +65,6 @@ def add_errors(errors) end end - def add_exception(method, message, classname: nil) - key = [classname, Array(method).first].compact.join('#') - - @exceptions[key] = message unless @exceptions.key?(key) - end - - def add_exceptions(exceptions) - exceptions.each_pair do |key, value| - add_exception(key, value) - end - end - def add_information(hash) @information.merge!(hash) end diff --git a/lib/opera/version.rb b/lib/opera/version.rb index 31d8663..3f9300d 100644 --- a/lib/opera/version.rb +++ b/lib/opera/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Opera - VERSION = '0.3.5' + VERSION = '0.4.0' end diff --git a/opera.gemspec b/opera.gemspec index 85e516c..b90f185 100644 --- a/opera.gemspec +++ b/opera.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |spec| spec.summary = 'Use simple DSL language to keep your Operations clean and maintainable' spec.homepage = 'https://github.com/Profinda/opera' spec.license = 'MIT' - spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0') + spec.required_ruby_version = Gem::Requirement.new('>= 3.1.0') spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage diff --git a/spec/opera/operation/base_spec.rb b/spec/opera/operation/base_spec.rb index ab65df0..bf68f8f 100644 --- a/spec/opera/operation/base_spec.rb +++ b/spec/opera/operation/base_spec.rb @@ -204,7 +204,7 @@ def step_2 end end - it { expect(subject.exceptions['step_1']).to include(/undefined method.*foo=/) } + it { expect { subject }.to raise_error(NoMethodError) } end context 'when defaulting to params reader' do @@ -405,12 +405,11 @@ def step_1 subject { operation_class.call(params: params, dependencies: dependencies) } context 'for validations' do - context 'when validation method returns unexpected object' do + context 'when validation step raises an exception' do let(:operation_class) do Class.new(Operation::Base) do validate do step :validation_1 - step :validation_2 end step :step_1 @@ -418,7 +417,24 @@ def validation_1 raise 'exception message' end - def validation_2 + def step_1; end + end + end + + it 'raises exception' do + expect { subject }.to raise_error(RuntimeError, 'exception message') + end + end + + context 'when validation method returns unexpected object' do + let(:operation_class) do + Class.new(Operation::Base) do + validate do + step :validation_1 + end + step :step_1 + + def validation_1 'unexpected string' end @@ -426,10 +442,8 @@ def step_1; end end end - it 'handles exception' do - expect(subject).to be_failure - expect(subject.exceptions["validation_1"]).to include(/exception message/) - expect(subject.exceptions["validation_2"]).to include(/String is not expected object/) + it 'raises exception' do + expect { subject }.to raise_error(TypeError, /String is not a valid result for 'validate' step/) end end @@ -581,31 +595,6 @@ def step_2 ) end end - - context 'for exceptioning step' do - let(:operation_class) do - Class.new(Operation::Base) do - step :step_1 - step :step_2 - - def step_1 - raise(StandardError, 'Example') - end - - def step_2 - true - end - end - end - - it 'calls step_1 only' do - expect_any_instance_of(operation_class).to receive(:step_1).and_call_original - expect_any_instance_of(operation_class).to_not receive(:step_2) - expect(subject.executions).to match_array(%i[step_1]) - expect(subject).to be_failure - expect(subject.exceptions).to match(a_hash_including('step_1' => include('Example'))) - end - end end context 'for success' do @@ -683,37 +672,6 @@ def step_3 ) end end - - context 'for exceptioning step' do - let(:operation_class) do - Class.new(Operation::Base) do - def self.name - 'MyClass' - end - - success do - step :step_1 - step :step_2 - end - - def step_1 - raise(StandardError, 'Example') - end - - def step_2 - true - end - end - end - - it 'calls step_1 only' do - expect_any_instance_of(operation_class).to receive(:step_1).and_call_original - expect_any_instance_of(operation_class).to receive(:step_2).and_call_original - expect(subject.executions).to match_array(%i[step_1 step_2]) - expect(subject).to be_failure - expect(subject.exceptions).to match(a_hash_including('MyClass#step_1' => include('Example'))) - end - end end context 'for context' do @@ -907,52 +865,6 @@ def step_5 expect(subject.executions).to match_array(%i[step_1 step_2 step_3 step_4 step_5]) end - context 'for raising exception' do - let(:operation_class) do - Class.new(Operation::Base) do - step :step_1 - step :step_2 - transaction do - step :step_3 - step :step_4 - end - step :step_5 - - def step_1 - true - end - - def step_2 - true - end - - def step_3 - raise(StandardError, 'example') - end - - def step_4 - true - end - - def step_5 - true - end - end - end - - it 'keeps track on exceptions' do - expect(subject.exceptions).to match(a_hash_including('step_3' => include('example'))) - end - - it 'evaluates 3 steps' do - expect(subject.executions).to match_array(%i[step_1 step_2 step_3]) - end - - it 'fails' do - expect(subject).to be_failure - end - end - context 'for finished execution step inside transaction' do let(:operation_class) do Class.new(Operation::Base) do @@ -1237,39 +1149,6 @@ def step_1 end end - context 'when the operations step throws exception' do - let(:dependencies) do - { injected_operation: valid_operation } - end - - let(:operation_class) do - Class.new(Operation::Base) do - operations :operations_collection - step :step_1 - - def operations_collection - nil.unknown_method? - (1..3).map do - dependencies[:injected_operation].call - end - end - - def step_1 - result.output = context[:operations_collection_output] - end - end - end - - it 'ends with failure' do - expect(subject).to be_failure - end - - it 'gives additional information about' do - expect(subject.exceptions['operations_collection']).to match('undefined method') - expect(subject.executions).to match_array([:operations_collection]) - end - end - context 'when the inner operation is valid' do let(:dependencies) do { injected_operation: valid_operation } diff --git a/spec/opera/operation/result_spec.rb b/spec/opera/operation/result_spec.rb index 843ba16..8e7affe 100644 --- a/spec/opera/operation/result_spec.rb +++ b/spec/opera/operation/result_spec.rb @@ -19,11 +19,10 @@ module Opera describe '#failures' do before do subject.add_error(:foo1, :bar1) - subject.add_exception(:foo2, :bar2) end - it 'returns errors and exceptions combined' do - expect(subject.failures).to eq(foo1: [:bar1], 'foo2' => :bar2) + it 'returns errors' do + expect(subject.failures).to eq(foo1: [:bar1]) end end @@ -44,9 +43,10 @@ module Opera before { subject.add_error(:example, 'Example') } it 'raises exception' do - expect do - subject.output! - end.to raise_error(Opera::Operation::Result::OutputError, 'Cannot retrieve output from a Failure.') + expect { subject.output! }.to raise_error(Opera::Operation::Result::OutputError) do |error| + expect(error.message).to eq('Cannot retrieve output from a Failure.') + expect(error.errors).to eq(example: ['Example']) + end end end end @@ -116,20 +116,6 @@ module Opera end end - describe '#add_exception' do - it do - subject.add_exception(:example, 'Example') - expect(subject.exceptions).to eq('example' => 'Example') - end - - context 'when classname provided' do - it do - subject.add_exception(:example, 'Example', classname: 'Foo') - expect(subject.exceptions).to eq('Foo#example' => 'Example') - end - end - end - describe '#add_information' do let(:result) do {