cuprum 1.1.0 → 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
  SHA256:
3
- metadata.gz: 838e5d593a57d931c54b2ce0831699999bdddf6de2599fb2d074e0d31a0f80e0
4
- data.tar.gz: 721a9e2add543ec5e5f704bc6ed6b8e0f353ef7f6a1da0c3e67b4139d81f9d89
3
+ metadata.gz: dab108b9d6630d5ebb692b2a918654cc9a6a2cb6256a5aad7331b40bd2ea7f1d
4
+ data.tar.gz: 55e8f74553df29e8b244e69257c9178b3b8ea0b931c4f8b7a58ae4f587d17e27
5
5
  SHA512:
6
- metadata.gz: e7d4073ddf03b470f65e123bd196a778af7eb04ed0cdd3ec032d2ba06eb701ba19bf3b8fd92f3cfdc89fcaaed066f892e75b200a9040ff64261cbcb2b7d7c5d4
7
- data.tar.gz: aa8c21b9b7ad0dc0757304bfb580faa836b02b1ab152806324f3526a67bfcedab3ca70c141a6de55cf5f48cc47e29da8fffad7301dd10dadeaa279619f7bffab
6
+ metadata.gz: 3f520bbc963d86937b02b8f7849bb064afafa32e43d47e8e5b394f5ff598e05665ba366bf2bffc44f7a3964982c28dba90d7621ab11b6b5a981217a092b62e15
7
+ data.tar.gz: b3b1b63fa90af7d8f2edfd1d7db6ce5b14e2f10397907fd9dcd07138d4c0d5f70a0d1b538c5c7bd2cb1c9b0a10dfd94997c673ba293dbedfcb0855dda413c43d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0
4
+
5
+ The "Straight On Till Morning" Update
6
+
7
+ As of version 1.2.0, Cuprum will no longer support Ruby 2.7.
8
+
9
+ ### Results
10
+
11
+ Added the `#properties` method (aliased as `#to_h`) that wraps the result's `#value`, `#status`, and `#error`. Collecting the properties in one method supports defining results with additional or custom properties, such as a checksum or metadata.
12
+
13
+ The `Cuprum::Result#==` method was updated to compare the result's properties.
14
+
15
+ - A result can now be matched against a hash with `:value`, `:status`, and `:error` keys.
16
+ - Matching a result against a non-result object with its own `#value`, `#status`, and `#error` methods is now deprecated. Define a `#to_h` method on that object to wrap its properties for comparison with a result.
17
+
18
+ ### RSpec
19
+
20
+ Updated the `Cuprum::RSpec::BeAResultMatcher` to accept an optional result class.
21
+
3
22
  ## 1.1.0
4
23
 
5
24
  The "Second Star To The Right" Update
