verbalize 1.3.0 → 1.4.1

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: 61174d8fa1734e70a63a74b34060d056515098ca
4
- data.tar.gz: b3fc22d20687f9eba615606cdbc10865e56ea02f
3
+ metadata.gz: 2d3d8f53fd6b37b6d5729c0c73d9fabb5cfe3c0b
4
+ data.tar.gz: 22afc7cd81eb619e8dd393f4b606824e479b2780
5
5
  SHA512:
6
- metadata.gz: f3aeff77f6ce6012c78c597cfaadd60f19450e0cdfbd2c08e9f785b2a32b60d55685cc5571c97f0278a8c35795548811ade2e1dcc20209fe8bf7f9a600c5fbd5
7
- data.tar.gz: f5c66899c7b5bcf67b088b92cbdeb1e81191d6479cea19204b9d8541cd144eec97a7f3b33e35a83ff67762429d703f05d896c8b3428e5ef679620de530eb69ab
6
+ metadata.gz: 771d473b91c42ff8f3c58a12c847eff35969b25aae89141e2dbb9dab4f063112a3552b991ac3174e913c2aab2b4362c8b3db4c95962c666cf446390339c2cc63
7
+ data.tar.gz: 01b65780f3f72a326d8c9b185dfba2ef257ec50bbe29abb16778cd9b9fde468fdd5cb67cd708d86a43618bf3982fdb7b5a8d5b81187905156e317018411b65f1
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ```ruby
8
8
  class Add
9
- include Verbalize
9
+ include Verbalize::Action
10
10
 
11
11
  input :a, :b
12
12
 
@@ -30,7 +30,7 @@ value # => 42
30
30
 
31
31
  ```ruby
32
32
  class Add
33
- include Verbalize
33
+ include Verbalize::Action
34
34
 
35
35
  input optional: [:a, :b]
36
36
 
@@ -55,7 +55,7 @@ Add.call(a: 660, b: 6) # => [:ok, 666]
55
55
 
56
56
  ```ruby
57
57
  class Divide
58
- include Verbalize
58
+ include Verbalize::Action
59
59
 
60
60
  input :a, :b
61
61
 
@@ -96,7 +96,7 @@ class RubyAdd
96
96
  end
97
97
 
98
98
  class VerbalizeAdd
99
- include Verbalize
99
+ include Verbalize::Action
100
100
 
101
101
  input :a, :b
102
102
 
@@ -150,6 +150,149 @@ Comparison:
150
150
 
151
151
  ```
152
152
 
