Skip to content

Commit

Permalink
Add clear error message for direct #call usage (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
tycooon committed Mar 4, 2022
1 parent 647f919 commit 01c7403
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 66 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3
ruby-version: "3.1"
bundler-cache: true
- name: Run Linter
run: bundle exec ci-helper RubocopLint
Expand All @@ -43,7 +43,7 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: [2.7]
ruby: ["2.7", "3.0"]
experimental: [false]
include:
- ruby: head
Expand Down
94 changes: 46 additions & 48 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activesupport (6.1.4.1)
activesupport (7.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
ast (2.4.2)
bundler-audit (0.9.0.1)
bundler (>= 1.2.0, < 3)
Expand All @@ -24,97 +23,96 @@ GEM
coderay (1.1.3)
colorize (0.8.1)
concurrent-ruby (1.1.9)
diff-lcs (1.4.4)
diff-lcs (1.5.0)
docile (1.4.0)
dry-inflector (0.2.1)
i18n (1.8.11)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
method_source (1.0.0)
minitest (5.14.4)
minitest (5.15.0)
parallel (1.21.0)
parser (3.0.3.1)
parser (3.1.1.0)
ast (~> 2.4.1)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
qonfig (0.26.0)
qonfig (0.27.0)
rack (2.2.3)
rainbow (3.0.0)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.2.0)
regexp_parser (2.2.1)
rexml (3.2.5)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0)
rspec-core (3.10.1)
rspec-support (~> 3.10.0)
rspec-expectations (3.10.1)
rspec (3.11.0)
rspec-core (~> 3.11.0)
rspec-expectations (~> 3.11.0)
rspec-mocks (~> 3.11.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-mocks (3.10.2)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.10.0)
rspec-support (3.10.3)
rubocop (1.17.0)
rspec-support (~> 3.11.0)
rspec-support (3.11.0)
rubocop (1.25.1)
parallel (~> 1.10)
parser (>= 3.0.0.0)
parser (>= 3.1.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.7.0, < 2.0)
rubocop-ast (>= 1.15.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.14.0)
parser (>= 3.0.1.1)
rubocop-config-umbrellio (1.17.0.53)
rubocop (= 1.17.0)
rubocop-performance (= 1.10.0)
rubocop-rails (= 2.9.1)
rubocop-rake (= 0.5.1)
rubocop-rspec (= 2.2.0)
rubocop-sequel (= 0.2.0)
rubocop-performance (1.10.0)
rubocop (>= 0.90.0, < 2.0)
rubocop-ast (1.16.0)
parser (>= 3.1.1.0)
rubocop-config-umbrellio (1.25.0.61)
rubocop (~> 1.25.0)
rubocop-performance (~> 1.13.0)
rubocop-rails (~> 2.13.0)
rubocop-rake (~> 0.6.0)
rubocop-rspec (~> 2.7.0)
rubocop-sequel (~> 0.3.3)
rubocop-performance (1.13.2)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.9.1)
rubocop-rails (2.13.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 0.90.0, < 2.0)
rubocop-rake (0.5.1)
rubocop
rubocop-rspec (2.2.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-ast (>= 1.1.0)
rubocop-sequel (0.2.0)
rubocop-rspec (2.7.0)
rubocop (~> 1.19)
rubocop-sequel (0.3.3)
rubocop (~> 1.0)
ruby-progressbar (1.11.0)
sequel (5.51.0)
sequel (5.54.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.3)
smart_engine (0.11.0)
smart_initializer (0.8.0)
simplecov_json_formatter (0.1.4)
smart_engine (0.12.0)
smart_initializer (0.9.0)
qonfig (~> 0.24)
smart_engine (~> 0.11)
smart_types (~> 0.4)
smart_types (0.7.0)
smart_engine (~> 0.11)
symbiont-ruby (0.7.0)
thor (1.1.0)
thor (1.2.1)
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
umbrellio-sequel-plugins (0.5.1.27)
sequel
symbiont-ruby
unicode-display_width (2.1.0)
zeitwerk (2.5.1)

PLATFORMS
arm64-darwin-21
x86_64-darwin-19
x86_64-darwin-20
x86_64-darwin-21
Expand All @@ -132,4 +130,4 @@ DEPENDENCIES
simplecov-lcov

BUNDLED WITH
2.2.31
2.3.8
44 changes: 34 additions & 10 deletions lib/resol/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
module Resol
class Service
class InvalidCommandImplementation < StandardError; end
class InvalidCommandCall < StandardError; end

class Failure < StandardError
attr_accessor :data, :code
Expand Down Expand Up @@ -39,16 +40,21 @@ def inherited(klass)
end

def call(*args, **kwargs, &block)
command = build(*args, **kwargs)
result = catch(command) do
__run_callbacks__(command)
command.call(&block)
nil
service = build(*args, **kwargs)

result = catch(service) do
service.instance_variable_set(:@__performing__, true)
__run_callbacks__(service)
service.call(&block)
:uncaught
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
if result == :uncaught
error_message = "No `#success!` or `#fail!` called in `#call` method in #{service.class}."
raise InvalidCommandImplementation, error_message
else
Resol::Success(result.data)
end
rescue self::Failure => e
Resol::Failure(e)
end
Expand All @@ -62,12 +68,30 @@ def call!(...)

private

attr_reader :__performing__

def fail!(code, data = nil)
raise self.class::Failure.new(code, data)
check_performing do
raise self.class::Failure.new(code, data)
end
end

def success!(data = nil)
throw(self, Result.new(data))
check_performing do
throw(self, Result.new(data))
end
end

def check_performing
if __performing__
yield
else
error_message =
"It looks like #call instance method was called directly in #{self.class}. " \
"You must always use class-level `.call` or `.call!` method."

raise InvalidCommandCall, error_message
end
end
end
end
41 changes: 35 additions & 6 deletions spec/service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ def call
end
end

class AbstractService < Resol::Service
end

class InheritedService < AbstractService
def call
success!(:success_result)
end
end

class ServiceWithCall < Resol::Service
def call
success!(:success_result)
end
end

class SubService < ServiceWithCall
end

class ServiceWithCallbacks < Resol::Service
before_call :define_instance_var

Expand Down Expand Up @@ -94,13 +112,20 @@ def call
end
end

it "raises an unimplemented error" do
it "raises an InvalidCommandImplementation error" do
expect { EmptyService.call! }.to raise_error do |error|
expect(error).to be_a(EmptyService::InvalidCommandImplementation)
expect(error.message).to eq("No success! or fail! called in the #call method in EmptyService")
expect(error.message).to eq(
"No `#success!` or `#fail!` called in `#call` method in EmptyService.",
)
end
end

it "properly works with inherited services" do
expect(InheritedService.call!).to eq(:success_result)
expect(SubService.call!).to eq(:success_result)
end

it "properly executes callbacks" do
expect(SubServiceWithCallbacks.call!).to eq("some_value_postfix")
expect(ServiceWithCallbacks.call!).to eq("some_value")
Expand All @@ -125,11 +150,15 @@ def call
end
end

context "when using instance #call inside other service" do
let(:expected_message) { /uncaught throw #<HackyService/ }
context "when using instance #call" do
it "raises error" do
expect { SuccessService.build.call }.to raise_error(Resol::Service::InvalidCommandCall)
end
end

it "raises an exception" do
expect { HackyService.call!(0) }.to raise_error(UncaughtThrowError, expected_message)
context "when using instance #call inside other service" do
it "raises error" do
expect { HackyService.call!(0) }.to raise_error(Resol::Service::InvalidCommandCall)
end
end
end

0 comments on commit 01c7403

Please sign in to comment.