Skip to content

Commit

Permalink
Use Kernel#throw as success interrupter (#7)
Browse files Browse the repository at this point in the history
* Remove Success Interruption, use Kernel#throw instead

* Add spec for rollback operation

* Add specs for failure

* More checks

* Remove value from success

* Fix bug with catching thows of other classes

* Fix bugs found in discus
  • Loading branch information
AnotherRegularDude authored Jul 1, 2021
1 parent 39ae88b commit d37f7a5
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 48 deletions.
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
resol (0.5.1)
resol (0.6.0)
smart_initializer (~> 0.7)

GEM
Expand Down Expand Up @@ -131,4 +131,4 @@ DEPENDENCIES
simplecov-lcov

BUNDLED WITH
2.2.19
2.2.21
39 changes: 14 additions & 25 deletions lib/resol/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,47 @@ module Resol
class Service
class InvalidCommandImplementation < StandardError; end

class Interruption < StandardError
attr_accessor :data
class Failure < StandardError
attr_accessor :data, :code

def initialize(data)
def initialize(code, data)
self.code = code
self.data = data
super
super(data)
end

def inspect
"#{self.class.name}: #{message}"
end

def message
data.inspect
end
end

class Failure < Interruption
attr_accessor :code

def initialize(code, data)
self.code = code
super(data)
end

def message
data ? "#{code.inspect} => #{data.inspect}" : code.inspect
end
end

class Success < Interruption; end

include SmartCore::Initializer
include Resol::Builder
include Resol::Callbacks

Result = Struct.new(:data)

class << self
def inherited(klass)
klass.const_set(:Failure, Class.new(klass::Failure))
klass.const_set(:Success, Class.new(klass::Success))
super
end

def call(*args, **kwargs, &block)
command = build(*args, **kwargs)
__run_callbacks__(command)
command.call(&block)
result = catch(command) do
__run_callbacks__(command)
command.call(&block)
nil
end
return Resol::Success(result.data) unless result.nil?

error_message = "No success! or fail! called in the #call method in #{command.class}"
raise InvalidCommandImplementation, error_message
rescue self::Success => e
Resol::Success(e.data)
rescue self::Failure => e
Resol::Failure(e)
end
Expand All @@ -78,7 +67,7 @@ def fail!(code, data = nil)
end

def success!(data = nil)
raise self.class::Success.new(data)
throw(self, Result.new(data))
end
end
end
2 changes: 1 addition & 1 deletion lib/resol/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Resol
VERSION = "0.5.1"
VERSION = "0.6.0"
end
29 changes: 10 additions & 19 deletions spec/interruptions_spec.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
# frozen_string_literal: true

RSpec.describe Resol::Service::Interruption do
describe Resol::Service::Success do
let(:object) { Resol::Service::Success.new(:some_data) }
RSpec.describe Resol::Service::Failure do
let(:object) { Resol::Service::Failure.new(:some_data, data) }

it { expect(object.inspect).to eq("Resol::Service::Success: :some_data") }
context "without data" do
let(:data) { nil }

it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data") }
it { expect(object.message).to eq(":some_data") }
end

describe Resol::Service::Failure do
let(:object) { Resol::Service::Failure.new(:some_data, data) }

context "without data" do
let(:data) { nil }

it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data") }
it { expect(object.message).to eq(":some_data") }
end

context "with data" do
let(:data) { :data }
context "with data" do
let(:data) { :data }

it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data => :data") }
it { expect(object.message).to eq(":some_data => :data") }
end
it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data => :data") }
it { expect(object.message).to eq(":some_data => :data") }
end
end
70 changes: 69 additions & 1 deletion spec/service_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# frozen_string_literal: true

class DB
class << self
attr_accessor :rollbacked

def transaction
self.rollbacked = false

yield
"return_some_val"
# rubocop:disable Lint/RescueException
rescue Exception
# rubocop:enable Lint/RescueException
self.rollbacked = true
raise
end
end
end

class SuccessService < Resol::Service
def call
success!(:success_result)
Expand All @@ -13,7 +31,9 @@ def call
end

class EmptyService < Resol::Service
def call; end
def call
"some_string"
end
end

class ServiceWithCallbacks < Resol::Service
Expand All @@ -40,6 +60,27 @@ def set_other_value
end
end

class ServiceWithTransaction < Resol::Service
def call
DB.transaction { success! }
end
end

class ServiceWithFailInTransaction < Resol::Service
def call
DB.transaction { fail!(:failed) }
end
end

class HackyService < Resol::Service
param :count

def call
success! unless count.zero?
HackyService.build(count + 1).call
end
end

RSpec.describe Resol::Service do
it "returns a success result" do
expect(SuccessService.call!).to eq(:success_result)
Expand All @@ -64,4 +105,31 @@ def set_other_value
expect(SubServiceWithCallbacks.call!).to eq("some_value_postfix")
expect(ServiceWithCallbacks.call!).to eq("some_value")
end

it "doesn't rollback transaction" do
result = ServiceWithTransaction.call
expect(result.success?).to eq(true)
expect(result.value!).to eq(nil)
expect(DB.rollbacked).to eq(false)
end

context "when service failed" do
it "rollbacks transaction" do
result = ServiceWithFailInTransaction.call
expect(result.failure?).to eq(true)
result.or do |error|
expect(error.code).to eq(:failed)
end

expect(DB.rollbacked).to eq(true)
end
end

context "when using instance #call inside other service" do
let(:expected_message) { /uncaught throw #<HackyService/ }

it "raises an exception" do
expect { HackyService.call!(0) }.to raise_error(UncaughtThrowError, expected_message)
end
end
end

0 comments on commit d37f7a5

Please sign in to comment.