153
+ ## Testing
154
+
155
+ ### Happy Path
156
+
157
+ When testing positive cases of a `Verbalize::Action`, it is recommended to test using the `call!` class method and
158
+ assert on the result. This implicitly ensures a successful result, and your tests will fail with a bang! if
159
+ something goes wrong:
160
+
161
+ ```ruby
162
+ class MyAction
163
+ include Verbalize::Action
164
+
165
+ input :a
166
+
167
+ def call
168
+ fail!('#{a} is greater than than 100!') if a >= 100
169
+ a + 1
170
+ end
171
+ end
172
+
173
+ it 'returns the expected result' do
174
+ result = MyAction.call!(a: 50)
175
+ expect(result).to eq 51
176
+ end
177
+ ```
178
+
179
+ ### Sad Path
180
+
181
+ When testing negative cases of a `Verbalize::Action`, it is recommended to test using the `call` non-bang
182
+ class method. Use of `call!` here is not advised as it will result in an exception being thrown. Set assertions
183
+ on both the outcome and error value of the result:
184
+
185
+ ```ruby
186
+ class MyAction
187
+ include Verbalize::Action
188
+
189
+ input :a
190
+
191
+ def call
192
+ fail!('#{a} is greater than 100!') if a >= 100
193
+ a + 1
194
+ end
195
+ end
196
+
197
+ # rspec:
198
+ it 'fails when the input is out of bounds' do
199
+ result = MyAction.call(a: 1000)
200
+
201
+ expect(result).to be_failed
202
+ expect(result.value).to eq '1000 is greater than 100!'
203
+ end
204
+ ```
205
+
206
+ ### Stubbing Responses
207
+
208
+ When unit testing, it may be necessary to stub responses of Actions. The approach required depends
209
+ on how the nested actions are called by the object under test.
210
+
211
+ #### Stubbing `call`
212
+
213
+ When an object calls a `Verbalize::Action` via `call`, you can stub the response with a `Verbalize::Success` or
214
+ `Verbalize::Failure` instance.
215
+
216
+ Example:
217
+
218
+ ```ruby
219
+ class Foo
220
+ def do_something
221
+ result = MyAction.call(a: 1)
222
+ raise 'I couldn\'t do the thing!' if result.failure?
223
+
224
+ 'baz'
225
+ end
226
+ end
227
+
228
+ # rspec:
229
+ describe Foo do
230
+ describe '#something' do
231
+ subject { described_class.new(foo: 'bar') }
232
+
233
+ it 'does the thing when my action succeeds' do
234
+ successful_result = Verbalize::Success.new(123)
235
+ allow(MyAction).to receive(:call)
236
+ .with(a: 1)
237
+ .and_return(successful_result)
238
+
239
+ result = described_class.do_something
240
+
241
+ expect(result).to eq 'baz'
242
+ end
243
+
244
+ it 'raises an error when MyAction fails' do
245
+ failed_result = Verbalize::Failure.new('Y U NO!')
246
+ allow(MyAction).to receive(:call)
247
+ .with(a: 3)
248
+ .and_return(failed_result)
249
+
250
+ expect {
251
+ described_class.do_something
252
+ }.to raise_error(StandardError, 'I couldnt do the thing!')
253
+ end
254
+ end
255
+ end
256
+ ```
257
+
258
+ #### Stubbing `call!`
259
+
260
+ When an object calls a `Verbalize::Action` via `call!`, you can stub the response for the positive case with the value
261
+ you expect to be returned. When stubbing the negative case, you should instead throw `Verbalize::THROWN_SYMBOL`
262
+ (and the error message, if desired):
263
+
264
+ Example:
265
+
266
+ ```ruby
267
+ # rspec:
268
+ describe Foo do
269
+ describe '#something' do
270
+ subject { described_class.new(foo: 'bar') }
271
+
272
+ it 'does the thing' do
273
+ allow(MyAction).to receive(:call!)
274
+ .with(a: 1)
275
+ .and_return(123)
276
+
277
+ result = described_class.something
278
+
279
+ expect(result).to eq 'baz'
280
+ end
281
+
282
+ it 'returns an error when MyAction fails' do
283
+ failed_result = Verbalize::Failure.new('Y U NO!')
284
+ allow(MyAction).to receive(:call)
285
+ .with(a: 3)
286
+ .and_throw(::Verbalize::THROWN_SYMBOL, 'the failure message, if any')
287
+
288
+ expect {
289
+ described_class.something
290
+ }.to raise_error UncaughtThrowError
291
+ end
292
+ end
293
+ end
294
+ ```
295
+
153
296
  ## Installation
154
297
 
155
298
  Add this line to your application's Gemfile:
data/lib/verbalize.rb CHANGED
@@ -1,94 +1,13 @@
1
1
  require 'verbalize/version'
2
- require 'verbalize/build_initialize_method'
3
- require 'verbalize/build_safe_action_method'
4
- require 'verbalize/build_dangerous_action_method'
5
- require 'verbalize/build_attribute_readers'
6
- require 'verbalize/result'
2
+ require 'verbalize/action'
7
3
 
8
4
  module Verbalize
9
- THROWN_SYMBOL = :verbalize_error
10
- VerbalizeError = Class.new(StandardError)
11
-
12
- def fail!(failure_value = nil)
13
- throw(THROWN_SYMBOL, failure_value)
14
- end
5
+ # Remove this in a later version of Verbalize if desired. Aliased here for backwards compatibility.
6
+ THROWN_SYMBOL = Action::THROWN_SYMBOL
15
7
 
16
8
  def self.included(target)
