slayer 0.3.1 → 0.4.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|