Skip to content

Commit

Permalink
Merge pull request #30 from michaelherold/add-i18n-support
Browse files Browse the repository at this point in the history
Add I18n configuration support
  • Loading branch information
michaelherold authored Oct 9, 2019
2 parents a705cbf + 55a05d7 commit 21dee41
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. This projec
### Added

* [#27](https://github.com/michaelherold/interactor-contracts/pull/27): Upgrade dry-validation to 1.0 - [@vaihtovirta](https://github.com/vaihtovirta).
* [#30](https://github.com/michaelherold/interactor-contracts/pull/30): Allow setting a custom I18n backend for contract messages - [@michaelherold](https://github.com/michaelherold).

### Changed

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ group :development do
end

group :development, :test do
gem 'i18n'
gem 'pry'
gem 'rake', '< 11'
end
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,36 @@ result.failure? #=> true

[dry-validation]: https://github.com/dryrb/dry-validation

### I18n support

You can [configure the underlying `dry-validation` contract][config] by passing
a block to the `config` method in your contract. This block will be evaluated on
the underlying configuration for the contract. For example, if you want to set
up the contract to use I18n in your Rails app, you might do something like this:

```ruby
class MyInteractor
include Interactor
include Interactor::Contracts

config do
messages.backend = :i18n
messages.load_paths << Rails.root / 'config' / 'locales' / 'errors.yml'
messages.top_namespace = :interactor_contracts
end
end
```

This sets up the I18n system (assuming the delicate load-order has been done in
the right way - you have to require `i18n` prior to requiring
`interactor-contracts` since we load `dry-validation` immediately) to use your
custom file. All lookups for error messages happen starting at the
`interactor_contracts` key in this example.

See [the documentation for `dry-validation`][config] for more information.

[config]: https://dry-rb.org/gems/dry-validation/1.0/configuration/

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run
Expand Down
10 changes: 10 additions & 0 deletions lib/interactor/contracts/contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ def add_expectation(&term)
expectations.add(&term)
end

# Configures the underlying contracts for the validation schemata
#
# @api private
# @private
# @return [void]
def config(&block)
promises.config(&block)
expectations.config(&block)
end

# The consequences for the Contract
#
# @example
Expand Down
21 changes: 21 additions & 0 deletions lib/interactor/contracts/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,27 @@ def promises(&block)
end
alias assures promises

# Sends configuration set up to the underlying contracts in the terms
#
# @example
# class CreatePerson
# include Interactor
# include Interactor::Contracts
#
# config do
# messages.backend = :i18n
# messages.top_namespace = :my_app
# messages.load_paths << File.join(__dir__, '..', 'errors.yml')
# end
# end
#
# @api public
# @params [Block] block the block to execute for the underlying contracts
# @return [void]
def config(&block)
contract.config(&block)
end

# The Contract to enforce on calls to the Interactor
#
# @example
Expand Down
13 changes: 13 additions & 0 deletions lib/interactor/contracts/terms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def initialize(terms = Class.new(Dry::Validation::Contract))
# @return [void]
def add(&term)
@terms = Class.new(Dry::Validation::Contract).tap do |new_terms|
new_terms.instance_variable_set(
:@config,
@terms.instance_variable_get(:@config).dup
)
new_terms.params(@terms.schema, &term)
end
end
Expand All @@ -52,6 +56,15 @@ def call(context)
Outcome.new(@terms.new.call(context.to_h))
end

# Configures the underlying contracts within the terms
#
# @api private
# @private
# @return [void]
def config(&block)
@terms.config.instance_exec(&block)
end

private

# Defines no-op rules block if no schema has been added
Expand Down
34 changes: 34 additions & 0 deletions spec/interactor/contracts/dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,40 @@
require 'spec_helper'

RSpec.describe Interactor::Contracts::DSL do
describe '.config' do
it 'allows you to configure the messaging for the contracts' do
require 'i18n'
require 'dry/schema/messages/i18n'

klass = Class.new do
include Interactor
include Interactor::Contracts

config do
messages.backend = :i18n
messages.top_namespace = :my_app
messages.load_paths << File.expand_path(
File.join('..', '..', 'support', 'errors.yml'),
__dir__
)
end

expects do
required(:bar).filled
end

on_breach do |breaches|
context.fail!(message: breaches.to_h)
end
end

result = klass.call

expect(result).to be_a_failure
expect(result.message).to eq(bar: ['bar is foobared'])
end
end

describe '.expects' do
subject(:interactor_call) { klass.call(context) }

Expand Down
4 changes: 4 additions & 0 deletions spec/support/errors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
en:
my_app:
errors:
key?: 'is foobared'

0 comments on commit 21dee41

Please sign in to comment.