17
- target.extend ClassMethods
18
- end
19
-
20
- module ClassMethods
21
- def verbalize(*arguments, **keyword_arguments)
22
- method_name, *arguments = arguments
23
- input(*arguments, method_name: method_name, **keyword_arguments)
24
- end
25
-
26
- def input( # rubocop:disable Metrics/MethodLength
27
- *required_keywords,
28
- optional: [],
29
- method_name: :call,
30
- **other_keyword_arguments
31
- )
32
-
33
- unless other_keyword_arguments.empty?
34
- raise(
35
- ArgumentError,
36
- "Unsupported keyword arguments received: #{other_keyword_arguments.inspect}"
37
- )
38
- end
39
-
40
- optional_keywords = Array(optional)
41
-
42
- class_eval BuildSafeActionMethod.call(
43
- required_keywords: required_keywords,
44
- optional_keywords: optional_keywords,
45
- method_name: method_name
46
- )
47
-
48
- class_eval BuildDangerousActionMethod.call(
49
- required_keywords: required_keywords,
50
- optional_keywords: optional_keywords,
51
- method_name: method_name
52
- )
53
-
54
- class_eval BuildInitializeMethod.call(
55
- required_keywords: required_keywords,
56
- optional_keywords: optional_keywords
57
- )
58
-
59
- class_eval BuildAttributeReaders.call(
60
- attributes: required_keywords + optional_keywords
61
- )
62
- end
63
-
64
- def call
65
- __verbalized_send(:call)
66
- end
67
-
68
- def call!
69
- __verbalized_send!(:call)
70
- end
71
-
72
- private
73
-
74
- def __verbalized_send(method_name, *args)
75
- outcome = :error
76
- value = catch(:verbalize_error) do
77
- value = new(*args).send(method_name)
78
- # The outcome is :ok if the call didn't throw.
79
- outcome = :ok
80
- value
81
- end
82
- Result.new(outcome: outcome, value: value)
83
- end
84
-
85
- def __verbalized_send!(method_name, *args)
86
- new(*args).send(method_name)
87
- rescue UncaughtThrowError => uncaught_throw_error
88
- fail_value = uncaught_throw_error.value
89
- error = VerbalizeError.new("Unhandled fail! called with: #{fail_value.inspect}.")
90
- error.set_backtrace(uncaught_throw_error.backtrace[2..-1])
91
- raise error
92
- end
9
+ warn Kernel.caller.first + ': `include Verbalize` is deprecated and will be removed in Verbalize v3.0; ' \
10
+ 'include `Verbalize::Action` instead'
11
+ target.include Verbalize::Action
93
12
  end
94
13
  end
