README.md in metaractor-2.0.0 vs README.md in metaractor-2.1.0
- old
+ new
@@ -102,9 +102,146 @@
# Be careful with this approach as some user code may run before the parameter validation
require_parameter! :awesome if context.mode == :awesome
end
```
+### Structured Errors
+As of v2.0.0, metaractor supports structured errors.
+```ruby
+class UpdateUser
+ include Metaractor
+
+ optional :is_admin
+ optional :user
+
+ def call
+ fail_with_error!(
+ errors: {
+ base: 'Invalid configuration',
+ is_admin: 'must be true or false',
+ user: [ title: 'cannot be blank', username: ['must be unique', 'must not be blank'] ]
+ }
+ )
+ end
+end
+
+result = UpdateUser.call
+result.error_messages
+# => [
+# 'Invalid configuration',
+# 'is_admin must be true or false',
+# 'user.title cannot be blank',
+# 'user.username must be unique',
+# 'user.username must not be blank'
+# ]
+
+result.errors.full_messages_for(:user)
+# => [
+# 'title cannot be blank',
+# 'username must be unique',
+# 'username must not be blank'
+# ]
+
+# The arguments to `slice` are a list of paths.
+# In this case we're asking for the errors under `base` and also
+# the errors found under user _and_ title.
+result.errors.slice(:base, [:user, :title])
+# => {
+# base: 'Invalid configuration',
+# user: { title: 'cannot be blank' }
+# }
+
+result.errors.to_h
+# => {
+# base: 'Invalid configuration',
+# is_admin: 'must be true or false',
+# user: {
+# title: 'cannot be blank',
+# username: ['must be unique', 'must not be blank']
+# }
+# }
+```
+
+### Spec Helpers
+Enable the helpers and/or matchers:
+```ruby
+RSpec.configure do |config|
+ config.include Metaractor::Spec::Helpers
+ config.include Metaractor::Spec::Matchers
+end
+```
+
+#### Helpers
+- `context_creator`
+```ruby
+# context_creator(error_message: nil, error_messages: [], errors: [], valid: nil, invalid: nil, success: nil, failure: nil, **attributes)
+
+# Create a blank context:
+context_creator
+
+# Create a context with some data:
+context_creator(message: message, user: user)
+
+# Create an invalid context:
+context_creator(error_message: "invalid context", invalid: true)
+
+# Create a context with string errors:
+context_creator(error_messages: ["That didn't work", "Neither did this"])
+
+# Create a context with structured errors:
+context_creator(
+ user: user,
+ errors: {
+ user: {
+ email: 'must be unique'
+ },
+ profile: {
+ first_name: 'cannot be blank'
+ }
+ }
+)
+```
+
+#### Matchers
+- `include_errors`
+```ruby
+result = context_creator(
+ errors: {
+ user: [
+ title: 'cannot be blank',
+ username: ['must be unique', 'must not be blank']
+ ]
+ }
+)
+
+expect(result).to include_errors(
+ 'username must be unique',
+ 'username must not be blank'
+).at_path(:user, :username)
+
+expect(result).to include_errors('user.title cannot be blank')
+```
+
+### Error Output
+Metaractor customizes the exception message for `Interactor::Failure`:
+```
+Interactor::Failure:
+ Errors:
+ {:base=>"NOPE"}
+
+ Previously Called:
+ Chained
+
+ Context:
+ {:parent=>true, :chained=>true}
+```
+
+You can further customize the exception message:
+```ruby
+# Configure FailureOutput to use awesome_print
+Metaractor::FailureOutput.hash_formatter = ->(hash) { hash.ai }
+```
+
### Further Reading
For more examples of all of the above approaches, please see the specs.
## Development