Skip to content

Commit 01c7403

Browse files
authored
Add clear error message for direct #call usage (#10)
1 parent 647f919 commit 01c7403

File tree

4 files changed

+117
-66
lines changed

4 files changed

+117
-66
lines changed

.github/workflows/test.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- uses: actions/checkout@v2
1717
- uses: ruby/setup-ruby@v1
1818
with:
19-
ruby-version: 3
19+
ruby-version: "3.1"
2020
bundler-cache: true
2121
- name: Run Linter
2222
run: bundle exec ci-helper RubocopLint
@@ -43,7 +43,7 @@ jobs:
4343
strategy:
4444
fail-fast: false
4545
matrix:
46-
ruby: [2.7]
46+
ruby: ["2.7", "3.0"]
4747
experimental: [false]
4848
include:
4949
- ruby: head

Gemfile.lock

+46-48
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ PATH
77
GEM
88
remote: https://rubygems.org/
99
specs:
10-
activesupport (6.1.4.1)
10+
activesupport (7.0.2.2)
1111
concurrent-ruby (~> 1.0, >= 1.0.2)
1212
i18n (>= 1.6, < 2)
1313
minitest (>= 5.1)
1414
tzinfo (~> 2.0)
15-
zeitwerk (~> 2.3)
1615
ast (2.4.2)
1716
bundler-audit (0.9.0.1)
1817
bundler (>= 1.2.0, < 3)
@@ -24,97 +23,96 @@ GEM
2423
coderay (1.1.3)
2524
colorize (0.8.1)
2625
concurrent-ruby (1.1.9)
27-
diff-lcs (1.4.4)
26+
diff-lcs (1.5.0)
2827
docile (1.4.0)
2928
dry-inflector (0.2.1)
30-
i18n (1.8.11)
29+
i18n (1.10.0)
3130
concurrent-ruby (~> 1.0)
3231
method_source (1.0.0)
33-
minitest (5.14.4)
32+
minitest (5.15.0)
3433
parallel (1.21.0)
35-
parser (3.0.3.1)
34+
parser (3.1.1.0)
3635
ast (~> 2.4.1)
3736
pry (0.14.1)
3837
coderay (~> 1.1)
3938
method_source (~> 1.0)
40-
qonfig (0.26.0)
39+
qonfig (0.27.0)
4140
rack (2.2.3)
42-
rainbow (3.0.0)
41+
rainbow (3.1.1)
4342
rake (13.0.6)
44-
regexp_parser (2.2.0)
43+
regexp_parser (2.2.1)
4544
rexml (3.2.5)
46-
rspec (3.10.0)
47-
rspec-core (~> 3.10.0)
48-
rspec-expectations (~> 3.10.0)
49-
rspec-mocks (~> 3.10.0)
50-
rspec-core (3.10.1)
51-
rspec-support (~> 3.10.0)
52-
rspec-expectations (3.10.1)
45+
rspec (3.11.0)
46+
rspec-core (~> 3.11.0)
47+
rspec-expectations (~> 3.11.0)
48+
rspec-mocks (~> 3.11.0)
49+
rspec-core (3.11.0)
50+
rspec-support (~> 3.11.0)
51+
rspec-expectations (3.11.0)
5352
diff-lcs (>= 1.2.0, < 2.0)
54-
rspec-support (~> 3.10.0)
55-
rspec-mocks (3.10.2)
53+
rspec-support (~> 3.11.0)
54+
rspec-mocks (3.11.0)
5655
diff-lcs (>= 1.2.0, < 2.0)
57-
rspec-support (~> 3.10.0)
58-
rspec-support (3.10.3)
59-
rubocop (1.17.0)
56+
rspec-support (~> 3.11.0)
57+
rspec-support (3.11.0)
58+
rubocop (1.25.1)
6059
parallel (~> 1.10)
61-
parser (>= 3.0.0.0)
60+
parser (>= 3.1.0.0)
6261
rainbow (>= 2.2.2, < 4.0)
6362
regexp_parser (>= 1.8, < 3.0)
6463
rexml
65-
rubocop-ast (>= 1.7.0, < 2.0)
64+
rubocop-ast (>= 1.15.1, < 2.0)
6665
ruby-progressbar (~> 1.7)
6766
unicode-display_width (>= 1.4.0, < 3.0)
68-
rubocop-ast (1.14.0)
69-
parser (>= 3.0.1.1)
70-
rubocop-config-umbrellio (1.17.0.53)
71-
rubocop (= 1.17.0)
72-
rubocop-performance (= 1.10.0)
73-
rubocop-rails (= 2.9.1)
74-
rubocop-rake (= 0.5.1)
75-
rubocop-rspec (= 2.2.0)
76-
rubocop-sequel (= 0.2.0)
77-
rubocop-performance (1.10.0)
78-
rubocop (>= 0.90.0, < 2.0)
67+
rubocop-ast (1.16.0)
68+
parser (>= 3.1.1.0)
69+
rubocop-config-umbrellio (1.25.0.61)
70+
rubocop (~> 1.25.0)
71+
rubocop-performance (~> 1.13.0)
72+
rubocop-rails (~> 2.13.0)
73+
rubocop-rake (~> 0.6.0)
74+
rubocop-rspec (~> 2.7.0)
75+
rubocop-sequel (~> 0.3.3)
76+
rubocop-performance (1.13.2)
77+
rubocop (>= 1.7.0, < 2.0)
7978
rubocop-ast (>= 0.4.0)
80-
rubocop-rails (2.9.1)
79+
rubocop-rails (2.13.2)
8180
activesupport (>= 4.2.0)
8281
rack (>= 1.1)
83-
rubocop (>= 0.90.0, < 2.0)
84-
rubocop-rake (0.5.1)
85-
rubocop
86-
rubocop-rspec (2.2.0)
82+
rubocop (>= 1.7.0, < 2.0)
83+
rubocop-rake (0.6.0)
8784
rubocop (~> 1.0)
88-
rubocop-ast (>= 1.1.0)
89-
rubocop-sequel (0.2.0)
85+
rubocop-rspec (2.7.0)
86+
rubocop (~> 1.19)
87+
rubocop-sequel (0.3.3)
9088
rubocop (~> 1.0)
9189
ruby-progressbar (1.11.0)
92-
sequel (5.51.0)
90+
sequel (5.54.0)
9391
simplecov (0.21.2)
9492
docile (~> 1.1)
9593
simplecov-html (~> 0.11)
9694
simplecov_json_formatter (~> 0.1)
9795
simplecov-html (0.12.3)
9896
simplecov-lcov (0.8.0)
99-
simplecov_json_formatter (0.1.3)
100-
smart_engine (0.11.0)
101-
smart_initializer (0.8.0)
97+
simplecov_json_formatter (0.1.4)
98+
smart_engine (0.12.0)
99+
smart_initializer (0.9.0)
102100
qonfig (~> 0.24)
103101
smart_engine (~> 0.11)
104102
smart_types (~> 0.4)
105103
smart_types (0.7.0)
106104
smart_engine (~> 0.11)
107105
symbiont-ruby (0.7.0)
108-
thor (1.1.0)
106+
thor (1.2.1)
109107
tzinfo (2.0.4)
110108
concurrent-ruby (~> 1.0)
111109
umbrellio-sequel-plugins (0.5.1.27)
112110
sequel
113111
symbiont-ruby
114112
unicode-display_width (2.1.0)
115-
zeitwerk (2.5.1)
116113

