class Dry::Validation::Contract
Contract
objects apply rules to input
A contract consists of a schema and rules. The schema is applied to the input before rules are applied, this way you can be sure that your rules won’t be applied to values that didn’t pass schema checks.
It’s up to you how exactly you’re going to separate schema checks from your rules.
@example
class NewUserContract < Dry::Validation::Contract params do required(:email).filled(:string) required(:age).filled(:integer) optional(:login).maybe(:string, :filled?) optional(:password).maybe(:string, min_size?: 10) optional(:password_confirmation).maybe(:string) end rule(:password) do key.failure('is required') if values[:login] && !values[:password] end rule(:age) do key.failure('must be greater or equal 18') if values[:age] < 18 end end new_user_contract = NewUserContract.new new_user_contract.call(email: 'jane@doe.org', age: 21)
@api public
Extension to use dry-logic predicates as macros.
@see Dry::Validation::PredicateRegistry::WHITELIST Available predicates
@example
Dry::Validation.load_extensions(:predicates_as_macros) class ApplicationContract < Dry::Validation::Contract import_predicates_as_macros end class AgeContract < ApplicationContract schema do required(:age).filled(:integer) end rule(:age).validate(gteq?: 18) end AgeContract.new.(age: 17).errors.first.text # => 'must be greater than or equal to 18'
@api public
Public Class Methods
Make macros available for self and its descendants.
# File lib/dry/validation/extensions/predicates_as_macros.rb, line 58 def self.import_predicates_as_macros registry = PredicateRegistry.new PredicateRegistry::WHITELIST.each do |name| register_macro(name) do |macro:| predicate_args = [*macro.args, value] message_opts = registry.message_opts(name, predicate_args) key.failure(name, message_opts) unless registry.(name, predicate_args) end end end
Public Instance Methods
Apply the contract to an input
@param [Hash] input The input to validate @param [Hash] context Initial context for rules
@return [Result]
@api public rubocop: disable Metrics/AbcSize
# File lib/dry/validation/contract.rb, line 92 def call(input, context = EMPTY_HASH) context_map = Concurrent::Map.new.tap do |map| default_context.each { |key, value| map[key] = value } context.each { |key, value| map[key] = value } end Result.new(schema.(input), context_map) do |result| rules.each do |rule| next if rule.keys.any? { |key| error?(result, key) } rule_result = rule.(self, result) rule_result.failures.each do |failure| result.add_error(message_resolver.(**failure)) end end end end
Return a nice string representation
@return [String]
@api public
# File lib/dry/validation/contract.rb, line 117 def inspect %(#<#{self.class} schema=#{schema.inspect} rules=#{rules.inspect}>) end
Private Instance Methods
@api private
# File lib/dry/validation/contract.rb, line 124 def error?(result, spec) path = Schema::Path[spec] if path.multi_value? return path.expand.any? { |nested_path| error?(result, nested_path) } end return true if result.schema_error?(path) path .to_a[0..-2] .each_with_index .any? { |_key, index| curr_path = Schema::Path[path.keys[0..index]] return false unless result.schema_error?(curr_path) result.errors.any? { |err| (other = Schema::Path[err.path]).same_root?(curr_path) && other == curr_path } } end
Get a registered macro
@return [Proc,#to_proc]
@api private
# File lib/dry/validation/contract.rb, line 152 def macro(name, *args) (macros.key?(name) ? macros[name] : Macros[name]).with(args) end
Return configured messages backend
@return [Dry::Schema::Messages::YAML, Dry::Schema::Messages::I18n]
@api private
# File lib/dry/validation/contract.rb, line 161 def messages self.class.messages end