slayer 0.3.1 → 0.4.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/README.md +223 -56
- data/Rakefile +1 -1
- data/lib/slayer.rb +1 -0
- data/lib/slayer/command.rb +7 -62
- data/lib/slayer/errors.rb +3 -4
- data/lib/slayer/hook.rb +154 -0
- data/lib/slayer/result.rb +4 -4
- data/lib/slayer/result_matcher.rb +3 -3
- data/lib/slayer/rspec.rb +42 -0
- data/lib/slayer/service.rb +68 -152
- data/lib/slayer/version.rb +1 -1
- data/slayer.gemspec +7 -8
- metadata +39 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6ddcd92133adc30cc39358c00cfd4cf8494d879
|
4
|
+
data.tar.gz: b427f2b04fe18881c4448de84e06e2593a1bcece
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 578355462582a7e68883d7aa7d73f9e52f4d8870d797c63f3f75061c9ccdd445342e9f51c0348583a022403e3a7569503808952764b07816536627eca2cf5a95
|
7
|
+
data.tar.gz: 6ce3eda0fce30e75602a50fefd714d3df12baf17a89f1fdd37914c6b47120fd1488830ac4e6a8a63fbae6b8c2410a51ddce178296b56788ebba1d9543a102865
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -16,13 +16,15 @@ Slayer provides 3 base classes for organizing your business logic: `Forms`, `Com
|
|
16
16
|
|
17
17
|
### Commands
|
18
18
|
|
19
|
-
`Slayer::Commands` are the bread and butter of your application's business logic. `Commands` are where you compose services, and perform one-off business logic tasks. In our applications, we usually create a single `Command` per `Controller` endpoint.
|
19
|
+
`Slayer::Commands` are the bread and butter of your application's business logic, and a specific implementation of the `Slayer::Service` object. `Commands` are where you compose services, and perform one-off business logic tasks. In our applications, we usually create a single `Command` per `Controller` endpoint.
|
20
20
|
|
21
|
-
`Commands`
|
21
|
+
`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
|
+
|
23
|
+
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.
|
22
24
|
|
23
25
|
### Services
|
24
26
|
|
25
|
-
`
|
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.
|
26
28
|
|
27
29
|
## Installation
|
28
30
|
|
@@ -44,6 +46,224 @@ Or install it yourself as:
|
|
44
46
|
$ gem install slayer
|
45
47
|
```
|
46
48
|
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
### Commands
|
52
|
+
|
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 `success?` or `failure?`, a 'value' payload object, a 'status' value, and a user presentable `message`.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# A Command that passes when given the string "foo"
|
57
|
+
# and fails if given anything else.
|
58
|
+
class FooCommand < Slayer::Command
|
59
|
+
def call(foo:)
|
60
|
+
unless foo == "foo"
|
61
|
+
flunk! value: foo, message: "Argument must be foo!"
|
62
|
+
end
|
63
|
+
|
64
|
+
pass! value: foo
|
65
|
+
end
|
66
|
+
end
|
67
|
+
```
|
68
|
+
|
69
|
+
Handling the results of a command can be done in two ways. The primary way is through a handler block. This block is passed a handler object, which is in turn given blocks to handle different result outcomes:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
FooCommand.call(foo: "foo") do |m|
|
73
|
+
m.pass do |result|
|
74
|
+
puts "This code runs on success"
|
75
|
+
end
|
76
|
+
|
77
|
+
m.fail do |result|
|
78
|
+
puts "This code runs on failure. Message: #{result.message}"
|
79
|
+
end
|
80
|
+
|
81
|
+
m.all do
|
82
|
+
puts "This code runs on failure or success"
|
83
|
+
end
|
84
|
+
|
85
|
+
m.ensure do
|
86
|
+
puts "This code always runs after other handler blocks"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
The second is less comprehensive, but can be useful for very simple commands. The `call` method on a `Command` returns its result object, which has statuses set on itself:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
result = FooCommand.call(foo: "foo")
|
95
|
+
puts result.success? # => true
|
96
|
+
|
97
|
+
result = FooCommand.call(foo: "bar")
|
98
|
+
puts result.success? # => false
|
99
|
+
```
|
100
|
+
|
101
|
+
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.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
# commands/user_controller.rb
|
105
|
+
class CreateUserCommand < Slayer::Command
|
106
|
+
def call(create_user_form:)
|
107
|
+
unless arguments_valid?(create_user_form)
|
108
|
+
flunk! value: create_user_form, status: :arguments_invalid
|
109
|
+
end
|
110
|
+
|
111
|
+
user = nil
|
112
|
+
transaction do
|
113
|
+
user = User.create(create_user_form.attributes)
|
114
|
+
end
|
115
|
+
|
116
|
+
unless user.persisted?
|
117
|
+
flunk! message: I18n.t('user.create.error'), status: :unprocessible_entity
|
118
|
+
end
|
119
|
+
|
120
|
+
pass! value: user
|
121
|
+
end
|
122
|
+
|
123
|
+
def arguments_valid?(create_user_form)
|
124
|
+
create_user_form.kind_of?(CreateUserForm) &&
|
125
|
+
create_user_form.valid? &&
|
126
|
+
!User.exists?(email: create_user_form.email)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# controllers/user_controller.rb
|
131
|
+
class UsersController < ApplicationController
|
132
|
+
def create
|
133
|
+
@create_user_form = CreateUserForm.from_params(create_user_params)
|
134
|
+
|
135
|
+
CreateUserCommand.call(create_user_form: @create_user_form) do |m|
|
136
|
+
m.pass do |user|
|
137
|
+
auto_login(user)
|
138
|
+
redirect_to root_path, notice: t('user.create.success')
|
139
|
+
end
|
140
|
+
|
141
|
+
m.fail(:arguments_invalid) do |result|
|
142
|
+
flash[:error] = result.errors.full_messages.to_sentence
|
143
|
+
render :new, status: :unprocessible_entity
|
144
|
+
end
|
145
|
+
|
146
|
+
m.fail do |result|
|
147
|
+
flash[:error] = t('user.create.error')
|
148
|
+
render :new, status: :bad_request
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private
|
154
|
+
|
155
|
+
def required_user_params
|
156
|
+
[:first_name, :last_name, :email, :password]
|
157
|
+
end
|
158
|
+
|
159
|
+
def create_user_params
|
160
|
+
permitted_params = required_user_params << :password_confirmation
|
161
|
+
params.require(:user).permit(permitted_params)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
### Result Matcher
|
167
|
+
|
168
|
+
The result matcher is an object that is used to handle `Slayer::Result` objects based on their status.
|
169
|
+
|
170
|
+
#### Handlers: `pass`, `fail`, `all`, `ensure`
|
171
|
+
|
172
|
+
The result matcher block can take 4 types of handler blocks: `pass`, `fail`, `all`, and `ensure`. They operate as you would expect based on their names.
|
173
|
+
|
174
|
+
* The `pass` block runs if the command was successful.
|
175
|
+
* The `fail` block runs if the command was `flunked`.
|
176
|
+
* The `all` block runs on any type of result --- `pass` or `fail` --- unless the result has already been handled.
|
177
|
+
* The `ensure` block always runs after the result has been handled.
|
178
|
+
|
179
|
+
#### Handler Params
|
180
|
+
|
181
|
+
Every handler in the result matcher block is given three arguments: `value`, `result`, and `command`. These encapsulate the `value` provided in the `pass!` or `flunk!` call from the `Command`, the returned `Slayer::Result` object, and the `Slayer::Command` instance that was just run:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
class NoArgCommand < Slayer::Command
|
185
|
+
def call
|
186
|
+
@instance_var = 'instance'
|
187
|
+
pass value: 'pass'
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
NoArgCommand.call do |m|
|
193
|
+
m.all do |value, result, command|
|
194
|
+
puts value # => 'pass'
|
195
|
+
puts result.success? # => true
|
196
|
+
puts command.instance_var # => 'instance'
|
197
|
+
end
|
198
|
+
endpoint
|
199
|
+
```
|
200
|
+
|
201
|
+
#### Statuses
|
202
|
+
|
203
|
+
You can pass a `status` flag to both the `pass!` and `flunk!` methods that allows the result matcher to process different kinds of successes and failures differently:
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
class StatusCommand < Slayer::Command
|
207
|
+
def call
|
208
|
+
flunk! message: "Generic flunk"
|
209
|
+
flunk! message: "Specific flunk", status: :specific_flunk
|
210
|
+
flunk! message: "Extra specific flunk", status: :extra_specific_flunk
|
211
|
+
|
212
|
+
pass! message: "Generic pass"
|
213
|
+
pass! message: "Specific pass", status: :specific_pass
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
StatusCommand.call do |m|
|
218
|
+
m.fail { puts "generic fail" }
|
219
|
+
m.fail(:specific_flunk) { puts "specific flunk" }
|
220
|
+
m.fail(:extra_specific_flunk) { puts "extra specific flunk" }
|
221
|
+
|
222
|
+
m.pass { puts "generic pass" }
|
223
|
+
m.pass(:specific_pass) { puts "specific pass" }
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
### Forms
|
228
|
+
|
229
|
+
### Services
|
230
|
+
|
231
|
+
## RSpec & Minitest Integrations
|
232
|
+
|
233
|
+
`Slayer` provides assertions and matchers that make testing your `Commands` simpler.
|
234
|
+
|
235
|
+
### RSpec
|
236
|
+
|
237
|
+
To use with RSpec, update your `spec_helper.rb` file to include:
|
238
|
+
|
239
|
+
`require 'slayer/rspec'`
|
240
|
+
|
241
|
+
This provides you with two new matchers: `be_successful_result` and `be_failed_result`, both of which can be chained with a `with_status` expectation:
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
RSpec.describe RSpecCommand do
|
245
|
+
describe '#call' do
|
246
|
+
context 'should pass' do
|
247
|
+
subject(:result) { RSpecCommand.call(should_pass: true) }
|
248
|
+
|
249
|
+
it { is_expected.to be_success_result }
|
250
|
+
it { is_expected.not_to be_failed_result }
|
251
|
+
it { is_expected.not_to be_successful_result.with_status(:no_status) }
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'should fail' do
|
255
|
+
subject(:result) { RSpecCommand.call(should_pass: false) }
|
256
|
+
|
257
|
+
it { is_expected.to be_failed_result }
|
258
|
+
it { is_expected.not_to be_failed_result }
|
259
|
+
it { is_expected.not_to be_failed_result.with_status(:no_status) }
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
### Minitest
|
266
|
+
|
47
267
|
## Rails Integration
|
48
268
|
|
49
269
|
While Slayer is independent of any framework, we do offer a first-class integration with Ruby on Rails. To install the Rails extensions, add this line to your application's Gemfile:
|
@@ -121,58 +341,6 @@ $ bin/rails g slayer:command foo_command
|
|
121
341
|
$ bin/rails g slayer:service foo_service
|
122
342
|
```
|
123
343
|
|
124
|
-
## Usage
|
125
|
-
|
126
|
-
### Commands
|
127
|
-
|
128
|
-
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 `success?` or `failure?`, a 'value' payload object, a 'status' value, and a user presentable `message`.
|
129
|
-
|
130
|
-
```ruby
|
131
|
-
# A Command that passes when given the string "foo"
|
132
|
-
# and fails if given anything else.
|
133
|
-
class FooCommand < Slayer::Command
|
134
|
-
def call(foo:)
|
135
|
-
if foo == "foo"
|
136
|
-
pass! value: foo, message: "Passing FooCommand"
|
137
|
-
else
|
138
|
-
fail! value: foo, message: "Failing FooCommand"
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
result = FooCommand.call(foo: "foo")
|
144
|
-
result.success? # => true
|
145
|
-
|
146
|
-
result = FooCommand.call(foo: "bar")
|
147
|
-
result.success? # => false
|
148
|
-
```
|
149
|
-
|
150
|
-
### Forms
|
151
|
-
|
152
|
-
### Services
|
153
|
-
|
154
|
-
Slayer Services are objects that should implement re-usable pieces of application logic or common tasks. To prevent circular dependencies Services are required to declare which other Service classes they depend on. If a circular dependency is detected an error is raised.
|
155
|
-
|
156
|
-
In order to enforce the lack of circular dependencies, Service objects can only call other Services that are declared in their dependencies.
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
class NetworkService < Slayer::Service
|
160
|
-
def self.post()
|
161
|
-
...
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
class StripeService < Slayer::Service
|
166
|
-
dependencies NetworkService
|
167
|
-
|
168
|
-
def self.pay()
|
169
|
-
...
|
170
|
-
NetworkService.post(url: "stripe.com", body: my_payload)
|
171
|
-
...
|
172
|
-
end
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
344
|
## Development
|
177
345
|
|
178
346
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -185,7 +353,6 @@ To generate documentation run `yard`. To view undocumented files run `yard stats
|
|
185
353
|
|
186
354
|
Bug reports and pull requests are welcome on GitHub at https://github.com/apsislabs/slayer.
|
187
355
|
|
188
|
-
|
189
356
|
## License
|
190
357
|
|
191
358
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
data/lib/slayer.rb
CHANGED
data/lib/slayer/command.rb
CHANGED
@@ -1,73 +1,18 @@
|
|
1
1
|
module Slayer
|
2
|
-
class Command
|
3
|
-
|
2
|
+
class Command < Service
|
3
|
+
singleton_skip_hook :call
|
4
4
|
|
5
|
-
# Internal: Command Class Methods
|
6
5
|
class << self
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def call!(*args, &block)
|
12
|
-
execute_call(block, *args) { |c, *a| c.run!(*a) }
|
6
|
+
def method_added(name)
|
7
|
+
return unless name == :call
|
8
|
+
super(name)
|
13
9
|
end
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
def execute_call(command_block, *args)
|
18
|
-
# Run the Command and capture the result
|
19
|
-
command = self.new
|
20
|
-
result = command.tap { yield(command, *args) }.result
|
21
|
-
|
22
|
-
# Throw an exception if we don't return a result
|
23
|
-
raise CommandNotImplementedError unless result.is_a? Result
|
24
|
-
|
25
|
-
# Run the command block if one was provided
|
26
|
-
unless command_block.nil?
|
27
|
-
matcher = Slayer::ResultMatcher.new(result, command)
|
28
|
-
|
29
|
-
command_block.call(matcher)
|
30
|
-
|
31
|
-
# raise error if not all defaults were handled
|
32
|
-
unless matcher.handled_defaults?
|
33
|
-
raise(CommandResultNotHandledError, 'The pass or fail condition of a result was not handled')
|
34
|
-
end
|
35
|
-
|
36
|
-
begin
|
37
|
-
matcher.execute_matching_block
|
38
|
-
ensure
|
39
|
-
matcher.execute_ensure_block
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
return result
|
11
|
+
def call(*args, &block)
|
12
|
+
self.new.call(*args, &block)
|
44
13
|
end
|
45
|
-
end # << self
|
46
|
-
|
47
|
-
def run(*args)
|
48
|
-
call(*args)
|
49
|
-
rescue CommandFailureError
|
50
|
-
# Swallow the Command Failure
|
51
|
-
end
|
52
|
-
|
53
|
-
# Run the Command
|
54
|
-
def run!(*args)
|
55
|
-
call(*args)
|
56
|
-
end
|
57
|
-
|
58
|
-
# Fail the Command
|
59
|
-
|
60
|
-
def fail!(value: nil, status: :default, message: nil)
|
61
|
-
@result = Result.new(value, status, message)
|
62
|
-
@result.fail!
|
63
|
-
end
|
64
|
-
|
65
|
-
# Pass the Command
|
66
|
-
def pass!(value: nil, status: :default, message: nil)
|
67
|
-
@result = Result.new(value, status, message)
|
68
14
|
end
|
69
15
|
|
70
|
-
# Call the command
|
71
16
|
def call
|
72
17
|
raise NotImplementedError, 'Commands must define method `#call`.'
|
73
18
|
end
|
data/lib/slayer/errors.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Slayer
|
2
|
-
class
|
2
|
+
class ResultFailureError < StandardError
|
3
3
|
attr_reader :result
|
4
4
|
|
5
5
|
def initialize(result)
|
@@ -10,13 +10,12 @@ module Slayer
|
|
10
10
|
|
11
11
|
class CommandNotImplementedError < StandardError
|
12
12
|
def initialize(message = nil)
|
13
|
-
message ||= 'Command implementation must
|
14
|
-
'return a <Slayer::Result> object'
|
13
|
+
message ||= 'Command implementation must return a <Slayer::Result> object'
|
15
14
|
super message
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
19
|
-
class
|
18
|
+
class ResultNotHandledError < StandardError; end
|
20
19
|
class FormValidationError < StandardError; end
|
21
20
|
class ServiceDependencyError < StandardError; end
|
22
21
|
end
|
data/lib/slayer/hook.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
module Slayer
|
2
|
+
# Hook adds the ability to wrap all calls to a class in a wrapper method. The
|
3
|
+
# wrapper method is provided with a block that can be used to invoke the called
|
4
|
+
# method.
|
5
|
+
#
|
6
|
+
# The methods #skip_hook and #only_hook can be used to control which methods are
|
7
|
+
# and are not wrapped with the hook call.
|
8
|
+
#
|
9
|
+
# @example Including Hook on a class.
|
10
|
+
# class MyHookedClass
|
11
|
+
# include Hook
|
12
|
+
#
|
13
|
+
# hook :say_hello_and_goodbye
|
14
|
+
#
|
15
|
+
# def self.say_hello_and_goodbye
|
16
|
+
# puts "hello!"
|
17
|
+
#
|
18
|
+
# yield # calls hooked method
|
19
|
+
#
|
20
|
+
# puts "goodbye!"
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def self.say_something
|
24
|
+
# puts "something"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# MyHookedClass.say_something
|
29
|
+
# # => "hello!"
|
30
|
+
# # "something"
|
31
|
+
# # "goodbye!"
|
32
|
+
#
|
33
|
+
# @example Skipping Hooks
|
34
|
+
#
|
35
|
+
# skip_hook :say_something, :do_something # the hook method will not be invoked for
|
36
|
+
# # these methods. They will be called directly.
|
37
|
+
#
|
38
|
+
# @example Only hooking
|
39
|
+
#
|
40
|
+
# only_hook :see_something, :hear_something # These are the only methods that will be
|
41
|
+
# # be hooked. All other methods will be
|
42
|
+
# # called directly.
|
43
|
+
module Hook
|
44
|
+
def self.included(klass)
|
45
|
+
klass.extend ClassMethods
|
46
|
+
end
|
47
|
+
|
48
|
+
# Everything in Hook::ClassMethods automatically get extended onto the
|
49
|
+
# class that includes Hook.
|
50
|
+
module ClassMethods
|
51
|
+
# Define the method that will be invoked whenever another method is invoked.
|
52
|
+
# This should be a class method.
|
53
|
+
def hook(hook_method)
|
54
|
+
@__hook = hook_method
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define the set of methods that should always be called directly, and should
|
58
|
+
# never be hooked
|
59
|
+
def skip_hook(*hook_skips)
|
60
|
+
@__hook_skips = hook_skips
|
61
|
+
end
|
62
|
+
|
63
|
+
def singleton_skip_hook(*hook_skips)
|
64
|
+
@__singleton_hook_skips = hook_skips
|
65
|
+
end
|
66
|
+
|
67
|
+
# If only_hook is called then only the methods provided will be hooked. All
|
68
|
+
# other methods will be called directly.
|
69
|
+
def only_hook(*hook_only)
|
70
|
+
@__hook_only = hook_only
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def __hook
|
76
|
+
@__hook ||= nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def __hook_skips
|
80
|
+
@__hook_skips ||= []
|
81
|
+
end
|
82
|
+
|
83
|
+
def __singleton_hook_skips
|
84
|
+
@__singleton_hook_skips ||= []
|
85
|
+
end
|
86
|
+
|
87
|
+
def __hook_only
|
88
|
+
@__hook_only ||= nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def __current_methods
|
92
|
+
@__current_methods ||= nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def run_hook(name, instance, passed_block, &block)
|
96
|
+
if __hook
|
97
|
+
send(__hook, name, instance, passed_block, &block)
|
98
|
+
else
|
99
|
+
# rubocop:disable Performance/RedundantBlockCall
|
100
|
+
block.call
|
101
|
+
# rubocop:enable Performance/RedundantBlockCall
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def singleton_method_added(name)
|
106
|
+
insert_hook_for(name,
|
107
|
+
define_method_fn: :define_singleton_method,
|
108
|
+
hook_target: self,
|
109
|
+
alias_target: singleton_class,
|
110
|
+
skip_methods: blacklist(__singleton_hook_skips))
|
111
|
+
end
|
112
|
+
|
113
|
+
def method_added(name)
|
114
|
+
insert_hook_for(name,
|
115
|
+
define_method_fn: :define_method,
|
116
|
+
hook_target: self,
|
117
|
+
alias_target: self,
|
118
|
+
skip_methods: blacklist(__hook_skips))
|
119
|
+
end
|
120
|
+
|
121
|
+
def insert_hook_for(name, define_method_fn:, hook_target:, alias_target:, skip_methods: [])
|
122
|
+
return if __current_methods && __current_methods.include?(name)
|
123
|
+
|
124
|
+
with_hooks = :"__#{name}_with_hooks"
|
125
|
+
without_hooks = :"__#{name}_without_hooks"
|
126
|
+
|
127
|
+
return if __hook_only && !__hook_only.include?(name.to_sym)
|
128
|
+
return if skip_methods.include? name.to_sym
|
129
|
+
|
130
|
+
@__current_methods = [name, with_hooks, without_hooks]
|
131
|
+
send(define_method_fn, with_hooks) do |*args, &block|
|
132
|
+
hook_target.send(:run_hook, name, self, block) do
|
133
|
+
send(without_hooks, *args)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
alias_target.send(:alias_method, without_hooks, name)
|
138
|
+
alias_target.send(:alias_method, name, with_hooks)
|
139
|
+
|
140
|
+
@__current_methods = nil
|
141
|
+
end
|
142
|
+
|
143
|
+
def blacklist(additional = [])
|
144
|
+
list = Object.methods(false) + Object.private_methods(false)
|
145
|
+
list += Slayer::Hook::ClassMethods.private_instance_methods(false)
|
146
|
+
list += Slayer::Hook::ClassMethods.instance_methods(false)
|
147
|
+
list += additional
|
148
|
+
list << __hook if __hook
|
149
|
+
|
150
|
+
list
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/slayer/result.rb
CHANGED
@@ -30,7 +30,7 @@ module Slayer
|
|
30
30
|
#
|
31
31
|
# If the block form of a {Command.call} is invoked, both the block must handle the default
|
32
32
|
# status for both a {Result#success?} and a {Result#failure?}. If both are not handled,
|
33
|
-
# the matching block will not be invoked and a {
|
33
|
+
# the matching block will not be invoked and a {ResultNotHandledError} will be
|
34
34
|
# raised.
|
35
35
|
#
|
36
36
|
# @example Matcher invokes the matching pass block, with precedence given to {#pass} and {#fail}
|
@@ -74,7 +74,7 @@ module Slayer
|
|
74
74
|
# m.pass(:ok) { puts "Pass, OK!"}
|
75
75
|
# m.fail { puts "Fail!" }
|
76
76
|
# end
|
77
|
-
# # => raises
|
77
|
+
# # => raises ResultNotHandledError (because no default pass was provided)
|
78
78
|
#
|
79
79
|
# # Call produces a successful Result with status :ok
|
80
80
|
# SuccessCommand.call do |m|
|
@@ -94,7 +94,7 @@ module Slayer
|
|
94
94
|
# SuccessCommand.call do |m|
|
95
95
|
# m.pass(:ok, :default) { puts "Pass, OK!"}
|
96
96
|
# end
|
97
|
-
# # => raises
|
97
|
+
# # => raises ResultNotHandledError (because no default fail was provided)
|
98
98
|
class ResultMatcher
|
99
99
|
attr_reader :result, :command
|
100
100
|
|
data/lib/slayer/rspec.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
|
3
|
+
RSpec::Matchers.define :be_success_result do
|
4
|
+
match do |result|
|
5
|
+
result.success?
|
6
|
+
end
|
7
|
+
|
8
|
+
chain :with_status do |status|
|
9
|
+
@status = status
|
10
|
+
end
|
11
|
+
|
12
|
+
failure_message do |result|
|
13
|
+
return 'expected command to succeed' if @status.nil?
|
14
|
+
return "expected command to succeed with status :#{@status}, but got :#{result.status}"
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_when_negated do |result|
|
18
|
+
return "expected command not to have status :#{@status}" if @status.present? && result.status == @status
|
19
|
+
return 'expected command to fail'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
RSpec::Matchers.define :be_failed_result do
|
24
|
+
match do |result|
|
25
|
+
return result.failure? if @status.nil?
|
26
|
+
return result.failure? && (result.status == @status)
|
27
|
+
end
|
28
|
+
|
29
|
+
chain :with_status do |status|
|
30
|
+
@status = status
|
31
|
+
end
|
32
|
+
|
33
|
+
failure_message do |result|
|
34
|
+
return 'expected command to fail' if @status.nil?
|
35
|
+
return "expected command to fail with status :#{@status}, but got :#{result.status}"
|
36
|
+
end
|
37
|
+
|
38
|
+
failure_message_when_negated do |result|
|
39
|
+
return "expected command not to have status :#{@status}" if @status.present? && result.status == @status
|
40
|
+
return 'expected command to succeed'
|
41
|
+
end
|
42
|
+
end
|
data/lib/slayer/service.rb
CHANGED
@@ -1,182 +1,98 @@
|
|
1
1
|
module Slayer
|
2
2
|
# Slayer Services are objects that should implement re-usable pieces of
|
3
|
-
# application logic or common tasks.
|
4
|
-
#
|
5
|
-
# circular dependency is detected an error is raised.
|
6
|
-
#
|
7
|
-
# In order to enforce the lack of circular dependencies, Service objects can
|
8
|
-
# only call other Services that are declared in their dependencies.
|
3
|
+
# application logic or common tasks. All methods in a service are wrapped
|
4
|
+
# by default to enforce the return of a +Slayer::Result+ object.
|
9
5
|
class Service
|
10
|
-
|
11
|
-
# dependencies that are included in this call my be invoked from class
|
12
|
-
# or instances methods of this service class.
|
13
|
-
#
|
14
|
-
# If no dependencies are provided, no other Service classes may be used by
|
15
|
-
# this Service class.
|
16
|
-
#
|
17
|
-
# @param deps [Array<Class>] An array of the other Slayer::Service classes that are used as dependencies
|
18
|
-
#
|
19
|
-
# @example Service calls with dependency declared
|
20
|
-
# class StripeService < Slayer::Service
|
21
|
-
# dependencies NetworkService
|
22
|
-
#
|
23
|
-
# def self.pay()
|
24
|
-
# ...
|
25
|
-
# NetworkService.post(url: "stripe.com", body: my_payload) # OK
|
26
|
-
# ...
|
27
|
-
# end
|
28
|
-
# end
|
29
|
-
#
|
30
|
-
# @example Service calls without a dependency declared
|
31
|
-
# class JiraApiService < Slayer::Service
|
32
|
-
#
|
33
|
-
# def self.create_issue()
|
34
|
-
# ...
|
35
|
-
# NetworkService.post(url: "stripe.com", body: my_payload) # Raises Slayer::ServiceDependencyError
|
36
|
-
# ...
|
37
|
-
# end
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# @return [Array<Class>] The transitive closure of dependencies for this object.
|
41
|
-
def self.dependencies(*deps)
|
42
|
-
raise(ServiceDependencyError, "There were multiple dependencies calls of #{self}") if @deps
|
43
|
-
|
44
|
-
deps.each do |dep|
|
45
|
-
unless dep.is_a?(Class)
|
46
|
-
raise(ServiceDependencyError, "The object #{dep} passed to dependencies service was not a class")
|
47
|
-
end
|
48
|
-
|
49
|
-
unless dep < Slayer::Service
|
50
|
-
raise(ServiceDependencyError, "The object #{dep} passed to dependencies was not a subclass of #{self}")
|
51
|
-
end
|
52
|
-
end
|
6
|
+
include Hook
|
53
7
|
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
@deps = deps
|
8
|
+
skip_hook :pass, :flunk, :flunk!, :try!
|
9
|
+
singleton_skip_hook :pass, :flunk, :flunk!, :try!
|
59
10
|
|
60
|
-
|
61
|
-
transitive_dependencies
|
62
|
-
end
|
11
|
+
attr_accessor :result
|
63
12
|
|
64
13
|
class << self
|
14
|
+
# Create a passing Result
|
15
|
+
def pass(value: nil, status: :default, message: nil)
|
16
|
+
Result.new(value, status, message)
|
17
|
+
end
|
65
18
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@deps ||= []
|
19
|
+
# Create a failing Result
|
20
|
+
def flunk(value: nil, status: :default, message: nil)
|
21
|
+
Result.new(value, status, message).fail
|
22
|
+
end
|
72
23
|
|
73
|
-
|
74
|
-
|
75
|
-
|
24
|
+
# Create a failing Result and halt execution of the Command
|
25
|
+
def flunk!(value: nil, status: :default, message: nil)
|
26
|
+
raise ResultFailureError, flunk(value: value, status: status, message: message)
|
27
|
+
end
|
76
28
|
|
77
|
-
|
78
|
-
|
29
|
+
# If the block produces a successful result the value of the result will be
|
30
|
+
# returned. Otherwise, this will create a failing result and halt the execution
|
31
|
+
# of the Command.
|
32
|
+
def try!(value: nil, status: nil, message: nil)
|
33
|
+
r = yield
|
34
|
+
flunk!(value: value, status: status || :default, message: message) unless r.is_a?(Result)
|
35
|
+
return r.value if r.success?
|
36
|
+
flunk!(value: value || r.value, status: status || r.status, message: message || r.message)
|
37
|
+
end
|
38
|
+
end
|
79
39
|
|
80
|
-
|
81
|
-
|
40
|
+
def pass(*args)
|
41
|
+
self.class.pass(*args)
|
42
|
+
end
|
82
43
|
|
83
|
-
|
84
|
-
|
44
|
+
def flunk(*args)
|
45
|
+
self.class.flunk(*args)
|
46
|
+
end
|
85
47
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
48
|
+
def flunk!(*args)
|
49
|
+
self.class.flunk!(*args)
|
50
|
+
end
|
90
51
|
|
91
|
-
|
92
|
-
|
52
|
+
def try!(*args, &block)
|
53
|
+
self.class.try!(*args, &block)
|
54
|
+
end
|
93
55
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
56
|
+
# Make sure child classes also hook correctly
|
57
|
+
def self.inherited(klass)
|
58
|
+
klass.include Hook
|
59
|
+
klass.hook :__service_hook
|
60
|
+
end
|
98
61
|
|
99
|
-
|
100
|
-
@transitive_dependencies = dependency_hash[self]
|
62
|
+
hook :__service_hook
|
101
63
|
|
102
|
-
|
64
|
+
# rubocop:disable Metrics/MethodLength
|
65
|
+
def self.__service_hook(_, instance, service_block)
|
66
|
+
begin
|
67
|
+
result = yield
|
68
|
+
rescue ResultFailureError => error
|
69
|
+
result = error.result
|
103
70
|
end
|
104
71
|
|
105
|
-
|
106
|
-
@deps ||= []
|
107
|
-
@@allowed_services ||= nil
|
72
|
+
raise CommandNotImplementedError unless result.is_a? Result
|
108
73
|
|
109
|
-
|
110
|
-
|
74
|
+
unless service_block.nil?
|
75
|
+
matcher = Slayer::ResultMatcher.new(result, instance)
|
111
76
|
|
112
|
-
|
113
|
-
@@allowed_services << (@deps + [self])
|
114
|
-
end
|
77
|
+
service_block.call(matcher)
|
115
78
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
if !allowed || !allowed.include?(self)
|
120
|
-
raise(ServiceDependencyError, "Attempted to call #{self} from another #{Slayer::Service}"\
|
121
|
-
' which did not declare it as a dependency')
|
122
|
-
end
|
79
|
+
# raise error if not all defaults were handled
|
80
|
+
unless matcher.handled_defaults?
|
81
|
+
raise(ResultNotHandledError, 'The pass or fail condition of a result was not handled')
|
123
82
|
end
|
124
|
-
end
|
125
83
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
def singleton_method_added(name)
|
132
|
-
return if self == Slayer::Service
|
133
|
-
return if @__last_methods_added && @__last_methods_added.include?(name)
|
134
|
-
|
135
|
-
with = :"#{name}_with_before_each_method"
|
136
|
-
without = :"#{name}_without_before_each_method"
|
137
|
-
|
138
|
-
@__last_methods_added = [name, with, without]
|
139
|
-
define_singleton_method with do |*args, &block|
|
140
|
-
before_each_method name
|
141
|
-
begin
|
142
|
-
send without, *args, &block
|
143
|
-
rescue
|
144
|
-
raise
|
145
|
-
ensure
|
146
|
-
after_each_method name
|
147
|
-
end
|
84
|
+
begin
|
85
|
+
matcher.execute_matching_block
|
86
|
+
ensure
|
87
|
+
matcher.execute_ensure_block
|
148
88
|
end
|
149
|
-
|
150
|
-
singleton_class.send(:alias_method, without, name)
|
151
|
-
singleton_class.send(:alias_method, name, with)
|
152
|
-
|
153
|
-
@__last_methods_added = nil
|
154
89
|
end
|
90
|
+
return result
|
91
|
+
end
|
92
|
+
# rubocop:enable Metrics/MethodLength
|
155
93
|
|
156
|
-
|
157
|
-
|
158
|
-
return if @__last_methods_added && @__last_methods_added.include?(name)
|
159
|
-
|
160
|
-
with = :"#{name}_with_before_each_method"
|
161
|
-
without = :"#{name}_without_before_each_method"
|
162
|
-
|
163
|
-
@__last_methods_added = [name, with, without]
|
164
|
-
define_method with do |*args, &block|
|
165
|
-
self.class.before_each_method name
|
166
|
-
begin
|
167
|
-
send without, *args, &block
|
168
|
-
rescue
|
169
|
-
raise
|
170
|
-
ensure
|
171
|
-
self.class.after_each_method name
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
alias_method without, name
|
176
|
-
alias_method name, with
|
94
|
+
private_class_method :inherited
|
95
|
+
private_class_method :__service_hook
|
177
96
|
|
178
|
-
@__last_methods_added = nil
|
179
|
-
end
|
180
|
-
end # << self
|
181
97
|
end # class Service
|
182
98
|
end # module Slayer
|
data/lib/slayer/version.rb
CHANGED
data/slayer.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'slayer/version'
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ['Wyatt Kirby', 'Noah Callaway']
|
10
10
|
spec.email = ['wyatt@apsis.io', 'noah@apsis.io']
|
11
11
|
|
12
|
-
spec.summary =
|
12
|
+
spec.summary = 'A killer service layer'
|
13
13
|
spec.homepage = 'http://www.apsis.io'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
@@ -19,16 +19,15 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_dependency 'virtus', '~> 1.0'
|
22
|
-
spec.add_dependency 'dry-validation', '~> 0.10'
|
23
22
|
|
24
|
-
spec.add_development_dependency 'coveralls'
|
25
|
-
spec.add_development_dependency 'simplecov', '~> 0.13'
|
26
23
|
spec.add_development_dependency 'bundler', '~> 1.12'
|
27
|
-
spec.add_development_dependency '
|
24
|
+
spec.add_development_dependency 'byebug', '~> 9.0'
|
25
|
+
spec.add_development_dependency 'coveralls'
|
28
26
|
spec.add_development_dependency 'minitest', '~> 5.0'
|
29
27
|
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
30
28
|
spec.add_development_dependency 'mocha', '~> 1.2'
|
31
|
-
spec.add_development_dependency '
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.48.1'
|
30
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
31
|
+
spec.add_development_dependency 'simplecov', '~> 0.13'
|
32
32
|
spec.add_development_dependency 'yard', '~> 0.9'
|
33
|
-
spec.add_development_dependency 'rubocop', '~> 0.47.1'
|
34
33
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slayer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0.beta2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wyatt Kirby
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-05-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: virtus
|
@@ -26,131 +26,131 @@ dependencies:
|
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '1.0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: bundler
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version: '
|
35
|
-
type: :
|
34
|
+
version: '1.12'
|
35
|
+
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
39
|
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
41
|
+
version: '1.12'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: byebug
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- - "
|
46
|
+
- - "~>"
|
47
47
|
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
48
|
+
version: '9.0'
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- - "
|
53
|
+
- - "~>"
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
55
|
+
version: '9.0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
57
|
+
name: coveralls
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
|
-
- - "
|
60
|
+
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '0
|
62
|
+
version: '0'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- - "
|
67
|
+
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '0
|
69
|
+
version: '0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
|
-
name:
|
71
|
+
name: minitest
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
74
|
- - "~>"
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: '
|
76
|
+
version: '5.0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: '
|
83
|
+
version: '5.0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
|
-
name:
|
85
|
+
name: minitest-reporters
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - "~>"
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: '
|
90
|
+
version: '1.1'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
97
|
+
version: '1.1'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
99
|
+
name: mocha
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
101
101
|
requirements:
|
102
102
|
- - "~>"
|
103
103
|
- !ruby/object:Gem::Version
|
104
|
-
version: '
|
104
|
+
version: '1.2'
|
105
105
|
type: :development
|
106
106
|
prerelease: false
|
107
107
|
version_requirements: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - "~>"
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
111
|
+
version: '1.2'
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
|
-
name:
|
113
|
+
name: rubocop
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
115
115
|
requirements:
|
116
116
|
- - "~>"
|
117
117
|
- !ruby/object:Gem::Version
|
118
|
-
version:
|
118
|
+
version: 0.48.1
|
119
119
|
type: :development
|
120
120
|
prerelease: false
|
121
121
|
version_requirements: !ruby/object:Gem::Requirement
|
122
122
|
requirements:
|
123
123
|
- - "~>"
|
124
124
|
- !ruby/object:Gem::Version
|
125
|
-
version:
|
125
|
+
version: 0.48.1
|
126
126
|
- !ruby/object:Gem::Dependency
|
127
|
-
name:
|
127
|
+
name: rake
|
128
128
|
requirement: !ruby/object:Gem::Requirement
|
129
129
|
requirements:
|
130
130
|
- - "~>"
|
131
131
|
- !ruby/object:Gem::Version
|
132
|
-
version: '
|
132
|
+
version: '10.0'
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
135
|
version_requirements: !ruby/object:Gem::Requirement
|
136
136
|
requirements:
|
137
137
|
- - "~>"
|
138
138
|
- !ruby/object:Gem::Version
|
139
|
-
version: '
|
139
|
+
version: '10.0'
|
140
140
|
- !ruby/object:Gem::Dependency
|
141
|
-
name:
|
141
|
+
name: simplecov
|
142
142
|
requirement: !ruby/object:Gem::Requirement
|
143
143
|
requirements:
|
144
144
|
- - "~>"
|
145
145
|
- !ruby/object:Gem::Version
|
146
|
-
version: '
|
146
|
+
version: '0.13'
|
147
147
|
type: :development
|
148
148
|
prerelease: false
|
149
149
|
version_requirements: !ruby/object:Gem::Requirement
|
150
150
|
requirements:
|
151
151
|
- - "~>"
|
152
152
|
- !ruby/object:Gem::Version
|
153
|
-
version: '
|
153
|
+
version: '0.13'
|
154
154
|
- !ruby/object:Gem::Dependency
|
155
155
|
name: yard
|
156
156
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,20 +165,6 @@ dependencies:
|
|
165
165
|
- - "~>"
|
166
166
|
- !ruby/object:Gem::Version
|
167
167
|
version: '0.9'
|
168
|
-
- !ruby/object:Gem::Dependency
|
169
|
-
name: rubocop
|
170
|
-
requirement: !ruby/object:Gem::Requirement
|
171
|
-
requirements:
|
172
|
-
- - "~>"
|
173
|
-
- !ruby/object:Gem::Version
|
174
|
-
version: 0.47.1
|
175
|
-
type: :development
|
176
|
-
prerelease: false
|
177
|
-
version_requirements: !ruby/object:Gem::Requirement
|
178
|
-
requirements:
|
179
|
-
- - "~>"
|
180
|
-
- !ruby/object:Gem::Version
|
181
|
-
version: 0.47.1
|
182
168
|
description:
|
183
169
|
email:
|
184
170
|
- wyatt@apsis.io
|
@@ -205,8 +191,10 @@ files:
|
|
205
191
|
- lib/slayer/command.rb
|
206
192
|
- lib/slayer/errors.rb
|
207
193
|
- lib/slayer/form.rb
|
194
|
+
- lib/slayer/hook.rb
|
208
195
|
- lib/slayer/result.rb
|
209
196
|
- lib/slayer/result_matcher.rb
|
197
|
+
- lib/slayer/rspec.rb
|
210
198
|
- lib/slayer/service.rb
|
211
199
|
- lib/slayer/version.rb
|
212
200
|
- slayer.gemspec
|
@@ -226,12 +214,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
226
214
|
version: '0'
|
227
215
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
228
216
|
requirements:
|
229
|
-
- - "
|
217
|
+
- - ">"
|
230
218
|
- !ruby/object:Gem::Version
|
231
|
-
version:
|
219
|
+
version: 1.3.1
|
232
220
|
requirements: []
|
233
221
|
rubyforge_project:
|
234
|
-
rubygems_version: 2.6.
|
222
|
+
rubygems_version: 2.6.13
|
235
223
|
signing_key:
|
236
224
|
specification_version: 4
|
237
225
|
summary: A killer service layer
|