Macros
This is an experimental feature and its API may change before 2.0.0
Macros are a simple way of streamlining rule definitions. Whenever you find yourself repeating the exact same type of validation rules, consider defining a macro to reduce duplication. You can either define globally available macros for all contracts, or a per-class macros where the class and its descandants will be able to access them.
Defining a global macro
To define a global macro you can use Dry::Validation.register_macro
API. Here's a simple example where we define a macro that checks format of a string:
Dry::Validation.register_macro(:email_format) do
unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
key.failure('not a valid email format')
end
end
Defining a macro for a contract class
Unlike global macros, contract macros are only available to the class and its descendants. To define a contract class macro you can use Dry::Validation::Contract.register_macro
. Here's the same example like above but we'll define a class macro:
class ApplicationContract < Dry::Validation::Contract
register_macro(:email_format) do
unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
key.failure('not a valid email format')
end
end
end
Using a macro with rules
To define a rule that will apply a macro, simply use Rule#validate
method:
class NewUserContract < ApplicationContract
params do
required(:email).filled(:string)
end
rule(:email).validate(:email_format)
end
Using i18n
backend with macros
If you want to provide a message for your macro, you need to set it under errors.%{your_macro}
path in the messages yaml file. Using our example with :email_format
macro, here's how it would look like:
en:
dry_validation:
errors:
email_format: "not a valid email format"
Then the macro definition needs to use the same identifier when setting a failure message:
class ApplicationContract < Dry::Validation::Contract
config.messages.backend = :i18n
register_macro(:email_format) do
unless /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i.match?(value)
key.failure(:email_format)
end
end
end
Macro with options
If you want a macro to be parameterized, you can achieve that by passing a hash with options where the keys are macro identifiers. This way you will have access to macro arguments in the rule block. Here's an example how we could define a macro that checks a minimum size of an array, where we pass the minimum size as an option.
First, let's define a :min_size
macro. Notice that we use a macro
parameter in the block, which is the instance of our macro that gives us access to its args
:
class ApplicationContract < Dry::Validation::Contract
register_macro(:min_size) do |macro:|
min = macro.args[0]
key.failure("must have at least #{min} elements") unless value.size >= min
end
end
With the macro defined, you can simply use this with Rule#validate
:
class NewUserContract < ApplicationContract
params do
required(:phone_numbers).value(:array)
end
rule(:phone_numbers).validate(min_size: 1)
end