metaractor 2.0.0 → 2.1.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: 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