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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d19f8fad7236722932ae900e99b7c4aaf0b8a708
4
- data.tar.gz: 1373707d67ae212131fa3f4ac95dda7d942831c1
3
+ metadata.gz: 2694a53679e9a4856529718e3d7a80108aa32273
4
+ data.tar.gz: 030762cf30ce2e24fef88a41098744ab65036965
5
5
  SHA512:
6
- metadata.gz: f877c39807cb3b1b7948786d484e018b176ad6ffd036b2da28ffcc5dd2464fee06ee8c3635a133f0cf088d32eda857b2d0301545a9f7f13cbe99013c39eeaf56
7
- data.tar.gz: 83eb7c4d081624d25378e2723595a62757b13642c7ef8b184b51a1a4885e4fc0d34409a36930caa6243b52e642a61e0def5fa03d2b3d7ca8dc2941a8471cc7ec
6
+ metadata.gz: ceed550cb8e32449ba3897c46a8b53ca4d89dc96874d66ebef9aa142227fddf928d8f2f4c6bfde3982239d348732035e31e90f472b6cb60a97b6d78b9ba422b5
7
+ data.tar.gz: 550fdeacbc19bebc36bab1eaf51c5171eb638f7a7364fa3d3d2079b9139e2244362fa1b8cc4b5ce4742c4010c63d9e8115d6e3526a8d58264dcfc4a75154e8f9
data/.gitignore CHANGED
@@ -13,5 +13,7 @@
13
13
  *.a
14
14
  mkmf.log
15
15
 
16
+ examples/vendor/gems
17
+
16
18
  vendor/bundle
17
19
  vendor/gems
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.call(group).deliver
204
+ notifier.deliver
200
205
  end
201
206
 
202
207
 
203
208
  private def notifier
204
- @notifier ||= NotificationsMailer.method(:group_created_notification)
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,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rspec'
4
+ gem 'pry'
5
+ gem 'skywalker', path: '../'
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+
3
+ %w(commands).each do |pattern|
4
+ path = File.expand_path("../../app/#{pattern}", __FILE__)
5
+ $:.push path unless $:.include?(path)
6
+ end
@@ -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
  ################################################################################
@@ -1,3 +1,3 @@
1
1
  module Skywalker
2
- VERSION = "1.1.1"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -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.1.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-14 00:00:00.000000000 Z
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