@@ -0,0 +1,103 @@
1
+ require_relative 'error'
2
+ require_relative 'build_initialize_method'
3
+ require_relative 'build_safe_action_method'
4
+ require_relative 'build_dangerous_action_method'
5
+ require_relative 'build_attribute_readers'
6
+ require_relative 'success'
7
+ require_relative 'failure'
8
+
9
+ module Verbalize
10
+ module Action
11
+ THROWN_SYMBOL = :verbalize_error
12
+
13
+ def fail!(failure_value = nil)
14
+ throw(THROWN_SYMBOL, failure_value)
15
+ end
16
+
17
+ def self.included(target)
18
+ target.extend ClassMethods
19
+ end
20
+
21
+ module ClassMethods
22
+ def verbalize(*arguments, **keyword_arguments)
23
+ method_name, *arguments = arguments
24
+ input(*arguments, method_name: method_name, **keyword_arguments)
25
+ end
26
+
27
+ def input( # rubocop:disable Metrics/MethodLength
28
+ *required_keywords,
29
+ optional: [],
30
+ method_name: :call,
31
+ **other_keyword_arguments
32
+ )
33
+
34
+ deprecate_custom_methods(method_name)
35
+
36
+ unless other_keyword_arguments.empty?
37
+ raise(
38
+ ArgumentError,
39
+ "Unsupported keyword arguments received: #{other_keyword_arguments.inspect}"
40
+ )
41
+ end
42
+
43
+ optional_keywords = Array(optional)
44
+
45
+ class_eval BuildSafeActionMethod.call(
46
+ required_keywords: required_keywords,
47
+ optional_keywords: optional_keywords,
48
+ method_name: method_name
49
+ )
50
+
51
+ class_eval BuildDangerousActionMethod.call(
52
+ required_keywords: required_keywords,
53
+ optional_keywords: optional_keywords,
54
+ method_name: method_name
55
+ )
56
+
57
+ class_eval BuildInitializeMethod.call(
58
+ required_keywords: required_keywords,
59
+ optional_keywords: optional_keywords
60
+ )
61
+
62
+ class_eval BuildAttributeReaders.call(
63
+ attributes: required_keywords + optional_keywords
64
+ )
65
+ end
66
+
67
+ def call
68
+ __verbalized_send(:call)
69
+ end
70
+
71
+ def call!
72
+ __verbalized_send!(:call)
73
+ end
74
+
75
+ private
76
+
77
+ def deprecate_custom_methods(method_name)
78
+ return if method_name == :call
79
+ warn Kernel.caller[2] + ': use of custom method names for Actions is deprecated and support ' \
80
+ 'for it will be dropped in Verbalize v2.0. The `verbalize` method will also be removed.' \
81
+ 'Use `input` and define `#call` on your Action class instead.'
82
+ end
83
+
84
+ def __verbalized_send(method_name, *args)
85
+ error = catch(:verbalize_error) do
86
+ value = new(*args).send(method_name)
87
+ return Success.new(value)
88
+ end
89
+
90
+ Failure.new(error)
91
+ end
92
+
93
+ def __verbalized_send!(method_name, *args)
94
+ new(*args).send(method_name)
95
+ rescue UncaughtThrowError => uncaught_throw_error
96
+ fail_value = uncaught_throw_error.value
97
+ error = VerbalizeError.new("Unhandled fail! called with: #{fail_value.inspect}.")
98
+ error.set_backtrace(uncaught_throw_error.backtrace[2..-1])
99
+ raise error
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Verbalize
2
+ VerbalizeError = Class.new(StandardError)
3
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'result'
2
+
3
+ module Verbalize
4
+ class Failure < Result
5
+ extend Gem::Deprecate
6
+
7
+ def initialize(failure)
8
+ super(outcome: :error, value: failure)
9
+ end
10
+
11
+ def failure
12
+ @value
13
+ end
14
+
15
+ def value
16
+ warn Kernel.caller.first + ': `Verbalize::Failure#value` is deprecated; use `Verbalize::Failure#failure` '\
17
+ 'instead when explicitly handling failures. `Verbalize::Failure#value` will raise an exception in Verbalize '\
18
+ '2.0 to prevent accidental use of `#value` on failure results without explicit error handling. '
19
+ @value
20
+ end
21
+ end
22
+ end
@@ -5,7 +5,7 @@ module Verbalize
5
5
  @value = value
6
6
  end
7
7
 
8
- attr_reader :outcome, :value
8
+ attr_reader :outcome
9
9
 
10
10
  def succeeded?
11
11
  !failed?
@@ -18,7 +18,13 @@ module Verbalize
18
18
  alias_method :failure?, :failed?
19
19
 
20
20
  def to_ary
21
- [outcome, value]
21
+ [outcome, @value]
22
+ end
23
+
24
+ def value
25
+ warn Kernel.caller.first + ': `Verbalize::Result#value` is deprecated and will be removed in Verbalize 2.0. '\
26
+ 'Use `Verbalize::Failure#error` or `Verbalize::Success#value` instead.'
27
+ @value
22
28
  end
23
29
  end
24
30
  end
@@ -0,0 +1,11 @@
1
+ require_relative 'result'
2
+
3
+ module Verbalize
4
+ class Success < Result
5
+ def initialize(value)
6
+ super(outcome: :ok, value: value)
7
+ end
8
+
9
+ attr_reader :value
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Verbalize
2
- VERSION = '1.3.0'.freeze
2
+ VERSION = '1.4.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: verbalize
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zach Taylor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-11-09 00:00:00.000000000 Z
11
+ date: 2016-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,12 +113,16 @@ files:
113
113
  - bin/setup
114
114
  - circle.yml
115
115
  - lib/verbalize.rb
116
+ - lib/verbalize/action.rb
116
117
  - lib/verbalize/build_attribute_readers.rb
117
118
  - lib/verbalize/build_dangerous_action_method.rb
118
119
  - lib/verbalize/build_initialize_method.rb
119
120
  - lib/verbalize/build_method_base.rb
120
121
  - lib/verbalize/build_safe_action_method.rb
122
+ - lib/verbalize/error.rb
123
+ - lib/verbalize/failure.rb
121
124
  - lib/verbalize/result.rb
125
+ - lib/verbalize/success.rb
122
126
  - lib/verbalize/version.rb
123
127
  - verbalize.gemspec
124
128
  homepage: https://github.com/taylorzr/verbalize