data/README.md CHANGED
@@ -30,7 +30,7 @@ On the opposite end of the scale, frameworks such as [Dry::Monads](https://dry-r
30
30
 
31
31
  ## Compatibility
32
32
 
33
- Cuprum is tested against Ruby (MRI) 2.7 through 3.2.
33
+ Cuprum is tested against Ruby (MRI) 3.0 through 3.2.
34
34
 
35
35
  ## Documentation
36
36
 
data/lib/cuprum/result.rb CHANGED
@@ -8,10 +8,10 @@ module Cuprum
8
8
  # Enumerates the default permitted values for a Result#status.
9
9
  STATUSES = %i[success failure].freeze
10
10
 
11
- # @param value [Object] The value returned by calling the command.
12
- # @param error [Object] The error (if any) generated when the command was
11
+ # @param value [Object] the value returned by calling the command.
12
+ # @param error [Object] the error (if any) generated when the command was
13
13
  # called. Can be a Cuprum::Error, a model errors object, etc.
14
- # @param status [String, Symbol] The status of the result. Must be :success,
14
+ # @param status [String, Symbol] the status of the result. Must be :success,
15
15
  # :failure, or nil.
16
16
  def initialize(value: nil, error: nil, status: nil)
17
17
  @value = value
@@ -31,18 +31,19 @@ module Cuprum
31
31
 
32
32
  # Compares the other object to the result.
33
33
  #
34
- # @param other [#value, #success?] An object responding to, at minimum,
35
- # #value and #success?. If present, the #failure? and #error values
36
- # will also be compared.
34
+ # In order to match the result, the object must respond to the #to_h method,
35
+ # and the value of object.to_h must be equal to the value of
36
+ # result.properties.
37
37
  #
38
- # @return [Boolean] True if all present values match the result, otherwise
39
- # false.
38
+ # @param other [#to_h] the result or object to compare.
39
+ #
40
+ # @return [Boolean] true if all values match the result, otherwise false.
40
41
  def ==(other)
41
- return false unless other.respond_to?(:value) && other.value == value
42
- return false unless other.respond_to?(:status) && other.status == status
43
- return false unless other.respond_to?(:error) && other.error == error
42
+ other = other.to_cuprum_result if other.respond_to?(:to_cuprum_result)
43
+
44
+ return properties == other.to_h if other.respond_to?(:to_h)
44
45
 
45
- true
46
+ deprecated_compare(other)
46
47
  end
47
48
 
48
49
  # @return [Boolean] true if the result status is :failure, otherwise false.
@@ -50,6 +51,16 @@ module Cuprum
50
51
  @status == :failure
51
52
  end
52
53
 
54
+ # @return [Hash{Symbol => Object}] a Hash representation of the result.
55
+ def properties
56
+ {
57
+ error: error,
58
+ status: status,
59
+ value: value
60
+ }
61
+ end
62
+ alias_method :to_h, :properties
63
+
53
64
  # @return [Boolean] true if the result status is :success, otherwise false.
54
65
  def success?
55
66
  @status == :success
@@ -66,6 +77,19 @@ module Cuprum
66
77
  self.class::STATUSES
67
78
  end
68
79
 
80
+ def deprecated_compare(other)
81
+ unless %i[value status error].all? { |sym| other.respond_to?(sym) }
82
+ return false
83
+ end
84
+
85
+ tools.core_tools.deprecate 'Cuprum::Result#==',
86
+ message: 'The compared object must respond to #to_h.'
87
+
88
+ other.value == value &&
89
+ other.status == status &&
90
+ other.error == error
91
+ end
92
+
69
93
  def normalize_status(status)
70
94
  return status unless status.is_a?(String) || status.is_a?(Symbol)
71
95
 
@@ -4,25 +4,31 @@ require 'cuprum/rspec/be_a_result_matcher'
4
4
 
5
5
  module Cuprum::RSpec
6
6
  module Matchers # rubocop:disable Style/Documentation
7
- # Asserts that the object is a Cuprum::Result with status: :failure.
7
+ # Asserts that the object is a result with status: :failure.
8
+ #
9
+ # @param expected_class [Class] the expected class of result. Defaults to
10
+ # Cuprum::Result.
8
11
  #
9
12
  # @return [Cuprum::RSpec::BeAResultMatcher] the generated matcher.
10
- def be_a_failing_result
11
- be_a_result.with_status(:failure)
13
+ def be_a_failing_result(expected_class = nil)
14
+ be_a_result(expected_class).with_status(:failure)
12
15
  end
13
16
 
14
17
  # Asserts that the object is a Cuprum::Result with status: :success.
15
18
  #
19
+ # @param expected_class [Class] the expected class of result. Defaults to
20
+ # Cuprum::Result.
21
+ #
16
22
  # @return [Cuprum::RSpec::BeAResultMatcher] the generated matcher.
17
- def be_a_passing_result
18
- be_a_result.with_status(:success).and_error(nil)
23
+ def be_a_passing_result(expected_class = nil)
24
+ be_a_result(expected_class).with_status(:success).and_error(nil)
19
25
  end
20
26
 
21
27
  # Asserts that the object is a Cuprum::Result.
22
28
  #
23
29
  # @return [Cuprum::RSpec::BeAResultMatcher] the generated matcher.
24
- def be_a_result
25
- Cuprum::RSpec::BeAResultMatcher.new
30
+ def be_a_result(expected_class = nil)
31
+ Cuprum::RSpec::BeAResultMatcher.new(expected_class)
26
32
  end
27
33
  end
28
34
  end
@@ -4,8 +4,68 @@ require 'cuprum/errors/operation_not_called'
4
4
  require 'cuprum/rspec'
5
5
 
6
6
  module Cuprum::RSpec
7
- # Custom matcher that asserts the actual object is a Cuprum result object with
8
- # the specified properties.
7
+ # Asserts the actual object is a result object with the specified properties.
8
+ #
9
+ # If initialized with a class, the matcher will assert that the actual object
10
+ # is an instance of that class. This can be useful for asserting that the
11
+ # result is an instance of a result subclass. If no class is given, the
12
+ # matcher asserts that the result is an object responding to
13
+ # #to_cuprum_result.
14
+ #
15
+ # The matcher also defines fluent methods for asserting on the result's
16
+ # properties:
17
+ #
18
+ # - The #with_value method asserts that the result has the specified value.
19
+ # Also aliased as #and_value.
20
+ # - The #with_error method asserts that the result has the specified error.
21
+ # Also aliased as #and_error.
22
+ # - The #with_status method asserts that the result has the specified status.
23
+ # Also aliased as #and_status.
24
+ #
25
+ # Generally speaking, you should use the #be_a_result, #be_a_passing_result,
26
+ # and #be_a_failing_result macros, rather than instantiating a
27
+ # BeAResultMatcher directly.
28
+ #
29
+ # @example Matching Any Result
30
+ # # Or use expect().to be_a_result
31
+ # matcher = Cuprum::RSpec::BeAResultMatcher.new
32
+ #
33
+ # matcher.matches?(nil) #=> false
34
+ # matcher.matches?(Cuprum::Result.new) #=> true
35
+ #
36
+ # @example Matching A Result Status
37
+ # # Or use expect().to be_a_passing_result
38
+ # matcher = Cuprum::RSpec::BeAResultMatcher.new.with_status(:success)
39
+ #
40
+ # matcher.matches?(Cuprum::Result.new(status: :failure)) #=> false
41
+ # matcher.matches?(Cuprum::Result.new(status: :success)) #=> false
42
+ #
43
+ # @example Matching A Result Value
44
+ # matcher = Cuprum::RSpec::BeAResultMatcher.new.with_value({ ok: true })
45
+ #
46
+ # matcher.matches?(Cuprum::Result.new(value: { ok: false })) #=> false
47
+ # matcher.matches?(Cuprum::Result.new(value: { ok: true })) #=> true
48
+ #
49
+ # @example Matching A Result Error
50
+ # error = Cuprum::Error.new(message: 'Something went wrong')
51
+ # matcher = Cuprum::RSpec::BeAResultMatcher.new.with_error(error)
52
+ #
53
+ # other_error = Cuprum::Error.new(message: 'Oh no')
54
+ # matcher.matches?(Cuprum::Result.new(error: other_error) #=> false
55
+ # matcher.matches?(Cuprum::Result.new(error: error) #=> true
56
+ #
57
+ # @example Matching A Result Class
58
+ # matcher = Cuprum::RSpec::BeAResultMatcher.new(CustomResult)
59
+ #
60
+ # matcher.matches?(Cuprum::Result.new) #=> false
61
+ # matcher.matches?(CustomResult.new) #=> true
62
+ #
63
+ # @example Matching Multiple Properties
64
+ # matcher =
65
+ # Cuprum::RSpec::BeAResultMatcher
66
+ # .with_status(:failure)
67
+ # .and_value({ ok: false })
68
+ # .and_error(Cuprum::Error.new(message: 'Something went wrong'))
9
69
  class BeAResultMatcher # rubocop:disable Metrics/ClassLength
10
70
  DEFAULT_VALUE = Object.new.freeze
11
71
  private_constant :DEFAULT_VALUE
@@ -13,15 +73,26 @@ module Cuprum::RSpec
13
73
  RSPEC_MATCHER_METHODS = %i[description failure_message matches?].freeze
14
74
  private_constant :RSPEC_MATCHER_METHODS
15
75
 
16
- def initialize
76
+ # @param expected_class [Class] the expected class of result. Defaults to
77
+ # Cuprum::Result.
78
+ def initialize(expected_class = nil)
79
+ @expected_class = expected_class
17
80
  @expected_error = DEFAULT_VALUE
18
81
  @expected_value = DEFAULT_VALUE
19
82
  end
20
83
 
84
+ # @return [Class] the expected class of result.
85
+ attr_reader :expected_class
86
+
21
87
  # @return [String] a short description of the matcher and expected
22
88
  # properties.
23
89
  def description
24
- message = 'be a Cuprum result'
90
+ message =
91
+ if expected_class
92
+ "be an instance of #{expected_class}"
93
+ else
94
+ 'be a Cuprum result'
95
+ end
25
96
 
26
97
  return message unless expected_properties?
27
98
 
@@ -30,7 +101,7 @@ module Cuprum::RSpec
30
101
 
31
102
  # Checks that the given actual object is not a Cuprum result.
32
103
  #
33
- # @param actual [Object] The actual object to match.
104
+ # @param actual [Object] the actual object to match.
34
105
  #
35
106
  # @return [Boolean] false if the actual object is a result; otherwise true.
36
107
  def does_not_match?(actual)
@@ -42,10 +113,12 @@ module Cuprum::RSpec
42
113
  end
43
114
 
44
115
  # @return [String] a summary message describing a failed expectation.
45
- def failure_message
116
+ def failure_message # rubocop:disable Metrics/MethodLength
46
117
  message = "expected #{actual.inspect} to #{description}"
47
118
 
48
- if !actual_is_result?
119
+ if !actual_is_result? && expected_class
120
+ "#{message}, but the object is not an instance of #{expected_class}"
121
+ elsif !actual_is_result?
49
122
  "#{message}, but the object is not a result"
50
123
  elsif actual_is_uncalled_operation?
51
124
  "#{message}, but the object is an uncalled operation"
@@ -72,7 +145,8 @@ module Cuprum::RSpec
72
145
  # @return [Boolean] true if the actual object is a result with the expected
73
146
  # properties; otherwise false.
74
147
  def matches?(actual)
75
- @actual = actual
148
+ @actual = actual
149
+ @non_matching_properties = nil
76
150
 
77
151
  actual_is_result? && !actual_is_uncalled_operation? && properties_match?
78
152
  end
@@ -125,7 +199,11 @@ module Cuprum::RSpec
125
199
  :expected_value
126
200
 
127
201
  def actual_is_result?
128
- actual.respond_to?(:to_cuprum_result)
202
+ return false unless actual.respond_to?(:to_cuprum_result)
203
+
204
+ return true unless expected_class
205
+
206
+ actual.to_cuprum_result.is_a?(expected_class)
129
207
  end
130
208
 
131
209
  def actual_is_uncalled_operation?
@@ -141,8 +219,8 @@ module Cuprum::RSpec
141
219
  def error_failure_message
142
220
  return '' if error_matches?
143
221
 
144
- "\n expected error: #{inspect_expected(expected_error)}" \
145
- "\n actual error: #{result.error.inspect}"
222
+ "#{pad_key('expected error')}#{inspect_expected(expected_error)}" \
223
+ "#{pad_key('actual error')}#{result.error.inspect}"
146
224
  end
147
225
 
148
226
  def error_matches?
@@ -153,10 +231,18 @@ module Cuprum::RSpec
153
231
  @error_matches = compare_items(expected_error, result.error)
154
232
  end
155
233
 
234
+ def expected_properties
235
+ expected = {}
236
+
237
+ expected['error'] = expected_error if expected_error?
238
+ expected['status'] = expected_status if expected_status?
239
+ expected['value'] = expected_value if expected_value?
240
+
241
+ expected
242
+ end
243
+
156
244
  def expected_properties?
157
- (expected_error? && !expected_error.nil?) ||
158
- expected_status? ||
159
- expected_value?
245
+ expected_properties.any?
160
246
  end
161
247
 
162
248
  def expected_error?
@@ -182,12 +268,23 @@ module Cuprum::RSpec
182
268
  ' positives, since any other result will match.'
183
269
  end
184
270
 
185
- # rubocop:disable Metrics/AbcSize
271
+ def non_matching_properties
272
+ @non_matching_properties ||=
273
+ expected_properties
274
+ .each_key
275
+ .reject { |key| send(:"#{key}_matches?") }
276
+ end
277
+
278
+ def pad_key(str)
279
+ # Value 11 from " expected " prefix.
280
+ len = 11 + non_matching_properties.map(&:length).max
281
+
282
+ "\n#{format("%#{len}s", str)}: "
283
+ end
284
+
186
285
  def properties_description
187
286
  msg = ''
188
- ary = []
189
- ary << 'value' if expected_value?
190
- ary << 'error' if expected_error? && !expected_error.nil?
287
+ ary = expected_properties.except('status').keys
191
288
 
192
289
  unless ary.empty?
193
290
  msg = "with the expected #{tools.array_tools.humanize_list(ary)}"
@@ -199,34 +296,28 @@ module Cuprum::RSpec
199
296
 
200
297
  msg + " and status: #{expected_status.inspect}"
201
298
  end
202
- # rubocop:enable Metrics/AbcSize
203
299
 
204
300
  def properties_failure_message
205
- properties_short_message +
206
- status_failure_message +
207
- value_failure_message +
208
- error_failure_message
301
+ expected_properties
302
+ .each_key
303
+ .map { |key| send(:"#{key}_failure_message") }
304
+ .unshift(properties_short_message)
305
+ .join
209
306
  end
210
307
 
211
308
  def properties_match?
212
- error_matches? && status_matches? && value_matches?
309
+ non_matching_properties.empty?
213
310
  end
214
311
 
215
312
  def properties_short_message
216
- ary = []
217
- ary << 'status' unless status_matches?
218
- ary << 'value' unless value_matches?
219
- ary << 'error' unless error_matches?
313
+ ary = non_matching_properties
220
314
 
221
315
  ", but the #{tools.array_tools.humanize_list(ary)}" \
222
316
  " #{tools.integer_tools.pluralize(ary.size, 'does', 'do')} not match:"
223
317
  end
224
318
 
225
319
  def properties_warning
226
- ary = []
227
- ary << 'value' if expected_value?
228
- ary << 'status' if expected_status?
229
- ary << 'error' if expected_error?
320
+ ary = expected_properties.keys
230
321
 
231
322
  return '' if ary.empty?
232
323
 
@@ -250,8 +341,8 @@ module Cuprum::RSpec
250
341
  def status_failure_message
251
342
  return '' if status_matches?
252
343
 
253
- "\n expected status: #{expected_status.inspect}" \
254
- "\n actual status: #{result.status.inspect}"
344
+ "#{pad_key('expected status')}#{expected_status.inspect}" \
345
+ "#{pad_key('actual status')}#{result.status.inspect}"
255
346
  end
256
347
 
257
348
  def status_matches?
@@ -269,8 +360,8 @@ module Cuprum::RSpec
269
360
  def value_failure_message
270
361
  return '' if value_matches?
271
362
 
272
- "\n expected value: #{inspect_expected(expected_value)}" \
273
- "\n actual value: #{result.value.inspect}"
363
+ "#{pad_key('expected value')}#{inspect_expected(expected_value)}" \
364
+ "#{pad_key('actual value')}#{result.value.inspect}"
274
365
  end
275
366
 
276
367
  def value_matches?
@@ -10,7 +10,7 @@ module Cuprum
10
10
  # Major version.
11
11
  MAJOR = 1
12
12
  # Minor version.
13
- MINOR = 1
13
+ MINOR = 2
14
14
  # Patch version.
15
15
  PATCH = 0
16
16
  # Prerelease version.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cuprum
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob "Merlin" Smith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-03-01 00:00:00.000000000 Z
11
+ date: 2023-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sleeping_king_studios-tools
@@ -24,76 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '3.10'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '3.10'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec-sleeping_king_studios
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '2.7'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '2.7'
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.31'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.31'
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop-rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '2.11'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '2.11'
83
- - !ruby/object:Gem::Dependency
84
- name: simplecov
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '0.21'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '0.21'
97
27
  description: |-
98
28
  An opinionated implementation of the Command pattern for Ruby applications.
99
29
  Cuprum wraps your business logic in a consistent, object-oriented interface
@@ -159,16 +89,16 @@ require_paths:
159
89
  - lib
160
90
  required_ruby_version: !ruby/object:Gem::Requirement
161
91
  requirements:
162
- - - ">="
92
+ - - "~>"
163
93
  - !ruby/object:Gem::Version
164
- version: 2.6.0
94
+ version: '3.0'
165
95
  required_rubygems_version: !ruby/object:Gem::Requirement
166
96
  requirements:
167
97
  - - ">="
168
98
  - !ruby/object:Gem::Version
169
99
  version: '0'
170
100
  requirements: []
171
- rubygems_version: 3.4.1
101
+ rubygems_version: 3.4.14
172
102
  signing_key:
173
103
  specification_version: 4
174
104
  summary: An opinionated implementation of the Command pattern.