slayer 0.4.0.beta4 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/release.yml +27 -0
- data/.github/workflows/test.yml +28 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +13 -18
- data/Dockerfile +1 -2
- data/README.md +67 -47
- data/Rakefile +0 -11
- data/lib/slayer/command.rb +65 -8
- data/lib/slayer/compat/compat_040.rb +52 -0
- data/lib/slayer/compat/minitest_compat_040.rb +12 -0
- data/lib/slayer/compat/rspec_compat_040.rb +8 -0
- data/lib/slayer/cops/return_matcher.rb +45 -0
- data/lib/slayer/minitest.rb +6 -7
- data/lib/slayer/result.rb +5 -5
- data/lib/slayer/result_matcher.rb +24 -24
- data/lib/slayer/rspec.rb +20 -4
- data/lib/slayer/version.rb +1 -1
- data/lib/slayer.rb +0 -2
- data/slayer.gemspec +5 -8
- metadata +23 -66
- data/.hound.yml +0 -2
- data/.rubocop_todo.yml +0 -48
- data/.travis.yml +0 -6
- data/CHANGELOG.md +0 -9
- data/lib/slayer/hook.rb +0 -154
- data/lib/slayer/service.rb +0 -136
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f88c862cf4596840150934c88372a6cda656557e34da5a3918e98f19b159c645
|
4
|
+
data.tar.gz: 84d431982bad263f5a8cad99e3cd7fdb62610b9b75a80621c8ad457d01f93600
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e390d010a072ec169f4c8a776547a4e3d562599db94a11a91534c7f0a2cc0667ce10c94fcfe765de658edb1bd477f38a4ac14b1972cbc183c03c666391193fbf
|
7
|
+
data.tar.gz: 13adcd8d46c0e28e1f4c243426ca1ad628157ca056d8298932dbdbf946d288661358a1c6e2a6ebbcf2e497a9efea45811579cb6063c28e46e80410f1a6f6d0e4
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# .github/workflows/release.yml
|
2
|
+
|
3
|
+
name: Release
|
4
|
+
|
5
|
+
on:
|
6
|
+
workflow_dispatch:
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
release:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
steps:
|
12
|
+
- uses: actions/checkout@v2
|
13
|
+
- uses: ruby/setup-ruby@v1
|
14
|
+
with:
|
15
|
+
ruby-version: 3.0.0
|
16
|
+
- run: bundle install
|
17
|
+
- name: publish gem
|
18
|
+
run: |
|
19
|
+
mkdir -p $HOME/.gem
|
20
|
+
touch $HOME/.gem/credentials
|
21
|
+
chmod 0600 $HOME/.gem/credentials
|
22
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
23
|
+
gem build *.gemspec
|
24
|
+
gem push *.gem
|
25
|
+
env:
|
26
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
27
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
name: Test & Lint
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [main]
|
6
|
+
pull_request:
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
test:
|
10
|
+
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby-version: ['3.1', '3.0', '2.7']
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v3
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@359bebbc29cbe6c87da6bc9ea3bc930432750108
|
21
|
+
with:
|
22
|
+
ruby-version: ${{ matrix.ruby-version }}
|
23
|
+
- name: Install dependencies
|
24
|
+
run: bundle install
|
25
|
+
- name: Rubocop
|
26
|
+
run: rubocop
|
27
|
+
- name: Run tests
|
28
|
+
run: bundle exec rake
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
inherit_from: .rubocop_todo.yml
|
2
|
-
|
3
1
|
AllCops:
|
4
|
-
|
2
|
+
NewCops: enable
|
3
|
+
SuggestExtensions: false
|
4
|
+
TargetRubyVersion: 3.1
|
5
5
|
Include:
|
6
6
|
- 'lib/**/*.rb'
|
7
7
|
- 'test/**/*.rb'
|
@@ -12,6 +12,14 @@ AllCops:
|
|
12
12
|
- 'bin/**/*'
|
13
13
|
- 'test/fixtures/**/*.rb'
|
14
14
|
|
15
|
+
Style/HashSyntax:
|
16
|
+
EnforcedShorthandSyntax: never
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: false
|
20
|
+
|
21
|
+
Naming/BlockForwarding:
|
22
|
+
Enabled: false
|
15
23
|
|
16
24
|
Style/RedundantSelf:
|
17
25
|
Enabled: false
|
@@ -34,13 +42,7 @@ Style/FrozenStringLiteralComment:
|
|
34
42
|
Layout/CommentIndentation:
|
35
43
|
Enabled: false
|
36
44
|
|
37
|
-
|
38
|
-
Enabled: false
|
39
|
-
|
40
|
-
Layout/IndentationConsistency:
|
41
|
-
EnforcedStyle: rails
|
42
|
-
|
43
|
-
Metrics/LineLength:
|
45
|
+
Layout/LineLength:
|
44
46
|
Max: 120
|
45
47
|
|
46
48
|
Metrics/ClassLength:
|
@@ -49,17 +51,10 @@ Metrics/ClassLength:
|
|
49
51
|
Layout/EmptyLineBetweenDefs:
|
50
52
|
AllowAdjacentOneLineDefs: true
|
51
53
|
|
52
|
-
Naming/
|
54
|
+
Naming/MethodParameterName:
|
53
55
|
AllowedNames:
|
54
56
|
- _
|
55
57
|
|
56
|
-
# Temporarily disabled until this can be resolved in the todo file
|
57
|
-
# Style/Documentation:
|
58
|
-
# Exclude:
|
59
|
-
# - 'spec/**/*'
|
60
|
-
# - 'test/**/*'
|
61
|
-
# - 'lib/ext/**/*'
|
62
|
-
|
63
58
|
Style/ClassVars:
|
64
59
|
Exclude:
|
65
60
|
- 'lib/slayer/service.rb'
|
data/Dockerfile
CHANGED
data/README.md
CHANGED
@@ -6,9 +6,11 @@
|
|
6
6
|
|
7
7
|
Slayer is intended to operate as a minimal service layer for your ruby application. To achieve this, Slayer provides base classes for business logic.
|
8
8
|
|
9
|
+
**Slayer is still under development, and not yet ready for production use. We are targetting a stable API with the 0.4.0 launch, so expect breaking changes until then.**
|
10
|
+
|
9
11
|
## Application Structure
|
10
12
|
|
11
|
-
Slayer provides
|
13
|
+
Slayer provides 2 base classes for organizing your business logic: `Forms` and `Commands`. These each have a distinct role in your application's structure.
|
12
14
|
|
13
15
|
### Forms
|
14
16
|
|
@@ -16,16 +18,12 @@ Slayer provides 3 base classes for organizing your business logic: `Forms`, `Com
|
|
16
18
|
|
17
19
|
### Commands
|
18
20
|
|
19
|
-
`Slayer::Commands` are the bread and butter of your application's business logic
|
21
|
+
`Slayer::Commands` are the bread and butter of your application's business logic. `Commands` wrap logic into easily tested, isolated, composable classes. In our applications, we usually create a single `Command` per `Controller` endpoint.
|
20
22
|
|
21
23
|
`Slayer::Commands` must implement a `call` method, which always return a structured `Slayer::Result` object making operating on results straightforward. The `call` method can also take a block, which provides `Slayer::ResultMatcher` object, and enforces handling of both `pass` and `fail` conditions for that result.
|
22
24
|
|
23
25
|
This helps provide confidence that your core business logic is behaving in expected ways, and helps coerce you to develop in a clean and testable way.
|
24
26
|
|
25
|
-
### Services
|
26
|
-
|
27
|
-
`Slayer::Service`s are the base class of `Slayer::Command`s, and encapsulate re-usable chunks of application logic. `Services` also return structured `Slayer::Result` objects.
|
28
|
-
|
29
27
|
## Installation
|
30
28
|
|
31
29
|
Add this line to your application's Gemfile:
|
@@ -50,7 +48,7 @@ $ gem install slayer
|
|
50
48
|
|
51
49
|
### Commands
|
52
50
|
|
53
|
-
Slayer Commands should implement `call`, which will `pass` or `fail` the service based on input. Commands return a `Slayer::Result` which has a predictable interface for determining `
|
51
|
+
Slayer Commands should implement `call`, which will `pass` or `fail` the service based on input. Commands return a `Slayer::Result` which has a predictable interface for determining `passed?` or `failed?`, a 'value' payload object, a 'status' value, and a user presentable `message`.
|
54
52
|
|
55
53
|
```ruby
|
56
54
|
# A Command that passes when given the string "foo"
|
@@ -58,10 +56,10 @@ Slayer Commands should implement `call`, which will `pass` or `fail` the service
|
|
58
56
|
class FooCommand < Slayer::Command
|
59
57
|
def call(foo:)
|
60
58
|
unless foo == "foo"
|
61
|
-
|
59
|
+
return err value: foo, message: "Argument must be foo!"
|
62
60
|
end
|
63
61
|
|
64
|
-
|
62
|
+
ok value: foo
|
65
63
|
end
|
66
64
|
end
|
67
65
|
```
|
@@ -70,11 +68,11 @@ Handling the results of a command can be done in two ways. The primary way is th
|
|
70
68
|
|
71
69
|
```ruby
|
72
70
|
FooCommand.call(foo: "foo") do |m|
|
73
|
-
m.
|
71
|
+
m.ok do |value|
|
74
72
|
puts "This code runs on success"
|
75
73
|
end
|
76
74
|
|
77
|
-
m.
|
75
|
+
m.err do |_value, result|
|
78
76
|
puts "This code runs on failure. Message: #{result.message}"
|
79
77
|
end
|
80
78
|
|
@@ -92,10 +90,10 @@ The second is less comprehensive, but can be useful for very simple commands. Th
|
|
92
90
|
|
93
91
|
```ruby
|
94
92
|
result = FooCommand.call(foo: "foo")
|
95
|
-
puts result.
|
93
|
+
puts result.ok? # => true
|
96
94
|
|
97
95
|
result = FooCommand.call(foo: "bar")
|
98
|
-
puts result.
|
96
|
+
puts result.ok? # => false
|
99
97
|
```
|
100
98
|
|
101
99
|
Here's a more complex example demonstrating how the command pattern can be used to encapuslate the logic for validating and creating a new user. This example is shown using a `rails` controller, but the same approach can be used regardless of the framework.
|
@@ -105,7 +103,7 @@ Here's a more complex example demonstrating how the command pattern can be used
|
|
105
103
|
class CreateUserCommand < Slayer::Command
|
106
104
|
def call(create_user_form:)
|
107
105
|
unless arguments_valid?(create_user_form)
|
108
|
-
|
106
|
+
return err value: create_user_form, status: :arguments_invalid
|
109
107
|
end
|
110
108
|
|
111
109
|
user = nil
|
@@ -114,10 +112,10 @@ class CreateUserCommand < Slayer::Command
|
|
114
112
|
end
|
115
113
|
|
116
114
|
unless user.persisted?
|
117
|
-
|
115
|
+
return err message: I18n.t('user.create.error'), status: :unprocessible_entity
|
118
116
|
end
|
119
117
|
|
120
|
-
|
118
|
+
ok value: user
|
121
119
|
end
|
122
120
|
|
123
121
|
def arguments_valid?(create_user_form)
|
@@ -133,17 +131,17 @@ class UsersController < ApplicationController
|
|
133
131
|
@create_user_form = CreateUserForm.from_params(create_user_params)
|
134
132
|
|
135
133
|
CreateUserCommand.call(create_user_form: @create_user_form) do |m|
|
136
|
-
m.
|
134
|
+
m.ok do |user|
|
137
135
|
auto_login(user)
|
138
136
|
redirect_to root_path, notice: t('user.create.success')
|
139
137
|
end
|
140
138
|
|
141
|
-
m.
|
139
|
+
m.err(:arguments_invalid) do |_user, result|
|
142
140
|
flash[:error] = result.errors.full_messages.to_sentence
|
143
141
|
render :new, status: :unprocessible_entity
|
144
142
|
end
|
145
143
|
|
146
|
-
m.
|
144
|
+
m.err do |_user, result|
|
147
145
|
flash[:error] = t('user.create.error')
|
148
146
|
render :new, status: :bad_request
|
149
147
|
end
|
@@ -167,24 +165,24 @@ end
|
|
167
165
|
|
168
166
|
The result matcher is an object that is used to handle `Slayer::Result` objects based on their status.
|
169
167
|
|
170
|
-
#### Handlers: `
|
168
|
+
#### Handlers: `ok`, `err`, `all`, `ensure`
|
171
169
|
|
172
|
-
The result matcher block can take 4 types of handler blocks: `
|
170
|
+
The result matcher block can take 4 types of handler blocks: `ok`, `err`, `all`, and `ensure`. They operate as you would expect based on their names.
|
173
171
|
|
174
|
-
* The `
|
175
|
-
* The `
|
176
|
-
* The `all` block runs on any type of result --- `
|
172
|
+
* The `ok` block runs if the command was successful.
|
173
|
+
* The `err` block runs if the command was `koed`.
|
174
|
+
* The `all` block runs on any type of result --- `ok` or `err` --- unless the result has already been handled.
|
177
175
|
* The `ensure` block always runs after the result has been handled.
|
178
176
|
|
179
177
|
#### Handler Params
|
180
178
|
|
181
|
-
Every handler in the result matcher block is given three arguments: `value`, `result`, and `command`. These encapsulate the `value` provided in the `
|
179
|
+
Every handler in the result matcher block is given three arguments: `value`, `result`, and `command`. These encapsulate the `value` provided in the `ok` or `return err` call from the `Command`, the returned `Slayer::Result` object, and the `Slayer::Command` instance that was just run:
|
182
180
|
|
183
181
|
```ruby
|
184
182
|
class NoArgCommand < Slayer::Command
|
185
183
|
def call
|
186
184
|
@instance_var = 'instance'
|
187
|
-
|
185
|
+
ok value: 'pass'
|
188
186
|
end
|
189
187
|
end
|
190
188
|
|
@@ -192,42 +190,39 @@ end
|
|
192
190
|
NoArgCommand.call do |m|
|
193
191
|
m.all do |value, result, command|
|
194
192
|
puts value # => 'pass'
|
195
|
-
puts result.
|
193
|
+
puts result.ok? # => true
|
196
194
|
puts command.instance_var # => 'instance'
|
197
195
|
end
|
198
|
-
|
196
|
+
end
|
199
197
|
```
|
200
198
|
|
201
199
|
#### Statuses
|
202
200
|
|
203
|
-
You can pass a `status` flag to both the `
|
201
|
+
You can pass a `status` flag to both the `ok` and `return err` methods that allows the result matcher to process different kinds of successes and failures differently:
|
204
202
|
|
205
203
|
```ruby
|
206
204
|
class StatusCommand < Slayer::Command
|
207
205
|
def call
|
208
|
-
|
209
|
-
|
210
|
-
|
206
|
+
return err message: "Extra specific ko", status: :extra_specific_err if extra_specific_err?
|
207
|
+
return err message: "Specific ko", status: :specific_err if specific_err?
|
208
|
+
return err message: "Generic ko" if generic_err?
|
209
|
+
|
210
|
+
return ok message: "Specific pass", status: :specific_pass if specific_pass?
|
211
211
|
|
212
|
-
|
213
|
-
pass! message: "Specific pass", status: :specific_pass
|
212
|
+
ok message: "Generic pass"
|
214
213
|
end
|
215
214
|
end
|
216
215
|
|
217
216
|
StatusCommand.call do |m|
|
218
|
-
m.
|
219
|
-
m.
|
220
|
-
m.
|
217
|
+
m.err { puts "generic err" }
|
218
|
+
m.err(:specific_err) { puts "specific err" }
|
219
|
+
m.err(:extra_specific_err) { puts "extra specific err" }
|
221
220
|
|
222
|
-
m.
|
223
|
-
m.
|
221
|
+
m.ok { puts "generic pass" }
|
222
|
+
m.ok(:specific_pass) { puts "specific pass" }
|
224
223
|
end
|
225
224
|
```
|
226
225
|
|
227
|
-
### Forms
|
228
|
-
|
229
|
-
### Services
|
230
|
-
|
231
226
|
## RSpec & Minitest Integrations
|
232
227
|
|
233
228
|
`Slayer` provides assertions and matchers that make testing your `Commands` simpler.
|
@@ -283,12 +278,12 @@ class MinitestCommandTest < Minitest::Test
|
|
283
278
|
@failed_result = MinitestCommand.call(should_pass: false)
|
284
279
|
end
|
285
280
|
|
286
|
-
def
|
281
|
+
def test_is_ok
|
287
282
|
assert_success @success_result, status: :no_status, message: 'message', value: 'value'
|
288
283
|
refute_failed @success_result, status: :no_status, message: 'message', value: 'value'
|
289
284
|
end
|
290
285
|
|
291
|
-
def
|
286
|
+
def test_is_err
|
292
287
|
assert_failed @failed_result, status: :no_status, message: 'message', value: 'value'
|
293
288
|
refute_success @failed_result, status: :no_status, message: 'message', value: 'value'
|
294
289
|
end
|
@@ -366,12 +361,29 @@ end
|
|
366
361
|
|
367
362
|
### Generators
|
368
363
|
|
369
|
-
Use generators to make sure your `Slayer` objects are always in the right place. `slayer_rails` includes generators for `Slayer::Form
|
364
|
+
Use generators to make sure your `Slayer` objects are always in the right place. `slayer_rails` includes generators for `Slayer::Form` and `Slayer::Command`.
|
370
365
|
|
371
366
|
```sh
|
372
367
|
$ bin/rails g slayer:form foo_form
|
373
368
|
$ bin/rails g slayer:command foo_command
|
374
|
-
|
369
|
+
```
|
370
|
+
|
371
|
+
## Compatability
|
372
|
+
|
373
|
+
Backwards compatability with previous versions requires additional includes.
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
require 'slayer/compat/compat_040'
|
377
|
+
```
|
378
|
+
|
379
|
+
If you use test matchers, you will have to separately require the compatability layer for your test runner:
|
380
|
+
|
381
|
+
```ruby
|
382
|
+
require 'slayer/compat/minitest_compat_040'
|
383
|
+
|
384
|
+
# OR
|
385
|
+
|
386
|
+
require 'slayer/compat/rspec_compat_040'
|
375
387
|
```
|
376
388
|
|
377
389
|
## Development
|
@@ -397,3 +409,11 @@ Any PRs should be accompanied with documentation in `README.md`, and changes doc
|
|
397
409
|
## License
|
398
410
|
|
399
411
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
412
|
+
|
413
|
+
---
|
414
|
+
|
415
|
+
# Built by Apsis
|
416
|
+
|
417
|
+
[![apsis](https://s3-us-west-2.amazonaws.com/apsiscdn/apsis.png)](https://www.apsis.io)
|
418
|
+
|
419
|
+
`slayer` was built by Apsis Labs. We love sharing what we build! Check out our [other libraries on Github](https://github.com/apsislabs), and if you like our work you can [hire us](https://www.apsis.io/work-with-us/) to build your vision.
|
data/Rakefile
CHANGED
@@ -3,15 +3,4 @@ require 'rspec/core/rake_task'
|
|
3
3
|
|
4
4
|
RSpec::Core::RakeTask.new(:spec)
|
5
5
|
|
6
|
-
if defined? Chandler
|
7
|
-
# Set Chandler options
|
8
|
-
Chandler::Tasks.configure do |config|
|
9
|
-
config.changelog_path = 'CHANGELOG.md'
|
10
|
-
config.github_repository = 'apsislabs/slayer'
|
11
|
-
end
|
12
|
-
|
13
|
-
# Add chandler as a prerequisite for `rake release`
|
14
|
-
task 'release:rubygem_push' => 'chandler:push'
|
15
|
-
end
|
16
|
-
|
17
6
|
task default: :spec
|
data/lib/slayer/command.rb
CHANGED
@@ -1,21 +1,78 @@
|
|
1
1
|
module Slayer
|
2
|
-
class Command
|
3
|
-
singleton_skip_hook :call
|
4
|
-
|
2
|
+
class Command
|
5
3
|
class << self
|
6
4
|
def call(*args, &block)
|
7
|
-
self.new
|
5
|
+
instance = self.new
|
6
|
+
|
7
|
+
begin
|
8
|
+
res = instance.call(*args, &block)
|
9
|
+
rescue ResultFailureError => e
|
10
|
+
res = e.result
|
11
|
+
end
|
12
|
+
|
13
|
+
raise CommandNotImplementedError unless res.is_a? Result
|
14
|
+
|
15
|
+
handle_match(res, instance, block) if block_given?
|
16
|
+
return res
|
17
|
+
end
|
18
|
+
ruby2_keywords :call if respond_to?(:ruby2_keywords, true)
|
19
|
+
|
20
|
+
def ok(value: nil, status: :default, message: nil)
|
21
|
+
Result.new(value, status, message)
|
22
|
+
end
|
23
|
+
|
24
|
+
def err(value: nil, status: :default, message: nil)
|
25
|
+
ok(value: value, status: status, message: message).fail
|
26
|
+
end
|
27
|
+
|
28
|
+
def err!(value: nil, status: :default, message: nil)
|
29
|
+
warn '[DEPRECATION] `err!` is deprecated. Please use `return err` instead.'
|
30
|
+
raise ResultFailureError, err(value: value, status: status, message: message)
|
8
31
|
end
|
9
32
|
|
10
33
|
private
|
11
34
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
35
|
+
def handle_match(res, instance, block)
|
36
|
+
matcher = Slayer::ResultMatcher.new(res, instance)
|
37
|
+
|
38
|
+
block.call(matcher)
|
39
|
+
|
40
|
+
# raise error if not all defaults were handled
|
41
|
+
unless matcher.handled_defaults?
|
42
|
+
raise(ResultNotHandledError, 'The pass or fail condition of a result was not handled')
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
matcher.execute_matching_block
|
47
|
+
ensure
|
48
|
+
matcher.execute_ensure_block
|
49
|
+
end
|
16
50
|
end
|
17
51
|
end
|
18
52
|
|
53
|
+
def ok(*args)
|
54
|
+
self.class.ok(*args)
|
55
|
+
end
|
56
|
+
ruby2_keywords :ok if respond_to?(:ruby2_keywords, true)
|
57
|
+
|
58
|
+
def err(*args)
|
59
|
+
self.class.err(*args)
|
60
|
+
end
|
61
|
+
ruby2_keywords :err if respond_to?(:ruby2_keywords, true)
|
62
|
+
|
63
|
+
def err!(*args)
|
64
|
+
self.class.err!(*args)
|
65
|
+
end
|
66
|
+
ruby2_keywords :err! if respond_to?(:ruby2_keywords, true)
|
67
|
+
|
68
|
+
def try!(value: nil, status: nil, message: nil)
|
69
|
+
r = yield
|
70
|
+
err!(value: value, status: status || :default, message: message) unless r.is_a?(Result)
|
71
|
+
return r.value if r.ok?
|
72
|
+
|
73
|
+
err!(value: value || r.value, status: status || r.status, message: message || r.message)
|
74
|
+
end
|
75
|
+
|
19
76
|
def call
|
20
77
|
raise NotImplementedError, 'Commands must define method `#call`.'
|
21
78
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# :nocov:
|
2
|
+
|
3
|
+
module Slayer
|
4
|
+
class Command
|
5
|
+
class << self
|
6
|
+
def pass(value: nil, status: :default, message: nil)
|
7
|
+
warn '[DEPRECATION] `pass` is deprecated. Please use `ok` instead.'
|
8
|
+
ok(value: value, status: status, message: message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def flunk(value: nil, status: :default, message: nil)
|
12
|
+
warn '[DEPRECATION] `flunk` is deprecated. Please use `err` instead.'
|
13
|
+
err(value: value, status: status, message: message)
|
14
|
+
end
|
15
|
+
|
16
|
+
def flunk!(value: nil, status: :default, message: nil)
|
17
|
+
warn '[DEPRECATION] `flunk!` is deprecated. Please use `return err` instead.'
|
18
|
+
err!(value: value, status: status, message: message)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
alias pass ok
|
23
|
+
alias flunk err
|
24
|
+
alias flunk! err!
|
25
|
+
end
|
26
|
+
|
27
|
+
class Result
|
28
|
+
def success?
|
29
|
+
warn '[DEPRECATION] `success?` is deprecated. Please use `ok?` instead.'
|
30
|
+
ok?
|
31
|
+
end
|
32
|
+
|
33
|
+
def failure?
|
34
|
+
warn '[DEPRECATION] `failure?` is deprecated. Please use `err?` instead.'
|
35
|
+
err?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ResultMatcher
|
40
|
+
def pass(...)
|
41
|
+
warn '[DEPRECATION] `pass` is deprecated. Please use `ok` instead.'
|
42
|
+
ok(...)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fail(...)
|
46
|
+
warn '[DEPRECATION] `fail` is deprecated. Please use `err` instead.'
|
47
|
+
err(...)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# :nocov:
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubocop'
|
2
|
+
|
3
|
+
module Slayer
|
4
|
+
class CommandReturn < RuboCop::Cop::Base
|
5
|
+
def_node_search :explicit_returns, 'return'
|
6
|
+
def_node_matcher :slayer_command?, '(class (const (const nil :Slayer) :Command) _)'
|
7
|
+
def_node_matcher :is_call_to_pass?, '(send nil :pass ?)'
|
8
|
+
def_node_matcher :is_call_to_flunk?, '(send nil :flunk! ?)'
|
9
|
+
|
10
|
+
def on_def(node)
|
11
|
+
return unless node.method?(:call)
|
12
|
+
return unless in_slayer_command?(node)
|
13
|
+
|
14
|
+
explicit_returns(node) do |n|
|
15
|
+
validate_return! n.child_nodes.first, n
|
16
|
+
end
|
17
|
+
|
18
|
+
# Temporarily does not look at implicit returns
|
19
|
+
#
|
20
|
+
# implicit_returns(node) do |node|
|
21
|
+
# validate_return! node
|
22
|
+
# end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Continue traversing `node` until you get to the last expression.
|
28
|
+
# If that expression is a call to `.can_see?`, then add an offense.
|
29
|
+
def implicit_returns(_node)
|
30
|
+
raise 'Not Implemented Yet'
|
31
|
+
end
|
32
|
+
|
33
|
+
def in_slayer_command?(node)
|
34
|
+
node.ancestors.any?(&:slayer_command?)
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_return!(node, return_node = nil)
|
38
|
+
return if is_call_to_pass? node
|
39
|
+
return if is_call_to_flunk? node
|
40
|
+
|
41
|
+
add_offense(return_node || node,
|
42
|
+
message: 'call in Slayer::Command must return the result of `pass` or call `flunk!`')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/slayer/minitest.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
# :nocov:
|
2
2
|
require 'minitest/assertions'
|
3
|
-
# rubocop:disable Style/Documentation
|
4
3
|
# rubocop:disable Metrics/MethodLength
|
5
4
|
module Minitest::Assertions
|
6
|
-
def
|
7
|
-
assert result.
|
5
|
+
def assert_ok(result, status: nil, message: nil, value: nil)
|
6
|
+
assert result.ok?, 'Expected command to succeed.'
|
8
7
|
|
9
8
|
unless status.nil?
|
10
9
|
assert_equal(
|
@@ -30,10 +29,10 @@ module Minitest::Assertions
|
|
30
29
|
)
|
31
30
|
end
|
32
31
|
end
|
33
|
-
alias
|
32
|
+
alias refute_err assert_ok
|
34
33
|
|
35
|
-
def
|
36
|
-
refute result.
|
34
|
+
def refute_ok(result, status: nil, message: nil, value: nil)
|
35
|
+
refute result.ok?, 'Expected command to fail.'
|
37
36
|
|
38
37
|
unless status.nil?
|
39
38
|
refute_equal(
|
@@ -59,7 +58,7 @@ module Minitest::Assertions
|
|
59
58
|
)
|
60
59
|
end
|
61
60
|
end
|
62
|
-
alias
|
61
|
+
alias assert_err refute_ok
|
63
62
|
end
|
64
63
|
# rubocop:enable Style/Documentation
|
65
64
|
# rubocop:enable Metrics/MethodLength
|