117114
PLATFORMS
115+
arm64-darwin-21
118116
x86_64-darwin-19
119117
x86_64-darwin-20
120118
x86_64-darwin-21
@@ -132,4 +130,4 @@ DEPENDENCIES
132130
simplecov-lcov
133131

134132
BUNDLED WITH
135-
2.2.31
133+
2.3.8

lib/resol/service.rb

+34-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
module Resol
88
class Service
99
class InvalidCommandImplementation < StandardError; end
10+
class InvalidCommandCall < StandardError; end
1011

1112
class Failure < StandardError
1213
attr_accessor :data, :code
@@ -39,16 +40,21 @@ def inherited(klass)
3940
end
4041

4142
def call(*args, **kwargs, &block)
42-
command = build(*args, **kwargs)
43-
result = catch(command) do
44-
__run_callbacks__(command)
45-
command.call(&block)
46-
nil
43+
service = build(*args, **kwargs)
44+
45+
result = catch(service) do
46+
service.instance_variable_set(:@__performing__, true)
47+
__run_callbacks__(service)
48+
service.call(&block)
49+
:uncaught
4750
end
48-
return Resol::Success(result.data) unless result.nil?
4951

50-
error_message = "No success! or fail! called in the #call method in #{command.class}"
51-
raise InvalidCommandImplementation, error_message
52+
if result == :uncaught
53+
error_message = "No `#success!` or `#fail!` called in `#call` method in #{service.class}."
54+
raise InvalidCommandImplementation, error_message
55+
else
56+
Resol::Success(result.data)
57+
end
5258
rescue self::Failure => e
5359
Resol::Failure(e)
5460
end
@@ -62,12 +68,30 @@ def call!(...)
6268

