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
Source
# 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
Make macros available for self and its descendants.
Public Instance Methods
Source
# File lib/dry/validation/contract.rb, line 92 def call(input, context = EMPTY_HASH) validate_input_type(input) 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
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
Source
# File lib/dry/validation/contract.rb, line 119 def inspect %(#<#{self.class} schema=#{schema.inspect} rules=#{rules.inspect}>) end
Return a nice string representation
@return [String]
@api public
Private Instance Methods
Source
# File lib/dry/validation/contract.rb, line 126 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
@api private
Source
# File lib/dry/validation/contract.rb, line 154 def macro(name, *args) (macros.key?(name) ? macros[name] : Macros[name]).with(args) end
Get a registered macro
@return [Proc,#to_proc]
@api private
Source
# File lib/dry/validation/contract.rb, line 163 def messages self.class.messages end
Return configured messages backend
@return [Dry::Schema::Messages::YAML, Dry::Schema::Messages::I18n]
@api private
Source
# File lib/dry/validation/contract.rb, line 167 def validate_input_type(input) return if input.is_a?(::Hash) raise ::ArgumentError, "Input must be a Hash. #{input.class} was given." end