verbalize 1.3.0 → 1.4.1

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 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