metaractor 2.0.0 → 2.1.0

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
  SHA256:
3
- metadata.gz: 43fa8164083ceda9dca36dc6584da4bf3ac511e67d0f7a2232468ae841f8bd56
4
- data.tar.gz: 1ed5b56c2524f9f73048a8f9f79c2ab4515bafe0749a60122b92841224b5194c
3
+ metadata.gz: 6e1ab1a1cd9e5ab11a43462021e80787506646d87cf41816ce67396a3cf5da9d
4
+ data.tar.gz: e9b61e4f8181d10ae02d2b84f712f30eb9a36e457d1dac7dff6a43c802b93785
5
5
  SHA512:
6
- metadata.gz: 535d587dde182fd7c298f050eefb6a560a35f27ffba8e90afd117a0395f760374b88794c09e2cd84821ba760b26ee0322bd1fd95d8d9b0b2c22dceac8b7cac14
7
- data.tar.gz: 8571061ff4a4fcd37d3592a82273db4bf308f394b92bb7f36b2241fbbb07cbca4c2e1b05882af339c4a99891efc0688e23d4f7643e12842e200e18379ac78ec4
6
+ metadata.gz: eb29390b6649c80867a4455e642cd75bc1fbc6732b583e4441f5fc0a42b3dee14ede6ceaad933322073e3f0dcc328e936e2d9c3f120ea97e75e38acb01cbe1d8
7
+ data.tar.gz: faa366cc5ad09fed640918a295696e3089a9ef96de61ce3cb073c0a6ee029be58c73db16bc056f781f9b795c776149437c7cfddc0bd84d2182d7ca5da3dabb96
data/README.md CHANGED
@@ -104,6 +104,143 @@ before do
104
104
  end
