skywalker 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +54 -4
- data/examples/Gemfile +5 -0
- data/examples/Gemfile.lock +58 -0
- data/examples/app/commands/create_group_command.rb +46 -0
- data/examples/spec/commands/create_group_command_spec.rb +78 -0
- data/examples/spec/spec_helper.rb +61 -0
- data/examples/spec/tiny_spec_helper.rb +6 -0
- data/lib/skywalker/command.rb +36 -5
- data/lib/skywalker/version.rb +1 -1
- data/spec/lib/skywalker/command_spec.rb +15 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2694a53679e9a4856529718e3d7a80108aa32273
|
4
|
+
data.tar.gz: 030762cf30ce2e24fef88a41098744ab65036965
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceed550cb8e32449ba3897c46a8b53ca4d89dc96874d66ebef9aa142227fddf928d8f2f4c6bfde3982239d348732035e31e90f472b6cb60a97b6d78b9ba422b5
|
7
|
+
data.tar.gz: 550fdeacbc19bebc36bab1eaf51c5171eb638f7a7364fa3d3d2079b9139e2244362fa1b8cc4b5ce4742c4010c63d9e8115d6e3526a8d58264dcfc4a75154e8f9
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -190,18 +190,23 @@ class CreateGroupCommand < Skywalker::Command
|
|
190
190
|
end
|
191
191
|
|
192
192
|
|
193
|
+
private def required_args
|
194
|
+
%w(group on_success on_failure)
|
195
|
+
end
|
196
|
+
|
197
|
+
|
193
198
|
private def save_group!
|
194
199
|
group.save!
|
195
200
|
end
|
196
201
|
|
197
202
|
|
198
203
|
private def send_notifications!
|
199
|
-
notifier.
|
204
|
+
notifier.deliver
|
200
205
|
end
|
201
206
|
|
202
207
|
|
203
208
|
private def notifier
|
204
|
-
@notifier ||= NotificationsMailer.
|
209
|
+
@notifier ||= NotificationsMailer.group_created_notification(group)
|
205
210
|
end
|
206
211
|
end
|
207
212
|
```
|
@@ -242,6 +247,10 @@ The following methods are overridable for easy customization:
|
|
242
247
|
|
243
248
|
- `execute!`
|
244
249
|
- Define your operations here.
|
250
|
+
- `required_args`
|
251
|
+
- An array of expected keys given to the command. Raises `ArgumentError` if keys are missing.
|
252
|
+
- `validate_arguments!`
|
253
|
+
- Checks required args are present, but can be customized. All instance variables are set by this point.
|
245
254
|
- `transaction(&block)`
|
246
255
|
- Uses an `ActiveRecord::Base.transaction` by default, but can be customized. `execute!` runs inside of this.
|
247
256
|
- `confirm_success`
|
@@ -255,9 +264,50 @@ The following methods are overridable for easy customization:
|
|
255
264
|
|
256
265
|
For further reference, simply see the command file. It's less than 90 LOC and well-commented.
|
257
266
|
|
258
|
-
## Testing
|
267
|
+
## Testing (and TDD)
|
268
|
+
|
269
|
+
Take a look at the `examples` directory, which uses example as above of a notifier, but makes it a bit more complicated: it assumes that we only send emails if the user (which we'll pass in) has a preference set to receive email.
|
270
|
+
|
271
|
+
### Assumptions
|
272
|
+
|
273
|
+
Here's what you can assume in your tests:
|
274
|
+
|
275
|
+
1. Arguments that are present in the list of required_args will throw an error before the command executes if they are not passed.
|
276
|
+
2. Operations that throw an error will abort the command and trigger its failure state.
|
277
|
+
3. Calling `Command.new().call` is functionally equivalent to calling `Command.call()`
|
278
|
+
|
279
|
+
### Strategy
|
280
|
+
|
281
|
+
There are two tests that you need to write. First, you'll want to write a Command spec, which are very simplistic specs and should be used to verify the validity of the command in isolation from the rest of the system. (This is what the example shows.) You'll also want to write some high-level integration tests to make sure that the command is implemented correctly inside your controller, and has the expected system-wide results. You shouldn't need to write integration specs to test every path -- it should suffice to test a successful path and a failing path, though your situation may vary depending on the detail of error handling you perform.
|
282
|
+
|
283
|
+
Here's one huge benefit: with a few small steps, you won't need to include `rails_helper` to boot up the entire environment. That means blazingly fast tests. All you need to do is stub `transaction` on your command, like so:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
RSpec.describe CreateGroupCommand do
|
287
|
+
describe "operations" do
|
288
|
+
let(:command) { CreateGroupCommand.new(group: double("group") }
|
289
|
+
|
290
|
+
before do
|
291
|
+
allow(command).to receive(:transaction).and_yield
|
292
|
+
end
|
293
|
+
|
294
|
+
# ...
|
295
|
+
end
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
### Common Failures
|
300
|
+
|
301
|
+
Your failures will initially look like this:
|
302
|
+
|
303
|
+
```
|
304
|
+
undefined method `call' for nil:NilClass
|
305
|
+
# .../lib/skywalker/command.rb:118:in `run_failure_callbacks'
|
306
|
+
```
|
307
|
+
|
308
|
+
This means that the command failed and you didn't specify an `on_failure` callback. You can stick a debugger
|
309
|
+
inside of `run_failure_callbacks`, and get the failure exception as `self.error`. You can also reraise the exceptiong to achieve a better result summary, but this is not done by default, as you may also want to test error handling.
|
259
310
|
|
260
|
-
To come.
|
261
311
|
|
262
312
|
## Contributing
|
263
313
|
|
data/examples/Gemfile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../
|
3
|
+
specs:
|
4
|
+
skywalker (1.1.1)
|
5
|
+
activerecord (~> 4.1)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.7)
|
11
|
+
activesupport (= 4.1.7)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.1.7)
|
14
|
+
activemodel (= 4.1.7)
|
15
|
+
activesupport (= 4.1.7)
|
16
|
+
arel (~> 5.0.0)
|
17
|
+
activesupport (4.1.7)
|
18
|
+
i18n (~> 0.6, >= 0.6.9)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
arel (5.0.1.20140414130214)
|
24
|
+
builder (3.2.2)
|
25
|
+
coderay (1.1.0)
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
i18n (0.6.11)
|
28
|
+
json (1.8.1)
|
29
|
+
method_source (0.8.2)
|
30
|
+
minitest (5.4.3)
|
31
|
+
pry (0.10.1)
|
32
|
+
coderay (~> 1.1.0)
|
33
|
+
method_source (~> 0.8.1)
|
34
|
+
slop (~> 3.4)
|
35
|
+
rspec (3.1.0)
|
36
|
+
rspec-core (~> 3.1.0)
|
37
|
+
rspec-expectations (~> 3.1.0)
|
38
|
+
rspec-mocks (~> 3.1.0)
|
39
|
+
rspec-core (3.1.7)
|
40
|
+
rspec-support (~> 3.1.0)
|
41
|
+
rspec-expectations (3.1.2)
|
42
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
43
|
+
rspec-support (~> 3.1.0)
|
44
|
+
rspec-mocks (3.1.3)
|
45
|
+
rspec-support (~> 3.1.0)
|
46
|
+
rspec-support (3.1.2)
|
47
|
+
slop (3.6.0)
|
48
|
+
thread_safe (0.3.4)
|
49
|
+
tzinfo (1.2.2)
|
50
|
+
thread_safe (~> 0.1)
|
51
|
+
|
52
|
+
PLATFORMS
|
53
|
+
ruby
|
54
|
+
|
55
|
+
DEPENDENCIES
|
56
|
+
pry
|
57
|
+
rspec
|
58
|
+
skywalker!
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'skywalker/command'
|
2
|
+
|
3
|
+
class CreateGroupCommand < Skywalker::Command
|
4
|
+
def execute!
|
5
|
+
save_group!
|
6
|
+
send_notifications!
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def required_args
|
11
|
+
%w(user group)
|
12
|
+
end
|
13
|
+
|
14
|
+
################################################################################
|
15
|
+
# Operations
|
16
|
+
################################################################################
|
17
|
+
private def save_group!
|
18
|
+
group.save!
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
private def send_notifications!
|
23
|
+
notifier.deliver if send_user_email?
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
################################################################################
|
28
|
+
# Guards
|
29
|
+
################################################################################
|
30
|
+
private def send_user_email?
|
31
|
+
user.receives_email?
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
################################################################################
|
36
|
+
# Accessors
|
37
|
+
################################################################################
|
38
|
+
private def notifier
|
39
|
+
@notifier ||= ::NotificationsMailer.group_created_notification(group)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# def run_failure_callbacks
|
44
|
+
# require 'pry'; binding.pry
|
45
|
+
# end
|
46
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'tiny_spec_helper'
|
2
|
+
require 'create_group_command'
|
3
|
+
|
4
|
+
RSpec.describe CreateGroupCommand do
|
5
|
+
let(:notifier) { double("notifier") }
|
6
|
+
let(:command) { CreateGroupCommand.new(group: group, notifier: notifier, user: user) }
|
7
|
+
let(:group) { double("group") }
|
8
|
+
let(:user) { double("user") }
|
9
|
+
|
10
|
+
describe "operations" do
|
11
|
+
before do
|
12
|
+
allow(command).to receive(:transaction).and_yield
|
13
|
+
allow(command).to receive_message_chain("on_success.call") { true }
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "save group" do
|
17
|
+
before do
|
18
|
+
allow(command).to receive(:send_notifications!)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "saves" do
|
22
|
+
expect(group).to receive(:save!)
|
23
|
+
command.call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "send notification" do
|
28
|
+
before do
|
29
|
+
allow(command).to receive(:save_group!)
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when user receives email" do
|
33
|
+
before do
|
34
|
+
allow(command).to receive(:send_user_email?).and_return(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "sends" do
|
38
|
+
# NB: You'll want to set `raise_delivery_errors` for mail to work
|
39
|
+
# transactionally.
|
40
|
+
expect(notifier).to receive(:deliver)
|
41
|
+
command.call
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when user does not receive email" do
|
46
|
+
before do
|
47
|
+
allow(command).to receive(:send_user_email?).and_return(false)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "does not send" do
|
51
|
+
expect(notifier).not_to receive(:deliver)
|
52
|
+
command.call
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
describe "guards" do
|
60
|
+
describe "#send_user_email?" do
|
61
|
+
context "when user does not receive email" do
|
62
|
+
let(:user) { double("user", receives_email?: false) }
|
63
|
+
|
64
|
+
it "returns false" do
|
65
|
+
expect(command.send(:send_user_email?)).to eq(false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when user receives email" do
|
70
|
+
let(:user) { double("user", receives_email?: true) }
|
71
|
+
|
72
|
+
it "returns true" do
|
73
|
+
expect(command.send(:send_user_email?)).to eq(true)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
2
|
+
RSpec.configure do |config|
|
3
|
+
# These two settings work together to allow you to limit a spec run
|
4
|
+
# to individual examples or groups you care about by tagging them with
|
5
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
6
|
+
# get run.
|
7
|
+
|
8
|
+
config.filter_run :focus
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
|
11
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
12
|
+
# file, and it's useful to allow more verbose output when running an
|
13
|
+
# individual spec file.
|
14
|
+
if config.files_to_run.one?
|
15
|
+
# Use the documentation formatter for detailed output,
|
16
|
+
# unless a formatter has already been configured
|
17
|
+
# (e.g. via a command-line flag).
|
18
|
+
config.default_formatter = 'doc'
|
19
|
+
end
|
20
|
+
|
21
|
+
# Print the 10 slowest examples and example groups at the
|
22
|
+
# end of the spec run, to help surface which specs are running
|
23
|
+
# particularly slow.
|
24
|
+
config.profile_examples = 2
|
25
|
+
|
26
|
+
# Run specs in random order to surface order dependencies. If you find an
|
27
|
+
# order dependency and want to debug it, you can fix the order by providing
|
28
|
+
# the seed, which is printed after each run.
|
29
|
+
# --seed 1234
|
30
|
+
config.order = :random
|
31
|
+
|
32
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
33
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
34
|
+
# test failures related to randomization by passing the same `--seed` value
|
35
|
+
# as the one that triggered the failure.
|
36
|
+
Kernel.srand config.seed
|
37
|
+
|
38
|
+
# rspec-expectations config goes here. You can use an alternate
|
39
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
40
|
+
# assertions if you prefer.
|
41
|
+
# config.expect_with :rspec do |expectations|
|
42
|
+
# # Enable only the newer, non-monkey-patching expect syntax.
|
43
|
+
# # For more details, see:
|
44
|
+
# # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
45
|
+
# expectations.syntax = :expect
|
46
|
+
# end
|
47
|
+
|
48
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
49
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
50
|
+
#
|
51
|
+
# config.mock_with :rspec do |mocks|
|
52
|
+
# # Enable only the newer, non-monkey-patching expect syntax.
|
53
|
+
# # For more details, see:
|
54
|
+
# # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
55
|
+
# mocks.syntax = :expect
|
56
|
+
|
57
|
+
# # Prevents you from mocking or stubbing a method that does not exist on
|
58
|
+
# # a real object. This is generally recommended.
|
59
|
+
# mocks.verify_partial_doubles = true
|
60
|
+
# end
|
61
|
+
end
|
data/lib/skywalker/command.rb
CHANGED
@@ -14,6 +14,42 @@ module Skywalker
|
|
14
14
|
# Instantiates command, setting all arguments.
|
15
15
|
################################################################################
|
16
16
|
def initialize(**args)
|
17
|
+
self.args = args
|
18
|
+
self.args.freeze
|
19
|
+
|
20
|
+
parse_arguments
|
21
|
+
validate_arguments!
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
attr_accessor :on_success,
|
27
|
+
:on_failure,
|
28
|
+
:error,
|
29
|
+
:args
|
30
|
+
|
31
|
+
|
32
|
+
################################################################################
|
33
|
+
# Ensure required keys are present.
|
34
|
+
################################################################################
|
35
|
+
private def validate_arguments!
|
36
|
+
missing_args = required_args.map(&:to_s) - args.keys.map(&:to_s)
|
37
|
+
|
38
|
+
raise ArgumentError, "#{missing_args.join(", ")} required but not given" \
|
39
|
+
if missing_args.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
################################################################################
|
44
|
+
# Any required keys should go here as either strings or symbols.
|
45
|
+
################################################################################
|
46
|
+
private def required_args
|
47
|
+
[]
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
private def parse_arguments
|
17
53
|
args.each_pair do |reader_method, value|
|
18
54
|
writer_method = "#{reader_method}="
|
19
55
|
|
@@ -27,11 +63,6 @@ module Skywalker
|
|
27
63
|
end
|
28
64
|
|
29
65
|
|
30
|
-
attr_accessor :on_success,
|
31
|
-
:on_failure,
|
32
|
-
:error
|
33
|
-
|
34
|
-
|
35
66
|
################################################################################
|
36
67
|
# Call: runs the transaction and all operations.
|
37
68
|
################################################################################
|
data/lib/skywalker/version.rb
CHANGED
@@ -12,6 +12,11 @@ module Skywalker
|
|
12
12
|
|
13
13
|
|
14
14
|
describe "instantiation" do
|
15
|
+
it "freezes the arguments given to it" do
|
16
|
+
command = Command.new(a_symbol: :my_symbol)
|
17
|
+
expect(command.args).to be_frozen
|
18
|
+
end
|
19
|
+
|
15
20
|
it "accepts a variable list of arguments" do
|
16
21
|
expect { Command.new(a_symbol: :my_symbol, a_string: "my string") }.not_to raise_error
|
17
22
|
end
|
@@ -30,6 +35,16 @@ module Skywalker
|
|
30
35
|
command = Command.new(a_symbol: :my_symbol)
|
31
36
|
expect(command.a_symbol).to eq(:my_symbol)
|
32
37
|
end
|
38
|
+
|
39
|
+
it "raises an error if an argument in its required_args is not present" do
|
40
|
+
allow_any_instance_of(Command).to receive(:required_args).and_return([:required_arg])
|
41
|
+
expect { Command.new }.to raise_error
|
42
|
+
end
|
43
|
+
|
44
|
+
it "does not raise an error if an argument in its required_args is present" do
|
45
|
+
allow_any_instance_of(Command).to receive(:required_args).and_return([:required_arg])
|
46
|
+
expect { Command.new(required_arg: :blah) }.not_to raise_error
|
47
|
+
end
|
33
48
|
end
|
34
49
|
|
35
50
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: skywalker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.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: 2014-11-
|
11
|
+
date: 2014-11-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -108,6 +108,12 @@ files:
|
|
108
108
|
- LICENSE.txt
|
109
109
|
- README.md
|
110
110
|
- Rakefile
|
111
|
+
- examples/Gemfile
|
112
|
+
- examples/Gemfile.lock
|
113
|
+
- examples/app/commands/create_group_command.rb
|
114
|
+
- examples/spec/commands/create_group_command_spec.rb
|
115
|
+
- examples/spec/spec_helper.rb
|
116
|
+
- examples/spec/tiny_spec_helper.rb
|
111
117
|
- lib/skywalker.rb
|
112
118
|
- lib/skywalker/command.rb
|
113
119
|
- lib/skywalker/version.rb
|