6369
private
6470

71+
attr_reader :__performing__
72+
6573
def fail!(code, data = nil)
66-
raise self.class::Failure.new(code, data)
74+
check_performing do
75+
raise self.class::Failure.new(code, data)
76+
end
6777
end
6878

6979
def success!(data = nil)
70-
throw(self, Result.new(data))
80+
check_performing do
81+
throw(self, Result.new(data))
82+
end
83+
end
84+
85+
def check_performing
86+
if __performing__
87+
yield
88+
else
89+
error_message =
90+
"It looks like #call instance method was called directly in #{self.class}. " \
91+
"You must always use class-level `.call` or `.call!` method."
92+
93+
raise InvalidCommandCall, error_message
94+
end
7195
end
7296
end
7397
end

spec/service_spec.rb

+35-6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ def call
3636
end
3737
end
3838

39+
class AbstractService < Resol::Service
40+
end
41+
42+
class InheritedService < AbstractService
43+
def call
44+
success!(:success_result)
45+
end
46+
end
47+
48+
class ServiceWithCall < Resol::Service
49+
def call
50+
success!(:success_result)
51+
end
52+
end
53+
54+
class SubService < ServiceWithCall
55+
end
56+
3957
class ServiceWithCallbacks < Resol::Service
4058
before_call :define_instance_var
4159

@@ -94,13 +112,20 @@ def call
94112
end
95113
end
96114

97-
it "raises an unimplemented error" do
115+
it "raises an InvalidCommandImplementation error" do
98116
expect { EmptyService.call! }.to raise_error do |error|
99117
expect(error).to be_a(EmptyService::InvalidCommandImplementation)
100-
expect(error.message).to eq("No success! or fail! called in the #call method in EmptyService")
118+
expect(error.message).to eq(
119+
"No `#success!` or `#fail!` called in `#call` method in EmptyService.",
120+
)
101121
end
102122
end
103123

124+
it "properly works with inherited services" do
125+
expect(InheritedService.call!).to eq(:success_result)
126+
expect(SubService.call!).to eq(:success_result)
127+
end
128+
104129
it "properly executes callbacks" do
105130
expect(SubServiceWithCallbacks.call!).to eq("some_value_postfix")
106131
expect(ServiceWithCallbacks.call!).to eq("some_value")
@@ -125,11 +150,15 @@ def call
125150
end
126151
end
127152

128-
context "when using instance #call inside other service" do
129-
let(:expected_message) { /uncaught throw #<HackyService/ }
153+
context "when using instance #call" do
154+
it "raises error" do
155+
expect { SuccessService.build.call }.to raise_error(Resol::Service::InvalidCommandCall)
156+
end
157+
end
130158

131-
it "raises an exception" do
132-
expect { HackyService.call!(0) }.to raise_error(UncaughtThrowError, expected_message)
159+
context "when using instance #call inside other service" do
160+
it "raises error" do
161+
expect { HackyService.call!(0) }.to raise_error(Resol::Service::InvalidCommandCall)
133162
end
134163
end
135164
end

0 commit comments

Comments
 (0)