skywalker 2.1.0 → 2.2.0
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/CHANGELOG.md +16 -0
- data/README.md +201 -68
- data/examples/Gemfile.lock +4 -23
- data/lib/skywalker/callable.rb +30 -0
- data/lib/skywalker/command.rb +4 -107
- data/lib/skywalker/transactional.rb +127 -0
- data/lib/skywalker/version.rb +1 -1
- data/skywalker.gemspec +0 -2
- data/spec/lib/skywalker/acceptable_spec.rb +1 -1
- data/spec/lib/skywalker/callable_spec.rb +23 -0
- data/spec/lib/skywalker/command_spec.rb +0 -130
- data/spec/lib/skywalker/transactional_spec.rb +163 -0
- metadata +9 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ff0a320a2103cf8829980f9ef745f34ea00ebc6
|
4
|
+
data.tar.gz: 4c04009e7c751282cd1f6b0d88d0e0e046486276
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d74caa0e24ad0681fdf6133eecf290967059090c09892932c3450aa04f1c97da3e770bfa36eda60fc31a8d4b628f4131253fe4e3a38061086312ab03762e711f
|
7
|
+
data.tar.gz: a381af6335e0eb034047bec3877434271be6dd1e6d32a19cb40974e0c37eabd723f8c7c6ec5c8b37549293afca5691adeef6d0f5580bda3e73ba5fcf3e897bd8
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
All commits by Rob Yurkowski unless otherwise noted.
|
2
2
|
|
3
|
+
## 2.2.0 (2016-06-21)
|
4
|
+
|
5
|
+
- Extracts command behaviour to `Skywalker::Callable` and
|
6
|
+
`Skywalker::Transactional`.
|
7
|
+
|
8
|
+
This simplifies the `Command` object, making it basically a combination of
|
9
|
+
`Callable` and `Transactional` mixins. It also allows for the reuse of the
|
10
|
+
constituent parts on a larger scale.
|
11
|
+
|
12
|
+
- Remove dependency on `ActiveRecord`.
|
13
|
+
|
14
|
+
We now do a check to see if ActiveRecord is defined. If not, we simply default
|
15
|
+
to calling the passed block. This allows us to avoid having to require
|
16
|
+
ActiveRecord, which lightens our dependencies and also makes us feel just a
|
17
|
+
tiny bit less dirty.
|
18
|
+
|
3
19
|
## 2.1.0 (2015-05-14)
|
4
20
|
|
5
21
|
- Yields self to any block given to any object implementing `Skywalker::Acceptable`.
|
data/README.md
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
# Skywalker
|
2
2
|
|
3
|
-
Skywalker is a gem that provides a simple command pattern for applications that
|
3
|
+
Skywalker is a gem that provides a simple command pattern for applications that
|
4
|
+
use transactions. (Or not! In later versions, Skywalker is much more modular and
|
5
|
+
can be [used for non-transactional purposes](#components), too.)
|
4
6
|
|
5
7
|
## Why Skywalker?
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
Because "Commander Skywalker".
|
10
|
+
|
11
|
+
It's impossible to come up with a single-word name for a gem about commands
|
12
|
+
that's at least marginally witty. If you can't achieve wit or cleverness, at
|
13
|
+
least achieve topicality, right?
|
9
14
|
|
10
15
|
## What is a command?
|
11
16
|
|
@@ -17,40 +22,47 @@ considering transactional blocks as objects:
|
|
17
22
|
|
18
23
|
### Testability
|
19
24
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
Skywalker places a strong emphasis on dependency injection.
|
26
|
+
|
27
|
+
This means that you can unit test the command for correctness without having to
|
28
|
+
do a full integration test for every single path through the code. That makes
|
29
|
+
your test suite lean and mean, and encourages you to aim for weaker forms of
|
30
|
+
coupling (i.e. preferring connascence of name, rather than identity).
|
31
|
+
|
32
|
+
Best practice is to describe the operations in methods, which can then be
|
33
|
+
stubbed out to test small portions in isolation.
|
24
34
|
|
25
|
-
This also allows you to make the reasonable inference that the command will
|
26
|
-
properly if one step raises an error, and by convention, the same method
|
27
|
-
will be called. In most cases, you can thereby verify happy path
|
28
|
-
through integration specs, and that will suffice.
|
35
|
+
This also allows you to make the reasonable inference that the command will
|
36
|
+
abort properly if one step raises an error, and by convention, the same method
|
37
|
+
(`on_failure`) will be called. In most cases, you can thereby verify happy path
|
38
|
+
and a single bad path through integration specs, and that will suffice.
|
29
39
|
|
30
40
|
### Reasonability
|
31
41
|
|
32
|
-
The benefit of abstraction means that you can easily reason about a command
|
33
|
-
having to know its internals. Standard caveats apply, but if you have a
|
34
|
-
command, you should be able to infer that calling the command with
|
35
|
-
will produce the expected result
|
42
|
+
The benefit of abstraction means that you can easily reason about a command
|
43
|
+
without having to know its internals. Standard caveats apply, but if you have a
|
44
|
+
`CreateGroup` command, you should be able to infer that calling the command with
|
45
|
+
the correct arguments will produce the expected result, rather than having to
|
46
|
+
remember all the side effects of an operation.
|
36
47
|
|
37
48
|
### Knowledge of Results Without Knowledge of Response
|
38
49
|
|
39
|
-
A command prescriptively takes callbacks or `#call`able objects, which can be
|
40
|
-
depending on the result of the command. By default, `Skywalker::Command`
|
41
|
-
an `on_success` and an `on_failure` callback, which are called after
|
42
|
-
results. You can define these in your controllers, which lets
|
43
|
-
but respond in unique ways, and keeps controller
|
50
|
+
A command prescriptively takes callbacks or `#call`able objects, which can be
|
51
|
+
called depending on the result of the command. By default, `Skywalker::Command`
|
52
|
+
can handle an `on_success` and an `on_failure` callback, which are called after
|
53
|
+
their respective results. You can define these in your controllers, which lets
|
54
|
+
you run the same command but respond in unique ways, and keeps controller
|
55
|
+
concerns inside the controller.
|
44
56
|
|
45
|
-
You can also easily override which callbacks are run. Need to run a different
|
46
|
-
if `request.xhr?`? Simply override `run_success_callbacks` and
|
47
|
-
and call your own.
|
57
|
+
You can also easily override which callbacks are run. Need to run a different
|
58
|
+
callback if `request.xhr?`? Simply override `run_success_callbacks` and
|
59
|
+
`run_failure_callbacks` and call your own.
|
48
60
|
|
49
61
|
### A Gateway to Harder Architectures
|
50
62
|
|
51
|
-
It's not hard to create an `Event` class and step up toward full event sourcing,
|
52
|
-
go a bit further and implement full CQRS. This is the architectural
|
53
|
-
warned you about.
|
63
|
+
It's not hard to create an `Event` class and step up toward full event sourcing,
|
64
|
+
or to go a bit further and implement full CQRS. This is the architectural
|
65
|
+
pattern your parents warned you about.
|
54
66
|
|
55
67
|
## Installation
|
56
68
|
|
@@ -70,11 +82,11 @@ Or install it yourself as:
|
|
70
82
|
|
71
83
|
## Usage
|
72
84
|
|
73
|
-
Let's talk about a situation where you're creating a group and sending an email
|
74
|
-
Rails app.
|
85
|
+
Let's talk about a situation where you're creating a group and sending an email
|
86
|
+
inside a Rails app.
|
75
87
|
|
76
|
-
Standard operating procedure usually falls into one of two patterns, both of
|
77
|
-
mediocre. The first makes use of ActiveRecord callbacks:
|
88
|
+
Standard operating procedure usually falls into one of two patterns, both of
|
89
|
+
which are mediocre. The first makes use of ActiveRecord callbacks:
|
78
90
|
|
79
91
|
```ruby
|
80
92
|
# app/controllers/groups_controller.rb
|
@@ -106,14 +118,14 @@ class Group < ActiveRecord::Base
|
|
106
118
|
end
|
107
119
|
```
|
108
120
|
|
109
|
-
This might seem concise because it keeps the controller small. (Fat model,
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
121
|
+
This might seem concise because it keeps the controller small. (Fat model, thin
|
122
|
+
controller has been a plank of Rails development for a while, but it's slowly
|
123
|
+
going away, thank heavens). But there are two problems here: first, it
|
124
|
+
introduces a point of coupling between the model and the mailer, which not only
|
125
|
+
makes testing slower, it means that these two objects are now entwined. Create a
|
126
|
+
group through the Rails console? You're sending an email with no way to skip
|
127
|
+
that. Secondly, it reduces the reasonability of the code. When you look at the
|
128
|
+
`GroupsController`, you can't suss out the fact that this sends an email.
|
117
129
|
|
118
130
|
**Moral #1: Orthogonal concerns should not be put into ActiveRecord callbacks.**
|
119
131
|
|
@@ -138,21 +150,24 @@ class GroupsController < ApplicationController
|
|
138
150
|
end
|
139
151
|
```
|
140
152
|
|
141
|
-
This is more reasonable, but it's longer in the controller and at some point
|
142
|
-
begin to glaze over. Imagine as these orthogonal concerns grow longer
|
143
|
-
you're sending a tweet about the group, scheduling a
|
144
|
-
or hitting a webhook URL. You're
|
153
|
+
This is more reasonable, but it's longer in the controller and at some point
|
154
|
+
your eyes begin to glaze over. Imagine as these orthogonal concerns grow longer
|
155
|
+
and longer. Maybe you're sending a tweet about the group, scheduling a
|
156
|
+
background job to update some thumbnails, or hitting a webhook URL. You're
|
157
|
+
losing the reasonability of the code because of the detail.
|
145
158
|
|
146
|
-
Moreover, imagine that the group email being sent contains critical instructions
|
147
|
-
to proceed. What if `NotificationMailer` has a syntax error? The group is
|
148
|
-
mail won't be sent. Now the user hasn't gotten a good error,
|
149
|
-
fouled up by half-performed requests. You can
|
150
|
-
|
159
|
+
Moreover, imagine that the group email being sent contains critical instructions
|
160
|
+
on how to proceed. What if `NotificationMailer` has a syntax error? The group is
|
161
|
+
created, but the mail won't be sent. Now the user hasn't gotten a good error,
|
162
|
+
and your database is potentially fouled up by half-performed requests. You can
|
163
|
+
run this in a transaction, but that does not reduce the complexity contained
|
164
|
+
within the controller.
|
151
165
|
|
152
|
-
**Moral #2: Rails controllers should dispatch to application logic, and receive
|
166
|
+
**Moral #2: Rails controllers should dispatch to application logic, and receive
|
167
|
+
instructions on how to respond.**
|
153
168
|
|
154
|
-
The purpose of the command is to group orthogonal but interdependent results
|
155
|
-
looks with a `Skywalker::Command`:
|
169
|
+
The purpose of the command is to group orthogonal but interdependent results
|
170
|
+
into logical operations. Here's how that looks with a `Skywalker::Command`:
|
156
171
|
|
157
172
|
|
158
173
|
```ruby
|
@@ -239,13 +254,19 @@ command = AddGroupCommand.call(
|
|
239
254
|
|
240
255
|
```
|
241
256
|
|
242
|
-
You can pass any object responding to `#call` to the `on_success` and
|
257
|
+
You can pass any object responding to `#call` to the `on_success` and
|
258
|
+
`on_failure` handlers, including procs, lambdas, controller methods, or other
|
259
|
+
commands themselves.
|
243
260
|
|
244
261
|
### What happens when callbacks fail?
|
245
262
|
|
246
|
-
Exceptions thrown inside the success callbacks (`on_success` or your own
|
263
|
+
Exceptions thrown inside the success callbacks (`on_success` or your own
|
264
|
+
callbacks defined in `run_success_callbacks`) will cause the command to fail and
|
265
|
+
run the failure callbacks.
|
247
266
|
|
248
|
-
Exceptions thrown inside the failure callbacks (`on_failure` or your own
|
267
|
+
Exceptions thrown inside the failure callbacks (`on_failure` or your own
|
268
|
+
callbacks defined in `run_failure_callbacks`) will not be caught and will bubble
|
269
|
+
out of the command.
|
249
270
|
|
250
271
|
### Overriding Methods
|
251
272
|
|
@@ -254,39 +275,63 @@ The following methods are overridable for easy customization:
|
|
254
275
|
- `execute!`
|
255
276
|
- Define your operations here.
|
256
277
|
- `required_args`
|
257
|
-
- An array of expected keys given to the command. Raises `ArgumentError` if
|
278
|
+
- An array of expected keys given to the command. Raises `ArgumentError` if
|
279
|
+
keys are missing.
|
258
280
|
- `validate_arguments!`
|
259
|
-
- Checks required args are present, but can be customized. All instance
|
281
|
+
- Checks required args are present, but can be customized. All instance
|
282
|
+
variables are set by this point.
|
260
283
|
- `transaction(&block)`
|
261
|
-
- Uses an `ActiveRecord::Base.transaction` by default, but can be customized.
|
284
|
+
- Uses an `ActiveRecord::Base.transaction` by default, but can be customized.
|
285
|
+
`execute!` runs inside of this.
|
262
286
|
- `confirm_success`
|
263
287
|
- Fires off callbacks on command success (i.e. non-error).
|
264
288
|
- `run_success_callbacks`
|
265
|
-
- Dictates which success callbacks are run. Defaults to `on_success` if
|
289
|
+
- Dictates which success callbacks are run. Defaults to `on_success` if
|
290
|
+
defined.
|
266
291
|
- `confirm_failure`
|
267
|
-
- Fires off callbacks on command failure (i.e. erroneous state), and sets the
|
292
|
+
- Fires off callbacks on command failure (i.e. erroneous state), and sets the
|
293
|
+
exception as `command.error`.
|
268
294
|
- `run_failure_callbacks`
|
269
|
-
- Dictates which failure callbacks are run. Defaults to `on_failure` if
|
295
|
+
- Dictates which failure callbacks are run. Defaults to `on_failure` if
|
296
|
+
defined.
|
270
297
|
|
271
|
-
For further reference, simply see the command file. It's less than 90 LOC and
|
298
|
+
For further reference, simply see the command file. It's less than 90 LOC and
|
299
|
+
well-commented.
|
272
300
|
|
273
301
|
## Testing (and TDD)
|
274
302
|
|
275
|
-
Take a look at the `examples` directory, which uses example as above of a
|
303
|
+
Take a look at the `examples` directory, which uses example as above of a
|
304
|
+
notifier, but makes it a bit more complicated: it assumes that we only send
|
305
|
+
emails if the user (which we'll pass in) has a preference set to receive email.
|
276
306
|
|
277
307
|
### Assumptions
|
278
308
|
|
279
309
|
Here's what you can assume in your tests:
|
280
310
|
|
281
|
-
1. Arguments that are present in the list of required_args will throw an error
|
282
|
-
|
283
|
-
|
311
|
+
1. Arguments that are present in the list of required_args will throw an error
|
312
|
+
before the command executes if they are not passed.
|
313
|
+
2. Operations that throw an error will abort the command and trigger its failure
|
314
|
+
state.
|
315
|
+
3. Calling `Command.new().call` is functionally equivalent to calling
|
316
|
+
`Command.call()`
|
284
317
|
|
285
318
|
### Strategy
|
286
319
|
|
287
|
-
There are two tests that you need to write. First, you'll want to write a
|
320
|
+
There are two tests that you need to write. First, you'll want to write a
|
321
|
+
Command spec, which are very simplistic specs and should be used to verify the
|
322
|
+
validity of the command in isolation from the rest of the system. (This is what
|
323
|
+
the example shows.)
|
288
324
|
|
289
|
-
|
325
|
+
You'll also want to write some high-level integration tests to make sure that
|
326
|
+
the command is implemented correctly inside your controller, and has the
|
327
|
+
expected system-wide results. You shouldn't need to write integration specs to
|
328
|
+
test every path -- it should suffice to test a successful path and a failing
|
329
|
+
path, though your situation may vary depending on the detail of error handling
|
330
|
+
you perform.
|
331
|
+
|
332
|
+
Here's one huge benefit: with a few small steps, you won't need to include
|
333
|
+
`rails_helper` to boot up the entire environment. That means blazingly fast
|
334
|
+
tests. All you need to do is stub `transaction` on your command, like so:
|
290
335
|
|
291
336
|
```ruby
|
292
337
|
RSpec.describe CreateGroupCommand do
|
@@ -311,8 +356,96 @@ undefined method `call' for nil:NilClass
|
|
311
356
|
# .../lib/skywalker/command.rb:118:in `run_failure_callbacks'
|
312
357
|
```
|
313
358
|
|
314
|
-
This means that the command failed and you didn't specify an `on_failure`
|
315
|
-
|
359
|
+
This means that the command failed and you didn't specify an `on_failure`
|
360
|
+
callback. You can stick a debugger inside of `run_failure_callbacks`, and get
|
361
|
+
the failure exception as `self.error`. You can also reraise the exception to
|
362
|
+
achieve a better result summary, but this is not done by default, as you may
|
363
|
+
also want to test error handling.
|
364
|
+
|
365
|
+
## Components
|
366
|
+
|
367
|
+
A `Skywalker::Command` is implemented through a series of modules that can be
|
368
|
+
used independently from each other and outside of the context of the `Command`
|
369
|
+
object.
|
370
|
+
|
371
|
+
### `Acceptable`
|
372
|
+
|
373
|
+
`Skywalker::Acceptable` allows an object to receive a keyword list of arguments
|
374
|
+
upon instantiation. It creates a reader and writer for each keyword that doesn't
|
375
|
+
already have one, and it will raise an error for any keyword not given that is
|
376
|
+
present inside its `required_args` list.
|
377
|
+
|
378
|
+
Example:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
require 'skywalker/acceptable'
|
382
|
+
|
383
|
+
class MyClass
|
384
|
+
include Skywalker::Acceptable
|
385
|
+
|
386
|
+
def required_args
|
387
|
+
%w(baz)
|
388
|
+
end
|
389
|
+
|
390
|
+
def bar
|
391
|
+
"definitely not #{@bar}"
|
392
|
+
end
|
393
|
+
|
394
|
+
def baz=(int)
|
395
|
+
@baz = int.to_s.reverse.to_i
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
|
400
|
+
MyClass.new(foo: "abc", bar: "xyz") # => ArgumentError, "baz required but not given"
|
401
|
+
|
402
|
+
instance = MyClass.new(foo: "abc", bar: "xyz", baz: 123) # => <MyClass#...>
|
403
|
+
|
404
|
+
instance.foo # => "abc"
|
405
|
+
instance.bar # => "definitely not xyz"
|
406
|
+
instance.baz # => 321
|
407
|
+
```
|
408
|
+
|
409
|
+
|
410
|
+
### `Callable`
|
411
|
+
|
412
|
+
A very simple module that allows a class to implement `self.call`, which
|
413
|
+
forwards any arguments to a new instance and then calls that instance.
|
414
|
+
|
415
|
+
Example:
|
416
|
+
|
417
|
+
```ruby
|
418
|
+
require 'skywalker/callable'
|
419
|
+
|
420
|
+
class MyClass
|
421
|
+
include Skywalker::Callable
|
422
|
+
|
423
|
+
def initialize(message)
|
424
|
+
@message = message
|
425
|
+
end
|
426
|
+
|
427
|
+
def call
|
428
|
+
@message
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
|
433
|
+
MyClass.new("Hello World").call # => Hello World
|
434
|
+
MyClass.call("Hello World 2") # => Hello World 2
|
435
|
+
```
|
436
|
+
|
437
|
+
Tiny but convenient.
|
438
|
+
|
439
|
+
### `Transactional`
|
440
|
+
|
441
|
+
Will include `Acceptable`.
|
442
|
+
|
443
|
+
Implements the core transactional logic used by `Skywalker::Command`.
|
444
|
+
|
445
|
+
Makes `call` open a transaction, running `execute!` and calling
|
446
|
+
`confirm_success` or `confirm_failure` as appropriate.
|
447
|
+
|
448
|
+
For example, see `Skywalker::Command` documentation above.
|
316
449
|
|
317
450
|
## Contributing
|
318
451
|
|
data/examples/Gemfile.lock
CHANGED
@@ -1,33 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
skywalker (2.
|
5
|
-
activerecord (>= 4.0)
|
4
|
+
skywalker (2.2.0)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
|
-
activemodel (4.2.1)
|
11
|
-
activesupport (= 4.2.1)
|
12
|
-
builder (~> 3.1)
|
13
|
-
activerecord (4.2.1)
|
14
|
-
activemodel (= 4.2.1)
|
15
|
-
activesupport (= 4.2.1)
|
16
|
-
arel (~> 6.0)
|
17
|
-
activesupport (4.2.1)
|
18
|
-
i18n (~> 0.7)
|
19
|
-
json (~> 1.7, >= 1.7.7)
|
20
|
-
minitest (~> 5.1)
|
21
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
22
|
-
tzinfo (~> 1.1)
|
23
|
-
arel (6.0.0)
|
24
|
-
builder (3.2.2)
|
25
9
|
coderay (1.1.0)
|
26
10
|
diff-lcs (1.2.5)
|
27
|
-
i18n (0.7.0)
|
28
|
-
json (1.8.2)
|
29
11
|
method_source (0.8.2)
|
30
|
-
minitest (5.6.1)
|
31
12
|
pry (0.10.1)
|
32
13
|
coderay (~> 1.1.0)
|
33
14
|
method_source (~> 0.8.1)
|
@@ -45,9 +26,6 @@ GEM
|
|
45
26
|
rspec-support (~> 3.1.0)
|
46
27
|
rspec-support (3.1.2)
|
47
28
|
slop (3.6.0)
|
48
|
-
thread_safe (0.3.5)
|
49
|
-
tzinfo (1.2.2)
|
50
|
-
thread_safe (~> 0.1)
|
51
29
|
|
52
30
|
PLATFORMS
|
53
31
|
ruby
|
@@ -56,3 +34,6 @@ DEPENDENCIES
|
|
56
34
|
pry
|
57
35
|
rspec
|
58
36
|
skywalker!
|
37
|
+
|
38
|
+
BUNDLED WITH
|
39
|
+
1.12.1
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Skywalker
|
2
|
+
module Callable
|
3
|
+
|
4
|
+
#
|
5
|
+
# Extend instead of include because we'd prefer to keep a uniform interface
|
6
|
+
# among Skywalker extensions, and we have in the past had additional
|
7
|
+
# instance methods defined herein.
|
8
|
+
#
|
9
|
+
# @since 2.2.0
|
10
|
+
#
|
11
|
+
def self.included(klass)
|
12
|
+
klass.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
|
18
|
+
#
|
19
|
+
# Provides a convenient way to call a command without having to instantiate
|
20
|
+
# and call.
|
21
|
+
#
|
22
|
+
# @since 2.2.0
|
23
|
+
#
|
24
|
+
def call(*args)
|
25
|
+
new(*args).call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
data/lib/skywalker/command.rb
CHANGED
@@ -1,112 +1,9 @@
|
|
1
|
-
require '
|
2
|
-
require 'skywalker/
|
1
|
+
require 'skywalker/callable'
|
2
|
+
require 'skywalker/transactional'
|
3
3
|
|
4
4
|
module Skywalker
|
5
5
|
class Command
|
6
|
-
include
|
7
|
-
|
8
|
-
################################################################################
|
9
|
-
# Class interface
|
10
|
-
################################################################################
|
11
|
-
|
12
|
-
#
|
13
|
-
# Provides a convenient way to call a command without having to instantiate
|
14
|
-
# and call.
|
15
|
-
#
|
16
|
-
# @since 1.0.0
|
17
|
-
#
|
18
|
-
def self.call(*args)
|
19
|
-
new(*args).call
|
20
|
-
end
|
21
|
-
|
22
|
-
|
23
|
-
attr_accessor :on_success,
|
24
|
-
:on_failure,
|
25
|
-
:error
|
26
|
-
|
27
|
-
|
28
|
-
#
|
29
|
-
# Call: runs the transaction and all operations.
|
30
|
-
#
|
31
|
-
# @since 1.0.0
|
32
|
-
#
|
33
|
-
def call
|
34
|
-
transaction do
|
35
|
-
execute!
|
36
|
-
|
37
|
-
confirm_success
|
38
|
-
end
|
39
|
-
|
40
|
-
rescue Exception => error
|
41
|
-
confirm_failure error
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
#
|
46
|
-
# Procedural execution method. This should be implemented inside subclasses
|
47
|
-
# to add operations.
|
48
|
-
#
|
49
|
-
# @since 1.0.0
|
50
|
-
#
|
51
|
-
protected def execute!
|
52
|
-
end
|
53
|
-
|
54
|
-
|
55
|
-
################################################################################
|
56
|
-
# Private interface
|
57
|
-
################################################################################
|
58
|
-
|
59
|
-
|
60
|
-
#
|
61
|
-
# Wraps the given block in transactional logic.
|
62
|
-
#
|
63
|
-
# @since 1.0.0
|
64
|
-
#
|
65
|
-
private def transaction(&block)
|
66
|
-
::ActiveRecord::Base.transaction(&block)
|
67
|
-
end
|
68
|
-
|
69
|
-
#
|
70
|
-
# Triggers the given callback on success
|
71
|
-
#
|
72
|
-
# @since 1.0.0
|
73
|
-
#
|
74
|
-
private def confirm_success
|
75
|
-
run_success_callbacks
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
#
|
80
|
-
# Runs success callback if given. Override to specify additional callbacks
|
81
|
-
# or to add branching logic here.
|
82
|
-
#
|
83
|
-
# @since 1.1.0
|
84
|
-
#
|
85
|
-
private def run_success_callbacks
|
86
|
-
on_success.call(self) unless on_success.nil?
|
87
|
-
end
|
88
|
-
|
89
|
-
|
90
|
-
#
|
91
|
-
# Triggered on failure of transaction. Sets `#error` so the exception can
|
92
|
-
# be retrieved, and triggers the error callbacks.
|
93
|
-
#
|
94
|
-
# @since 1.0.0
|
95
|
-
#
|
96
|
-
private def confirm_failure(error)
|
97
|
-
self.error = error
|
98
|
-
run_failure_callbacks
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
# Runs failure callback if given. Override to specify additional callbacks
|
104
|
-
# or to add branching logic here.
|
105
|
-
#
|
106
|
-
# @since 1.1.0
|
107
|
-
#
|
108
|
-
private def run_failure_callbacks
|
109
|
-
on_failure.call(self) unless on_failure.nil?
|
110
|
-
end
|
6
|
+
include Callable
|
7
|
+
include Transactional
|
111
8
|
end
|
112
9
|
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'skywalker/acceptable'
|
2
|
+
|
3
|
+
module Skywalker
|
4
|
+
module Transactional
|
5
|
+
|
6
|
+
#
|
7
|
+
# Requires Acceptable and add accessors for callbacks.
|
8
|
+
#
|
9
|
+
# @since 2.2.0
|
10
|
+
#
|
11
|
+
def self.included(klass)
|
12
|
+
klass.include Acceptable
|
13
|
+
klass.send(:attr_accessor, :on_success, :on_failure, :error)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
#
|
18
|
+
# Runs the transaction and all operations.
|
19
|
+
#
|
20
|
+
# @since 2.2.0
|
21
|
+
#
|
22
|
+
def call
|
23
|
+
transaction do
|
24
|
+
execute!
|
25
|
+
|
26
|
+
confirm_success
|
27
|
+
end
|
28
|
+
|
29
|
+
rescue Exception => error
|
30
|
+
confirm_failure error
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
#
|
35
|
+
# Procedural execution method. This should be implemented inside subclasses
|
36
|
+
# to add operations.
|
37
|
+
#
|
38
|
+
# @since 2.2.0
|
39
|
+
#
|
40
|
+
protected def execute!
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
################################################################################
|
45
|
+
# Private interface
|
46
|
+
################################################################################
|
47
|
+
|
48
|
+
|
49
|
+
#
|
50
|
+
# Wraps the given block in transactional logic.
|
51
|
+
#
|
52
|
+
# @since 2.2.0
|
53
|
+
#
|
54
|
+
private def transaction(&block)
|
55
|
+
if active_record_defined?
|
56
|
+
active_record_transaction_method.call(&block)
|
57
|
+
else
|
58
|
+
block.call
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
#
|
64
|
+
# Allows us to artificially declare whether AR is loaded for specs.
|
65
|
+
#
|
66
|
+
# @since 2.2.0
|
67
|
+
#
|
68
|
+
private def active_record_defined?
|
69
|
+
defined?(ActiveRecord)
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Allows us to artificially choose which method to use as the AR
|
74
|
+
# transaction method.
|
75
|
+
#
|
76
|
+
# @since 2.2.0
|
77
|
+
#
|
78
|
+
private def active_record_transaction_method
|
79
|
+
::ActiveRecord::Base.method(:transaction)
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
#
|
84
|
+
# Triggers the given callback on success
|
85
|
+
#
|
86
|
+
# @since 2.2.0
|
87
|
+
#
|
88
|
+
private def confirm_success
|
89
|
+
run_success_callbacks
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
#
|
94
|
+
# Runs success callback if given. Override to specify additional callbacks
|
95
|
+
# or to add branching logic here.
|
96
|
+
#
|
97
|
+
# @since 2.2.0
|
98
|
+
#
|
99
|
+
private def run_success_callbacks
|
100
|
+
on_success.call(self) unless on_success.nil?
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#
|
105
|
+
# Triggered on failure of transaction. Sets `#error` so the exception can
|
106
|
+
# be retrieved, and triggers the error callbacks.
|
107
|
+
#
|
108
|
+
# @since 2.2.0
|
109
|
+
#
|
110
|
+
private def confirm_failure(error)
|
111
|
+
self.error = error
|
112
|
+
run_failure_callbacks
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
#
|
117
|
+
# Runs failure callback if given. Override to specify additional callbacks
|
118
|
+
# or to add branching logic here.
|
119
|
+
#
|
120
|
+
# @since 2.2.0
|
121
|
+
#
|
122
|
+
private def run_failure_callbacks
|
123
|
+
on_failure.call(self) unless on_failure.nil?
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
data/lib/skywalker/version.rb
CHANGED
data/skywalker.gemspec
CHANGED
@@ -20,8 +20,6 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.required_ruby_version = '>= 2.1.2'
|
22
22
|
|
23
|
-
spec.add_dependency 'activerecord', '>= 4.0'
|
24
|
-
|
25
23
|
spec.add_development_dependency "bundler", "~> 1.7"
|
26
24
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
25
|
spec.add_development_dependency "rspec"
|
@@ -32,7 +32,7 @@ module Skywalker
|
|
32
32
|
|
33
33
|
it "raises an error if an argument in its required_args is not present" do
|
34
34
|
allow_any_instance_of(klass).to receive(:required_args).and_return([:required_arg])
|
35
|
-
expect { klass.new }.to raise_error
|
35
|
+
expect { klass.new }.to raise_error ArgumentError
|
36
36
|
end
|
37
37
|
|
38
38
|
it "does not raise an error if an argument in its required_args is present" do
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'skywalker/callable'
|
3
|
+
|
4
|
+
module Skywalker
|
5
|
+
RSpec.describe Callable do
|
6
|
+
let(:klass) { Class.new { include Callable } }
|
7
|
+
|
8
|
+
describe ".call" do
|
9
|
+
it "instantiates and calls" do
|
10
|
+
expect(klass).to receive_message_chain('new.call')
|
11
|
+
klass.call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
describe "#call" do
|
17
|
+
it "raises an error because it is not defined" do
|
18
|
+
instance = klass.new
|
19
|
+
expect { instance.call }.to raise_error NoMethodError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -3,135 +3,5 @@ require 'skywalker/command'
|
|
3
3
|
|
4
4
|
module Skywalker
|
5
5
|
RSpec.describe Command do
|
6
|
-
describe "convenience" do
|
7
|
-
it "provides a class call method that instantiates and calls" do
|
8
|
-
expect(Command).to receive_message_chain('new.call')
|
9
|
-
Command.call
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
describe "validity control" do
|
15
|
-
let(:command) { Command.new }
|
16
|
-
|
17
|
-
it "executes in a transaction" do
|
18
|
-
expect(command).to receive(:transaction)
|
19
|
-
command.call
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
describe "execution" do
|
25
|
-
before do
|
26
|
-
allow(command).to receive(:transaction).and_yield
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
describe "success handling" do
|
31
|
-
let(:on_success) { double("on_success callback") }
|
32
|
-
let(:command) { Command.new(on_success: on_success) }
|
33
|
-
|
34
|
-
before do
|
35
|
-
allow(command).to receive(:execute!).and_return(true)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "triggers the confirm_success method" do
|
39
|
-
expect(command).to receive(:confirm_success)
|
40
|
-
command.call
|
41
|
-
end
|
42
|
-
|
43
|
-
it "runs the success callbacks" do
|
44
|
-
expect(command).to receive(:run_success_callbacks)
|
45
|
-
command.call
|
46
|
-
end
|
47
|
-
|
48
|
-
describe "on_success" do
|
49
|
-
context "when on_success is not nil" do
|
50
|
-
it "calls the on_success callback with itself" do
|
51
|
-
expect(on_success).to receive(:call).with(command)
|
52
|
-
command.call
|
53
|
-
end
|
54
|
-
|
55
|
-
context "when on_success is not callable" do
|
56
|
-
let(:on_failure) { double("on_failure") }
|
57
|
-
let(:command) { Command.new(on_success: "a string", on_failure: on_failure) }
|
58
|
-
|
59
|
-
it "confirms failure if the on_success callback fails" do
|
60
|
-
expect(on_failure).to receive(:call).with(command)
|
61
|
-
command.call
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
context "when on_success is nil" do
|
67
|
-
let(:nil_callback) { double("fakenil", nil?: true) }
|
68
|
-
let(:command) { Command.new(on_success: nil_callback) }
|
69
|
-
|
70
|
-
it "does not call on_success" do
|
71
|
-
expect(nil_callback).not_to receive(:call)
|
72
|
-
command.call
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
describe "failure handling" do
|
80
|
-
let(:on_failure) { double("on_failure callback") }
|
81
|
-
let(:command) { Command.new(on_failure: on_failure) }
|
82
|
-
|
83
|
-
before do
|
84
|
-
allow(command).to receive(:execute!).and_raise(ScriptError)
|
85
|
-
end
|
86
|
-
|
87
|
-
it "triggers the confirm_failure method" do
|
88
|
-
expect(command).to receive(:confirm_failure)
|
89
|
-
command.call
|
90
|
-
end
|
91
|
-
|
92
|
-
it "sets the error on the command" do
|
93
|
-
allow(on_failure).to receive(:call)
|
94
|
-
expect(command).to receive(:error=)
|
95
|
-
command.call
|
96
|
-
end
|
97
|
-
|
98
|
-
it "runs the failure callbacks" do
|
99
|
-
allow(command).to receive(:error=)
|
100
|
-
expect(command).to receive(:run_failure_callbacks)
|
101
|
-
command.call
|
102
|
-
end
|
103
|
-
|
104
|
-
describe "on_failure" do
|
105
|
-
before do
|
106
|
-
allow(command).to receive(:error=)
|
107
|
-
end
|
108
|
-
|
109
|
-
context "when on_failure is not nil" do
|
110
|
-
it "calls the on_failure callback with itself" do
|
111
|
-
expect(on_failure).to receive(:call).with(command)
|
112
|
-
command.call
|
113
|
-
end
|
114
|
-
|
115
|
-
context "when on_failure is not callable" do
|
116
|
-
let(:command) { Command.new(on_failure: "a string") }
|
117
|
-
|
118
|
-
it "raises an error" do
|
119
|
-
expect { command.call }.to raise_error
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
context "when on_failure is nil" do
|
125
|
-
let(:nil_callback) { double("fakenil", nil?: true) }
|
126
|
-
let(:command) { Command.new(on_failure: nil_callback) }
|
127
|
-
|
128
|
-
it "does not call on_failure" do
|
129
|
-
expect(nil_callback).not_to receive(:call)
|
130
|
-
command.call
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
6
|
end
|
137
7
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'skywalker/transactional'
|
3
|
+
|
4
|
+
module Skywalker
|
5
|
+
RSpec.describe Transactional do
|
6
|
+
let(:klass) { Class.new { include Transactional } }
|
7
|
+
|
8
|
+
describe "#transaction" do
|
9
|
+
let(:block) { Proc.new { "hey" } }
|
10
|
+
let(:instance) { klass.new }
|
11
|
+
let(:tx_method) { double("tx_method") }
|
12
|
+
|
13
|
+
context "when ActiveRecord is present" do
|
14
|
+
before do
|
15
|
+
allow(instance).to receive(:active_record_defined?).and_return true
|
16
|
+
allow(instance).to receive(:active_record_transaction_method).and_return tx_method
|
17
|
+
end
|
18
|
+
|
19
|
+
it "calls the transaction method with the block" do
|
20
|
+
allow(tx_method).to receive(:call) do |&blk|
|
21
|
+
blk.call(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
expect(instance.send(:transaction, &block)).to eq "hey"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when ActiveRecord is not present" do
|
29
|
+
before do
|
30
|
+
allow(instance).to receive(:active_record_defined?).and_return false
|
31
|
+
end
|
32
|
+
|
33
|
+
it "calls the block" do
|
34
|
+
expect(instance.send(:transaction, &block)).to eq "hey"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "validity control" do
|
40
|
+
let(:instance) { klass.new }
|
41
|
+
|
42
|
+
it "executes in a transaction" do
|
43
|
+
expect(instance).to receive(:transaction)
|
44
|
+
instance.call
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "execution" do
|
49
|
+
before do
|
50
|
+
allow(instance).to receive(:transaction).and_yield
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
describe "success handling" do
|
55
|
+
let(:on_success) { double("on_success callback") }
|
56
|
+
let(:instance) { klass.new(on_success: on_success) }
|
57
|
+
|
58
|
+
before do
|
59
|
+
allow(instance).to receive(:execute!).and_return(true)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "triggers the confirm_success method" do
|
63
|
+
expect(instance).to receive(:confirm_success)
|
64
|
+
instance.call
|
65
|
+
end
|
66
|
+
|
67
|
+
it "runs the success callbacks" do
|
68
|
+
expect(instance).to receive(:run_success_callbacks)
|
69
|
+
instance.call
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "on_success" do
|
73
|
+
context "when on_success is not nil" do
|
74
|
+
it "calls the on_success callback with itself" do
|
75
|
+
expect(on_success).to receive(:call).with(instance)
|
76
|
+
instance.call
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when on_success is not callable" do
|
80
|
+
let(:on_failure) { double("on_failure") }
|
81
|
+
let(:instance) { klass.new(on_success: "a string", on_failure: on_failure) }
|
82
|
+
|
83
|
+
it "confirms failure if the on_success callback fails" do
|
84
|
+
expect(on_failure).to receive(:call).with(instance)
|
85
|
+
instance.call
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when on_success is nil" do
|
91
|
+
let(:nil_callback) { double("fakenil", nil?: true) }
|
92
|
+
let(:instance) { klass.new(on_success: nil_callback) }
|
93
|
+
|
94
|
+
it "does not call on_success" do
|
95
|
+
expect(nil_callback).not_to receive(:call)
|
96
|
+
instance.call
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
describe "failure handling" do
|
104
|
+
let(:on_failure) { double("on_failure callback") }
|
105
|
+
let(:instance) { klass.new(on_failure: on_failure) }
|
106
|
+
|
107
|
+
before do
|
108
|
+
allow(instance).to receive(:execute!).and_raise(ScriptError)
|
109
|
+
end
|
110
|
+
|
111
|
+
it "triggers the confirm_failure method" do
|
112
|
+
expect(instance).to receive(:confirm_failure)
|
113
|
+
instance.call
|
114
|
+
end
|
115
|
+
|
116
|
+
it "sets the error on the instance" do
|
117
|
+
allow(on_failure).to receive(:call)
|
118
|
+
expect(instance).to receive(:error=)
|
119
|
+
instance.call
|
120
|
+
end
|
121
|
+
|
122
|
+
it "runs the failure callbacks" do
|
123
|
+
allow(instance).to receive(:error=)
|
124
|
+
expect(instance).to receive(:run_failure_callbacks)
|
125
|
+
instance.call
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "on_failure" do
|
129
|
+
before do
|
130
|
+
allow(instance).to receive(:error=)
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when on_failure is not nil" do
|
134
|
+
it "calls the on_failure callback with itself" do
|
135
|
+
expect(on_failure).to receive(:call).with(instance)
|
136
|
+
instance.call
|
137
|
+
end
|
138
|
+
|
139
|
+
context "when on_failure is not callable" do
|
140
|
+
let(:instance) { klass.new(on_failure: "a string") }
|
141
|
+
|
142
|
+
it "raises an error" do
|
143
|
+
expect { instance.call }.to raise_error NoMethodError
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when on_failure is nil" do
|
149
|
+
let(:nil_callback) { double("fakenil", nil?: true) }
|
150
|
+
let(:instance) { klass.new(on_failure: nil_callback) }
|
151
|
+
|
152
|
+
it "does not call on_failure" do
|
153
|
+
expect(nil_callback).not_to receive(:call)
|
154
|
+
instance.call
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skywalker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Yurkowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: activerecord
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '4.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - ">="
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '4.0'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: bundler
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -116,11 +102,15 @@ files:
|
|
116
102
|
- examples/spec/tiny_spec_helper.rb
|
117
103
|
- lib/skywalker.rb
|
118
104
|
- lib/skywalker/acceptable.rb
|
105
|
+
- lib/skywalker/callable.rb
|
119
106
|
- lib/skywalker/command.rb
|
107
|
+
- lib/skywalker/transactional.rb
|
120
108
|
- lib/skywalker/version.rb
|
121
109
|
- skywalker.gemspec
|
122
110
|
- spec/lib/skywalker/acceptable_spec.rb
|
111
|
+
- spec/lib/skywalker/callable_spec.rb
|
123
112
|
- spec/lib/skywalker/command_spec.rb
|
113
|
+
- spec/lib/skywalker/transactional_spec.rb
|
124
114
|
- spec/spec_helper.rb
|
125
115
|
homepage: https://github.com/robyurkowski/skywalker
|
126
116
|
licenses:
|
@@ -142,11 +132,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
142
132
|
version: '0'
|
143
133
|
requirements: []
|
144
134
|
rubyforge_project:
|
145
|
-
rubygems_version: 2.
|
135
|
+
rubygems_version: 2.5.1
|
146
136
|
signing_key:
|
147
137
|
specification_version: 4
|
148
138
|
summary: A simple command pattern implementation for transactional operations.
|
149
139
|
test_files:
|
150
140
|
- spec/lib/skywalker/acceptable_spec.rb
|
141
|
+
- spec/lib/skywalker/callable_spec.rb
|
151
142
|
- spec/lib/skywalker/command_spec.rb
|
143
|
+
- spec/lib/skywalker/transactional_spec.rb
|
152
144
|
- spec/spec_helper.rb
|