Skip to content

Commit d37f7a5

Browse files
Use Kernel#throw as success interrupter (#7)
* 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
1 parent 39ae88b commit d37f7a5

File tree

5 files changed

+96
-48
lines changed

5 files changed

+96
-48
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
resol (0.5.1)
4+
resol (0.6.0)
55
smart_initializer (~> 0.7)
66

77
GEM
@@ -131,4 +131,4 @@ DEPENDENCIES
131131
simplecov-lcov
132132

133133
BUNDLED WITH
134-
2.2.19
134+
2.2.21

lib/resol/service.rb

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,58 +8,47 @@ module Resol
88
class Service
99
class InvalidCommandImplementation < StandardError; end
1010

11-
class Interruption < StandardError
12-
attr_accessor :data
11+
class Failure < StandardError
12+
attr_accessor :data, :code
1313

14-
def initialize(data)
14+
def initialize(code, data)
15+
self.code = code
1516
self.data = data
16-
super
17+
super(data)
1718
end
1819

1920
def inspect
2021
"#{self.class.name}: #{message}"
2122
end
2223

23-
def message
24-
data.inspect
25-
end
26-
end
27-
28-
class Failure < Interruption
29-
attr_accessor :code
30-
31-
def initialize(code, data)
32-
self.code = code
33-
super(data)
34-
end
35-
3624
def message
3725
data ? "#{code.inspect} => #{data.inspect}" : code.inspect
3826
end
3927
end
4028

41-
class Success < Interruption; end
42-
4329
include SmartCore::Initializer
4430
include Resol::Builder
4531
include Resol::Callbacks
4632

33+
Result = Struct.new(:data)
34+
4735
class << self
4836
def inherited(klass)
4937
klass.const_set(:Failure, Class.new(klass::Failure))
50-
klass.const_set(:Success, Class.new(klass::Success))
5138
super
5239
end
5340

5441
def call(*args, **kwargs, &block)
5542
command = build(*args, **kwargs)
56-
__run_callbacks__(command)
57-
command.call(&block)
43+
result = catch(command) do
44+
__run_callbacks__(command)
45+
command.call(&block)
46+
nil
47+
end
48+
return Resol::Success(result.data) unless result.nil?
5849

5950
error_message = "No success! or fail! called in the #call method in #{command.class}"
6051
raise InvalidCommandImplementation, error_message
61-
rescue self::Success => e
62-
Resol::Success(e.data)
6352
rescue self::Failure => e
6453
Resol::Failure(e)
6554
end
@@ -78,7 +67,7 @@ def fail!(code, data = nil)
7867
end
7968

8069
def success!(data = nil)
81-
raise self.class::Success.new(data)
70+
throw(self, Result.new(data))
8271
end
8372
end
8473
end

lib/resol/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# frozen_string_literal: true
22

33
module Resol
4-
VERSION = "0.5.1"
4+
VERSION = "0.6.0"
55
end

spec/interruptions_spec.rb

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
11
# frozen_string_literal: true
22

3-
RSpec.describe Resol::Service::Interruption do
4-
describe Resol::Service::Success do
5-
let(:object) { Resol::Service::Success.new(:some_data) }
3+
RSpec.describe Resol::Service::Failure do
4+
let(:object) { Resol::Service::Failure.new(:some_data, data) }
65

7-
it { expect(object.inspect).to eq("Resol::Service::Success: :some_data") }
6+
context "without data" do
7+
let(:data) { nil }
8+
9+
it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data") }
810
it { expect(object.message).to eq(":some_data") }
911
end
1012

11-
describe Resol::Service::Failure do
12-
let(:object) { Resol::Service::Failure.new(:some_data, data) }
13-
14-
context "without data" do
15-
let(:data) { nil }
16-
17-
it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data") }
18-
it { expect(object.message).to eq(":some_data") }
19-
end
20-
21-
context "with data" do
22-
let(:data) { :data }
13+
context "with data" do
14+
let(:data) { :data }
2315

24-
it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data => :data") }
25-
it { expect(object.message).to eq(":some_data => :data") }
26-
end
16+
it { expect(object.inspect).to eq("Resol::Service::Failure: :some_data => :data") }
17+
it { expect(object.message).to eq(":some_data => :data") }
2718
end
2819
end

spec/service_spec.rb

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# frozen_string_literal: true
22

3+
class DB
4+
class << self
5+
attr_accessor :rollbacked
6+
7+
def transaction
8+
self.rollbacked = false
9+
10+
yield
11+
"return_some_val"
12+
# rubocop:disable Lint/RescueException
13+
rescue Exception
14+
# rubocop:enable Lint/RescueException
15+
self.rollbacked = true
16+
raise
17+
end
18+
end
19+
end
20+
321
class SuccessService < Resol::Service
422
def call
523
success!(:success_result)
@@ -13,7 +31,9 @@ def call
1331
end
1432

1533
class EmptyService < Resol::Service
16-
def call; end
34+
def call
35+
"some_string"
36+
end
1737
end
1838

1939
class ServiceWithCallbacks < Resol::Service
@@ -40,6 +60,27 @@ def set_other_value
4060
end
4161
end
4262

63+
class ServiceWithTransaction < Resol::Service
64+
def call
65+
DB.transaction { success! }
66+
end
67+
end
68+
69+
class ServiceWithFailInTransaction < Resol::Service
70+
def call
71+
DB.transaction { fail!(:failed) }
72+
end
73+
end
74+
75+
class HackyService < Resol::Service
76+
param :count
77+
78+
def call
79+
success! unless count.zero?
80+
HackyService.build(count + 1).call
81+
end
82+
end
83+
4384
RSpec.describe Resol::Service do
4485
it "returns a success result" do
4586
expect(SuccessService.call!).to eq(:success_result)
@@ -64,4 +105,31 @@ def set_other_value
64105
expect(SubServiceWithCallbacks.call!).to eq("some_value_postfix")
65106
expect(ServiceWithCallbacks.call!).to eq("some_value")
66107
end
108+
109+
it "doesn't rollback transaction" do
110+
result = ServiceWithTransaction.call
111+
expect(result.success?).to eq(true)
112+
expect(result.value!).to eq(nil)
113+
expect(DB.rollbacked).to eq(false)
114+
end
115+
116+
context "when service failed" do
117+
it "rollbacks transaction" do
118+
result = ServiceWithFailInTransaction.call
119+
expect(result.failure?).to eq(true)
120+
result.or do |error|
121+
expect(error.code).to eq(:failed)
122+
end
123+
124+
expect(DB.rollbacked).to eq(true)
125+
end
126+
end
127+
128+
context "when using instance #call inside other service" do
129+
let(:expected_message) { /uncaught throw #<HackyService/ }
130+
131+
it "raises an exception" do
132+
expect { HackyService.call!(0) }.to raise_error(UncaughtThrowError, expected_message)
133+
end
134+
end
67135
end

0 commit comments

Comments
 (0)