105
105
  ```
106
106
 
107
+ ### Structured Errors
108
+ As of v2.0.0, metaractor supports structured errors.
109
+ ```ruby
110
+ class UpdateUser
111
+ include Metaractor
112
+
113
+ optional :is_admin
114
+ optional :user
115
+
116
+ def call
117
+ fail_with_error!(
118
+ errors: {
119
+ base: 'Invalid configuration',
120
+ is_admin: 'must be true or false',
121
+ user: [ title: 'cannot be blank', username: ['must be unique', 'must not be blank'] ]
122
+ }
123
+ )
124
+ end
125
+ end
126
+
127
+ result = UpdateUser.call
128
+ result.error_messages
129
+ # => [
130
+ # 'Invalid configuration',
131
+ # 'is_admin must be true or false',
132
+ # 'user.title cannot be blank',
133
+ # 'user.username must be unique',
134
+ # 'user.username must not be blank'
135
+ # ]
136
+
137
+ result.errors.full_messages_for(:user)
138
+ # => [
139
+ # 'title cannot be blank',
140
+ # 'username must be unique',
141
+ # 'username must not be blank'
142
+ # ]
143
+
144
+ # The arguments to `slice` are a list of paths.
145
+ # In this case we're asking for the errors under `base` and also
146
+ # the errors found under user _and_ title.
147
+ result.errors.slice(:base, [:user, :title])
148
+ # => {
149
+ # base: 'Invalid configuration',
150
+ # user: { title: 'cannot be blank' }
151
+ # }
152
+
153
+ result.errors.to_h
154
+ # => {
155
+ # base: 'Invalid configuration',
156
+ # is_admin: 'must be true or false',
157
+ # user: {
158
+ # title: 'cannot be blank',
159
+ # username: ['must be unique', 'must not be blank']
160
+ # }
161
+ # }
162
+ ```
163
+
164
+ ### Spec Helpers
165
+ Enable the helpers and/or matchers:
166
+ ```ruby
167
+ RSpec.configure do |config|
168
+ config.include Metaractor::Spec::Helpers
169
+ config.include Metaractor::Spec::Matchers
170
+ end
171
+ ```
172
+
173
+ #### Helpers
174
+ - `context_creator`
175
+ ```ruby
176
+ # context_creator(error_message: nil, error_messages: [], errors: [], valid: nil, invalid: nil, success: nil, failure: nil, **attributes)
177
+
178
+ # Create a blank context:
179
+ context_creator
180
+
181
+ # Create a context with some data:
182
+ context_creator(message: message, user: user)
183
+
184
+ # Create an invalid context:
185
+ context_creator(error_message: "invalid context", invalid: true)
186
+
187
+ # Create a context with string errors:
188
+ context_creator(error_messages: ["That didn't work", "Neither did this"])
189
+
190
+ # Create a context with structured errors:
191
+ context_creator(
192
+ user: user,
193
+ errors: {
194
+ user: {
195
+ email: 'must be unique'
196
+ },
197
+ profile: {
198
+ first_name: 'cannot be blank'
199
+ }
200
+ }
201
+ )
202
+ ```
203
+
204
+ #### Matchers
205
+ - `include_errors`
206
+ ```ruby
207
+ result = context_creator(
208
+ errors: {
209
+ user: [
210
+ title: 'cannot be blank',
211
+ username: ['must be unique', 'must not be blank']
212
+ ]
213
+ }
214
+ )
215
+
216
+ expect(result).to include_errors(
217
+ 'username must be unique',
218
+ 'username must not be blank'
219
+ ).at_path(:user, :username)
220
+
221
+ expect(result).to include_errors('user.title cannot be blank')
222
+ ```
223
+
224
+ ### Error Output
225
+ Metaractor customizes the exception message for `Interactor::Failure`:
226
+ ```
227
+ Interactor::Failure:
228
+ Errors:
229
+ {:base=>"NOPE"}
230
+
231
+ Previously Called:
232
+ Chained
233
+
234
+ Context:
235
+ {:parent=>true, :chained=>true}
236
+ ```
237
+
238
+ You can further customize the exception message:
239
+ ```ruby
240
+ # Configure FailureOutput to use awesome_print
241
+ Metaractor::FailureOutput.hash_formatter = ->(hash) { hash.ai }
242
+ ```
243
+
107
244
  ### Further Reading
108
245
  For more examples of all of the above approaches, please see the specs.
109
246
 
@@ -0,0 +1,39 @@
1
+ module Metaractor
2
+ module FailureOutput
3
+ def self.format_hash(hash)
4
+ if @hash_formatter.nil?
5
+ @hash_formatter = ->(hash){ hash.inspect }
6
+ end
7
+
8
+ @hash_formatter.call(hash)
9
+ end
10
+
11
+ def self.hash_formatter=(callable)
12
+ @hash_formatter = callable
13
+ end
14
+
15
+ def to_s
16
+ str = ''
17
+
18
+ if !context.errors.empty?
19
+ str << "Errors:\n"
20
+ str << Metaractor::FailureOutput.format_hash(context.errors.to_h)
21
+ str << "\n\n"
22
+ end
23
+
24
+ if !context._called.empty?
25
+ str << "Previously Called:\n"
26
+ context._called.each do |interactor|
27
+ str << interactor.class.name.to_s
28
+ end
29
+ str << "\n\n"
30
+ end
31
+
32
+ str << "Context:\n"
33
+ str << Metaractor::FailureOutput.format_hash(context.to_h.reject{|k,_| k == :errors})
34
+ str
35
+ end
36
+ end
37
+ end
38
+
39
+ Interactor::Failure.send(:include, Metaractor::FailureOutput)
@@ -1,3 +1,3 @@
1
1
  module Metaractor
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
data/lib/metaractor.rb CHANGED
@@ -9,6 +9,7 @@ require 'metaractor/context_validity'
9
9
  require 'metaractor/chain_failures'
10
10
  require 'metaractor/fail_from_context'
11
11
  require 'metaractor/context_has_key'
12
+ require 'metaractor/failure_output'
12
13
 
13
14
  module Metaractor
14
15
  def self.included(base)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metaractor
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Schlesinger
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-03-17 00:00:00.000000000 Z
11
+ date: 2020-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: interactor
@@ -103,6 +103,7 @@ files:
103
103
  - lib/metaractor/context_validity.rb
104
104
  - lib/metaractor/errors.rb
105
105
  - lib/metaractor/fail_from_context.rb
106
+ - lib/metaractor/failure_output.rb
106
107
  - lib/metaractor/handle_errors.rb
107
108
  - lib/metaractor/parameters.rb
108
109
  - lib/metaractor/run_with_context.rb