All Files ( 91.69% covered at 62.48 hits/line )
63 files in total.
2371 relevant lines,
2174 lines covered and
197 lines missed.
(
91.69%
)
# frozen_string_literal: true
#--
# Copyright (c) 2004-2020 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
- 1
require "active_support"
- 1
require "active_support/rails"
- 1
require "active_model/version"
- 1
module ActiveModel
- 1
extend ActiveSupport::Autoload
- 1
autoload :Attribute
- 1
autoload :Attributes
- 1
autoload :AttributeAssignment
- 1
autoload :AttributeMethods
- 1
autoload :BlockValidator, "active_model/validator"
- 1
autoload :Callbacks
- 1
autoload :Conversion
- 1
autoload :Dirty
- 1
autoload :EachValidator, "active_model/validator"
- 1
autoload :ForbiddenAttributesProtection
- 1
autoload :Lint
- 1
autoload :Model
- 1
autoload :Name, "active_model/naming"
- 1
autoload :Naming
- 1
autoload :SecurePassword
- 1
autoload :Serialization
- 1
autoload :Translation
- 1
autoload :Type
- 1
autoload :Validations
- 1
autoload :Validator
- 1
eager_autoload do
- 1
autoload :Errors
- 1
autoload :Error
- 1
autoload :RangeError, "active_model/errors"
- 1
autoload :StrictValidationFailed, "active_model/errors"
- 1
autoload :UnknownAttributeError, "active_model/errors"
end
- 1
module Serializers
- 1
extend ActiveSupport::Autoload
- 1
eager_autoload do
- 1
autoload :JSON
end
end
- 1
def self.eager_load!
super
ActiveModel::Serializers.eager_load!
end
end
- 1
ActiveSupport.on_load(:i18n) do
- 1
I18n.load_path << File.expand_path("active_model/locale/en.yml", __dir__)
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/object/duplicable"
- 1
module ActiveModel
- 1
class Attribute # :nodoc:
- 1
class << self
- 1
def from_database(name, value_before_type_cast, type, value = nil)
- 110
FromDatabase.new(name, value_before_type_cast, type, nil, value)
end
- 1
def from_user(name, value_before_type_cast, type, original_attribute = nil)
- 65
FromUser.new(name, value_before_type_cast, type, original_attribute)
end
- 1
def with_cast_value(name, value_before_type_cast, type)
- 9
WithCastValue.new(name, value_before_type_cast, type)
end
- 1
def null(name)
- 11
Null.new(name)
end
- 1
def uninitialized(name, type)
- 11
Uninitialized.new(name, type)
end
end
- 1
attr_reader :name, :value_before_type_cast, :type
# This method should not be called directly.
# Use #from_database or #from_user
- 1
def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil)
- 210
@name = name
- 210
@value_before_type_cast = value_before_type_cast
- 210
@type = type
- 210
@original_attribute = original_attribute
- 210
@value = value unless value.nil?
end
- 1
def value
# `defined?` is cheaper than `||=` when we get back falsy values
- 220
@value = type_cast(value_before_type_cast) unless defined?(@value)
- 220
@value
end
- 1
def original_value
- 124
if assigned?
- 62
original_attribute.original_value
else
- 62
type_cast(value_before_type_cast)
end
end
- 1
def value_for_database
- 49
type.serialize(value)
end
- 1
def changed?
- 103
changed_from_assignment? || changed_in_place?
end
- 1
def changed_in_place?
- 41
has_been_read? && type.changed_in_place?(original_value_for_database, value)
end
- 1
def forgetting_assignment
- 47
with_value_from_database(value_for_database)
end
- 1
def with_value_from_user(value)
- 61
type.assert_valid_value(value)
- 60
self.class.from_user(name, value, type, original_attribute || self)
end
- 1
def with_value_from_database(value)
- 51
self.class.from_database(name, value, type)
end
- 1
def with_cast_value(value)
- 2
self.class.with_cast_value(name, value, type)
end
- 1
def with_type(type)
- 2
if changed_in_place?
with_value_from_user(value).with_type(type)
else
- 2
self.class.new(name, value_before_type_cast, type, original_attribute)
end
end
- 1
def type_cast(*)
raise NotImplementedError
end
- 1
def initialized?
- 126
true
end
- 1
def came_from_user?
false
end
- 1
def has_been_read?
- 47
defined?(@value)
end
- 1
def ==(other)
- 14
self.class == other.class &&
name == other.name &&
value_before_type_cast == other.value_before_type_cast &&
type == other.type
end
- 1
alias eql? ==
- 1
def hash
[self.class, name, value_before_type_cast, type].hash
end
- 1
def init_with(coder)
@name = coder["name"]
@value_before_type_cast = coder["value_before_type_cast"]
@type = coder["type"]
@original_attribute = coder["original_attribute"]
@value = coder["value"] if coder.map.key?("value")
end
- 1
def encode_with(coder)
coder["name"] = name
coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
coder["type"] = type if type
coder["original_attribute"] = original_attribute if original_attribute
coder["value"] = value if defined?(@value)
end
- 1
def original_value_for_database
- 13
if assigned?
- 4
original_attribute.original_value_for_database
else
- 9
_original_value_for_database
end
end
- 1
private
- 1
attr_reader :original_attribute
- 1
alias :assigned? :original_attribute
- 1
def initialize_dup(other)
- 150
if defined?(@value) && @value.duplicable?
- 4
@value = @value.dup
end
end
- 1
def changed_from_assignment?
- 103
assigned? && type.changed?(original_value, value, value_before_type_cast)
end
- 1
def _original_value_for_database
type.serialize(original_value)
end
- 1
class FromDatabase < Attribute # :nodoc:
- 1
def type_cast(value)
- 68
type.deserialize(value)
end
- 1
def _original_value_for_database
- 9
value_before_type_cast
end
- 1
private :_original_value_for_database
end
- 1
class FromUser < Attribute # :nodoc:
- 1
def type_cast(value)
- 70
type.cast(value)
end
- 1
def came_from_user?
!type.value_constructed_by_mass_assignment?(value_before_type_cast)
end
end
- 1
class WithCastValue < Attribute # :nodoc:
- 1
def type_cast(value)
- 69
value
end
- 1
def changed_in_place?
- 28
false
end
end
- 1
class Null < Attribute # :nodoc:
- 1
def initialize(name)
- 11
super(name, nil, Type.default_value)
end
- 1
def type_cast(*)
nil
end
- 1
def with_type(type)
- 7
self.class.with_cast_value(name, nil, type)
end
- 1
def with_value_from_database(value)
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
end
- 1
alias_method :with_value_from_user, :with_value_from_database
- 1
alias_method :with_cast_value, :with_value_from_database
end
- 1
class Uninitialized < Attribute # :nodoc:
- 1
UNINITIALIZED_ORIGINAL_VALUE = Object.new
- 1
def initialize(name, type)
- 11
super(name, nil, type)
end
- 1
def value
- 7
if block_given?
- 3
yield name
end
end
- 1
def original_value
UNINITIALIZED_ORIGINAL_VALUE
end
- 1
def value_for_database
end
- 1
def initialized?
- 4
false
end
- 1
def forgetting_assignment
dup
end
- 1
def with_type(type)
self.class.new(name, type)
end
end
- 1
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
end
end
# frozen_string_literal: true
- 1
require "active_model/attribute"
- 1
module ActiveModel
- 1
class Attribute # :nodoc:
- 1
class UserProvidedDefault < FromUser # :nodoc:
- 1
def initialize(name, value, type, database_default)
- 2
@user_provided_value = value
- 2
super(name, value, type, database_default)
end
- 1
def value_before_type_cast
- 14
if user_provided_value.is_a?(Proc)
- 6
@memoized_value_before_type_cast ||= user_provided_value.call
else
- 8
@user_provided_value
end
end
- 1
def with_type(type)
self.class.new(name, user_provided_value, type, original_attribute)
end
- 1
def marshal_dump
- 2
result = [
name,
value_before_type_cast,
type,
original_attribute,
]
- 2
result << value if defined?(@value)
- 2
result
end
- 1
def marshal_load(values)
- 2
name, user_provided_value, type, original_attribute, value = values
- 2
@name = name
- 2
@user_provided_value = user_provided_value
- 2
@type = type
- 2
@original_attribute = original_attribute
- 2
if values.length == 5
- 2
@value = value
end
end
- 1
private
- 1
attr_reader :user_provided_value
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/hash/keys"
- 1
module ActiveModel
- 1
module AttributeAssignment
- 1
include ActiveModel::ForbiddenAttributesProtection
# Allows you to set all the attributes by passing in a hash of attributes with
# keys matching the attribute names.
#
# If the passed hash responds to <tt>permitted?</tt> method and the return value
# of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
# exception is raised.
#
# class Cat
# include ActiveModel::AttributeAssignment
# attr_accessor :name, :status
# end
#
# cat = Cat.new
# cat.assign_attributes(name: "Gorby", status: "yawning")
# cat.name # => 'Gorby'
# cat.status # => 'yawning'
# cat.assign_attributes(status: "sleeping")
# cat.name # => 'Gorby'
# cat.status # => 'sleeping'
- 1
def assign_attributes(new_attributes)
- 66
unless new_attributes.respond_to?(:each_pair)
- 1
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
end
- 65
return if new_attributes.empty?
- 19
_assign_attributes(sanitize_for_mass_assignment(new_attributes))
end
- 1
alias attributes= assign_attributes
- 1
private
- 1
def _assign_attributes(attributes)
- 18
attributes.each do |k, v|
- 28
_assign_attribute(k, v)
end
end
- 1
def _assign_attribute(k, v)
- 28
setter = :"#{k}="
- 28
if respond_to?(setter)
- 24
public_send(setter, v)
else
- 4
raise UnknownAttributeError.new(self, k.to_s)
end
end
end
end
# frozen_string_literal: true
- 1
require "concurrent/map"
- 1
module ActiveModel
# Raised when an attribute is not defined.
#
# class User < ActiveRecord::Base
# has_many :pets
# end
#
# user = User.first
# user.pets.select(:id).first.user_id
# # => ActiveModel::MissingAttributeError: missing attribute: user_id
- 1
class MissingAttributeError < NoMethodError
end
# == Active \Model \Attribute \Methods
#
# Provides a way to add prefixes and suffixes to your methods as
# well as handling the creation of <tt>ActiveRecord::Base</tt>-like
# class methods such as +table_name+.
#
# The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
# * <tt>include ActiveModel::AttributeMethods</tt> in your class.
# * Call each of its methods you want to add, such as +attribute_method_suffix+
# or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
# * Define an +attributes+ method which returns a hash with each
# attribute name in your model as hash key and the attribute value as hash value.
# Hash keys must be strings.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::AttributeMethods
#
# attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
# attribute_method_suffix '_contrived?'
# attribute_method_prefix 'clear_'
# define_attribute_methods :name
#
# attr_accessor :name
#
# def attributes
# { 'name' => @name }
# end
#
# private
#
# def attribute_contrived?(attr)
# true
# end
#
# def clear_attribute(attr)
# send("#{attr}=", nil)
# end
#
# def reset_attribute_to_default!(attr)
# send("#{attr}=", 'Default Name')
# end
# end
- 1
module AttributeMethods
- 1
extend ActiveSupport::Concern
- 1
NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
- 1
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
- 1
included do
- 10
class_attribute :attribute_aliases, instance_writer: false, default: {}
- 10
class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
end
- 1
module ClassMethods
# Declares a method available for all attributes with the given prefix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
# #{prefix}#{attr}(*args, &block)
#
# to
#
# #{prefix}attribute(#{attr}, *args, &block)
#
# An instance method <tt>#{prefix}attribute</tt> must exist and accept
# at least the +attr+ argument.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_prefix 'clear_'
# define_attribute_methods :name
#
# private
#
# def clear_attribute(attr)
# send("#{attr}=", nil)
# end
# end
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.clear_name
# person.name # => nil
- 1
def attribute_method_prefix(*prefixes)
self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
undefine_attribute_methods
end
# Declares a method available for all attributes with the given suffix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
# #{attr}#{suffix}(*args, &block)
#
# to
#
# attribute#{suffix}(#{attr}, *args, &block)
#
# An <tt>attribute#{suffix}</tt> instance method must exist and accept at
# least the +attr+ argument.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_suffix '_short?'
# define_attribute_methods :name
#
# private
#
# def attribute_short?(attr)
# send(attr).length < 5
# end
# end
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.name_short? # => true
- 1
def attribute_method_suffix(*suffixes)
- 27
self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
- 8
undefine_attribute_methods
end
# Declares a method available for all attributes with the given prefix
# and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
# the method.
#
# #{prefix}#{attr}#{suffix}(*args, &block)
#
# to
#
# #{prefix}attribute#{suffix}(#{attr}, *args, &block)
#
# An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
# accept at least the +attr+ argument.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
# define_attribute_methods :name
#
# private
#
# def reset_attribute_to_default!(attr)
# send("#{attr}=", 'Default Name')
# end
# end
#
# person = Person.new
# person.name # => 'Gem'
# person.reset_name_to_default!
# person.name # => 'Default Name'
- 1
def attribute_method_affix(*affixes)
- 8
self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
- 4
undefine_attribute_methods
end
# Allows you to make aliases for attributes.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_suffix '_short?'
# define_attribute_methods :name
#
# alias_attribute :nickname, :name
#
# private
#
# def attribute_short?(attr)
# send(attr).length < 5
# end
# end
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.nickname # => "Bob"
# person.name_short? # => true
# person.nickname_short? # => true
- 1
def alias_attribute(new_name, old_name)
- 4
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
- 4
CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
- 4
attribute_method_matchers.each do |matcher|
- 4
matcher_new = matcher.method_name(new_name).to_s
- 4
matcher_old = matcher.method_name(old_name).to_s
- 4
define_proxy_call false, owner, matcher_new, matcher_old
end
end
end
# Is +new_name+ an alias?
- 1
def attribute_alias?(new_name)
attribute_aliases.key? new_name.to_s
end
# Returns the original name for the alias +name+
- 1
def attribute_alias(name)
attribute_aliases[name.to_s]
end
# Declares the attributes that should be prefixed and suffixed by
# <tt>ActiveModel::AttributeMethods</tt>.
#
# To use, pass attribute names (as strings or symbols). Be sure to declare
# +define_attribute_methods+ after you define any prefix, suffix or affix
# methods, or they will not hook in.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name, :age, :address
# attribute_method_prefix 'clear_'
#
# # Call to define_attribute_methods must appear after the
# # attribute_method_prefix, attribute_method_suffix or
# # attribute_method_affix declarations.
# define_attribute_methods :name, :age, :address
#
# private
#
# def clear_attribute(attr)
# send("#{attr}=", nil)
# end
# end
- 1
def define_attribute_methods(*attr_names)
- 9
CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
- 23
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
end
end
# Declares an attribute that should be prefixed and suffixed by
# <tt>ActiveModel::AttributeMethods</tt>.
#
# To use, pass an attribute name (as string or symbol). Be sure to declare
# +define_attribute_method+ after you define any prefix, suffix or affix
# method, or they will not hook in.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_suffix '_short?'
#
# # Call to define_attribute_method must appear after the
# # attribute_method_prefix, attribute_method_suffix or
# # attribute_method_affix declarations.
# define_attribute_method :name
#
# private
#
# def attribute_short?(attr)
# send(attr).length < 5
# end
# end
#
# person = Person.new
# person.name = 'Bob'
# person.name # => "Bob"
# person.name_short? # => true
- 1
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
- 29
CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
- 29
attribute_method_matchers.each do |matcher|
- 105
method_name = matcher.method_name(attr_name)
- 105
unless instance_method_already_implemented?(method_name)
- 104
generate_method = "define_method_#{matcher.target}"
- 104
if respond_to?(generate_method, true)
- 10
send(generate_method, attr_name.to_s, owner: owner)
else
- 94
define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
end
end
end
- 29
attribute_method_matchers_cache.clear
end
end
# Removes all the previously dynamically defined methods from the class.
#
# class Person
# include ActiveModel::AttributeMethods
#
# attr_accessor :name
# attribute_method_suffix '_short?'
# define_attribute_method :name
#
# private
#
# def attribute_short?(attr)
# send(attr).length < 5
# end
# end
#
# person = Person.new
# person.name = 'Bob'
# person.name_short? # => true
#
# Person.undefine_attribute_methods
#
# person.name_short? # => NoMethodError
- 1
def undefine_attribute_methods
- 21
generated_attribute_methods.module_eval do
- 21
undef_method(*instance_methods)
end
- 21
attribute_method_matchers_cache.clear
end
- 1
private
- 1
class CodeGenerator
- 1
class << self
- 1
def batch(owner, path, line)
- 42
if owner.is_a?(CodeGenerator)
- 14
yield owner
else
- 28
instance = new(owner, path, line)
- 28
result = yield instance
- 28
instance.execute
- 28
result
end
end
end
- 1
def initialize(owner, path, line)
- 28
@owner = owner
- 28
@path = path
- 28
@line = line
- 28
@sources = ["# frozen_string_literal: true\n"]
- 28
@renames = {}
end
- 1
def <<(source_line)
- 108
@sources << source_line
end
- 1
def rename_method(old_name, new_name)
@renames[old_name] = new_name
end
- 1
def execute
- 28
@owner.module_eval(@sources.join(";"), @path, @line - 1)
- 28
@renames.each do |old_name, new_name|
@owner.alias_method new_name, old_name
@owner.undef_method old_name
end
end
end
- 1
private_constant :CodeGenerator
- 1
def generated_attribute_methods
- 164
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
end
- 1
def instance_method_already_implemented?(method_name)
- 105
generated_attribute_methods.method_defined?(method_name)
end
# The methods +method_missing+ and +respond_to?+ of this module are
# invoked often in a typical rails, both of which invoke the method
# +matched_attribute_method+. The latter method iterates through an
# array doing regular expression matches, which results in a lot of
# object creations. Most of the time it returns a +nil+ match. As the
# match result is always the same given a +method_name+, this cache is
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
# this cache).
- 1
def attribute_method_matchers_cache
- 1409
@attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4)
end
- 1
def attribute_method_matchers_matching(method_name)
- 1359
attribute_method_matchers_cache.compute_if_absent(method_name) do
- 44
attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
end
end
# Define a method `name` in `mod` that dispatches to `send`
# using the given `extra` args. This falls back on `define_method`
# and `send` if the given names cannot be compiled.
- 1
def define_proxy_call(include_private, code_generator, name, target, *extra)
- 98
defn = if NAME_COMPILABLE_REGEXP.match?(name)
- 95
"def #{name}(*args)"
else
- 3
"define_method(:'#{name}') do |*args|"
end
- 98
extra = (extra.map!(&:inspect) << "*args").join(", ")
- 98
body = if CALL_COMPILABLE_REGEXP.match?(target)
- 97
"#{"self." unless include_private}#{target}(#{extra})"
else
- 1
"send(:'#{target}', #{extra})"
end
code_generator <<
defn <<
body <<
- 98
"end" <<
"ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
end
- 1
class AttributeMethodMatcher #:nodoc:
- 1
attr_reader :prefix, :suffix, :target
- 1
AttributeMethodMatch = Struct.new(:target, :attr_name)
- 1
def initialize(options = {})
- 33
@prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
- 33
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
- 33
@target = "#{@prefix}attribute#{@suffix}"
- 33
@method_name = "#{prefix}%s#{suffix}"
end
- 1
def match(method_name)
- 30
if @regex =~ method_name
- 19
AttributeMethodMatch.new(target, $1)
end
end
- 1
def method_name(attr_name)
- 113
@method_name % attr_name
end
end
end
# Allows access to the object attributes, which are held in the hash
# returned by <tt>attributes</tt>, as though they were first-class
# methods. So a +Person+ class with a +name+ attribute can for example use
# <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
# the attributes hash -- except for multiple assignments with
# <tt>ActiveRecord::Base#attributes=</tt>.
#
# It's also possible to instantiate related objects, so a <tt>Client</tt>
# class belonging to the +clients+ table with a +master_id+ foreign key
# can instantiate master through <tt>Client#master</tt>.
- 1
def method_missing(method, *args, &block)
- 9
if respond_to_without_attributes?(method, true)
- 2
super
else
- 7
match = matched_attribute_method(method.to_s)
- 7
match ? attribute_missing(match, *args, &block) : super
end
end
- 1
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
# +attribute_missing+ is like +method_missing+, but for attributes. When
# +method_missing+ is called we check to see if there is a matching
# attribute method. If so, we tell +attribute_missing+ to dispatch the
# attribute. This method can be overloaded to customize the behavior.
- 1
def attribute_missing(match, *args, &block)
- 3
__send__(match.target, match.attr_name, *args, &block)
end
# A +Person+ instance with a +name+ attribute can ask
# <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
# and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
- 1
alias :respond_to_without_attributes? :respond_to?
- 1
def respond_to?(method, include_private_methods = false)
- 1379
if super
- 25
true
- 1354
elsif !include_private_methods && super(method, true)
# If we're here then we haven't found among non-private methods
# but found among all methods. Which means that the given method is private.
- 2
false
else
- 1352
!matched_attribute_method(method.to_s).nil?
end
end
- 1
private
- 1
def attribute_method?(attr_name)
- 1811
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
- 1
def matched_attribute_method(method_name)
- 1359
matches = self.class.send(:attribute_method_matchers_matching, method_name)
- 3170
matches.detect { |match| attribute_method?(match.attr_name) }
end
- 1
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
- 1
def _read_attribute(attr)
- 67
__send__(attr)
end
- 1
module AttrNames # :nodoc:
- 1
DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
# We want to generate the methods via module_eval rather than
# define_method, because define_method is slower on dispatch.
# Evaluating many similar methods may use more memory as the instruction
# sequences are duplicated and cached (in MRI). define_method may
# be slower on dispatch, but if you're careful about the closure
# created, then define_method will consume much less memory.
#
# But sometimes the database might return columns with
# characters that are not allowed in normal method names (like
# 'my_column(omg)'. So to work around this we first define with
# the __temp__ identifier, and then use alias method to rename
# it to what we want.
#
# We are also defining a constant to hold the frozen string of
# the attribute name. Using a constant means that we do not have
# to allocate an object on each call to the attribute method.
# Making it frozen means that it doesn't get duped when used to
# key the @attributes in read_attribute.
- 1
def self.define_attribute_accessor_method(owner, attr_name, writer: false)
- 10
method_name = "#{attr_name}#{'=' if writer}"
- 10
if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
- 10
yield method_name, "'#{attr_name}'"
else
safe_name = attr_name.unpack1("h*")
const_name = "ATTR_#{safe_name}"
const_set(const_name, attr_name) unless const_defined?(const_name)
temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
yield temp_method_name, attr_name_expr
owner.rename_method(temp_method_name, method_name)
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/hash/indifferent_access"
- 1
require "active_support/core_ext/object/duplicable"
- 1
module ActiveModel
- 1
class AttributeMutationTracker # :nodoc:
- 1
OPTION_NOT_GIVEN = Object.new
- 1
def initialize(attributes)
- 61
@attributes = attributes
end
- 1
def changed_attribute_names
- 18
attr_names.select { |attr_name| changed?(attr_name) }
end
- 1
def changed_values
- 10
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
- 20
if changed?(attr_name)
- 8
result[attr_name] = original_value(attr_name)
end
end
end
- 1
def changes
- 27
attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result|
- 49
if change = change_to_attribute(attr_name)
- 34
result.merge!(attr_name => change)
end
end
end
- 1
def change_to_attribute(attr_name)
- 46
if changed?(attr_name)
- 31
[original_value(attr_name), fetch_value(attr_name)]
end
end
- 1
def any_changes?
- 46
attr_names.any? { |attr| changed?(attr) }
end
- 1
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
- 203
attribute_changed?(attr_name) &&
- 136
(OPTION_NOT_GIVEN == from || original_value(attr_name) == from) &&
- 130
(OPTION_NOT_GIVEN == to || fetch_value(attr_name) == to)
end
- 1
def changed_in_place?(attr_name)
attributes[attr_name].changed_in_place?
end
- 1
def forget_change(attr_name)
- 4
attributes[attr_name] = attributes[attr_name].forgetting_assignment
- 4
forced_changes.delete(attr_name)
end
- 1
def original_value(attr_name)
- 20
attributes[attr_name].original_value
end
- 1
def force_change(attr_name)
forced_changes[attr_name] = fetch_value(attr_name)
end
- 1
private
- 1
attr_reader :attributes
- 1
def forced_changes
- 370
@forced_changes ||= {}
end
- 1
def attr_names
- 25
attributes.keys
end
- 1
def attribute_changed?(attr_name)
- 97
forced_changes.include?(attr_name) || !!attributes[attr_name].changed?
end
- 1
def fetch_value(attr_name)
- 11
attributes.fetch_value(attr_name)
end
end
- 1
class ForcedMutationTracker < AttributeMutationTracker # :nodoc:
- 1
def initialize(attributes)
- 30
super
- 30
@finalized_changes = nil
end
- 1
def changed_in_place?(attr_name)
false
end
- 1
def change_to_attribute(attr_name)
- 31
if finalized_changes&.include?(attr_name)
- 8
finalized_changes[attr_name].dup
else
- 23
super
end
end
- 1
def forget_change(attr_name)
- 4
forced_changes.delete(attr_name)
end
- 1
def original_value(attr_name)
- 41
if changed?(attr_name)
- 41
forced_changes[attr_name]
else
fetch_value(attr_name)
end
end
- 1
def force_change(attr_name)
- 44
forced_changes[attr_name] = clone_value(attr_name) unless attribute_changed?(attr_name)
end
- 1
def finalize_changes
- 12
@finalized_changes = changes
end
- 1
private
- 1
attr_reader :finalized_changes
- 1
def attr_names
- 36
forced_changes.keys
end
- 1
def attribute_changed?(attr_name)
- 150
forced_changes.include?(attr_name)
end
- 1
def fetch_value(attr_name)
- 67
attributes.send(:_read_attribute, attr_name)
end
- 1
def clone_value(attr_name)
- 38
value = fetch_value(attr_name)
- 38
value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
value
end
end
- 1
class NullMutationTracker # :nodoc:
- 1
include Singleton
- 1
def changed_attribute_names
[]
end
- 1
def changed_values
{}
end
- 1
def changes
- 2
{}
end
- 1
def change_to_attribute(attr_name)
end
- 1
def any_changes?
false
end
- 1
def changed?(attr_name, **)
false
end
- 1
def changed_in_place?(attr_name)
false
end
- 1
def original_value(attr_name)
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/enumerable"
- 1
require "active_support/core_ext/object/deep_dup"
- 1
require "active_model/attribute_set/builder"
- 1
require "active_model/attribute_set/yaml_encoder"
- 1
module ActiveModel
- 1
class AttributeSet # :nodoc:
- 1
delegate :each_value, :fetch, :except, to: :attributes
- 1
def initialize(attributes)
- 87
@attributes = attributes
end
- 1
def [](name)
- 448
@attributes[name] || default_attribute(name)
end
- 1
def []=(name, value)
- 14
@attributes[name] = value
end
- 1
def values_before_type_cast
- 1
attributes.transform_values(&:value_before_type_cast)
end
- 1
def to_hash
- 50
keys.index_with { |name| self[name].value }
end
- 1
alias :to_h :to_hash
- 1
def key?(name)
attributes.key?(name) && self[name].initialized?
end
- 1
def keys
- 144
attributes.each_key.select { |name| self[name].initialized? }
end
- 1
def fetch_value(name, &block)
- 34
self[name].value(&block)
end
- 1
def write_from_database(name, value)
- 3
@attributes[name] = self[name].with_value_from_database(value)
end
- 1
def write_from_user(name, value)
- 56
raise FrozenError, "can't modify frozen attributes" if frozen?
- 55
@attributes[name] = self[name].with_value_from_user(value)
- 55
value
end
- 1
def write_cast_value(name, value)
@attributes[name] = self[name].with_cast_value(value)
value
end
- 1
def freeze
- 3
attributes.freeze
- 3
super
end
- 1
def deep_dup
- 41
AttributeSet.new(attributes.deep_dup)
end
- 1
def initialize_dup(_)
- 1
@attributes = @attributes.dup
- 1
super
end
- 1
def initialize_clone(_)
- 2
@attributes = @attributes.clone
- 2
super
end
- 1
def reset(key)
if key?(key)
write_from_database(key, nil)
end
end
- 1
def accessed
- 6
attributes.each_key.select { |name| self[name].has_been_read? }
end
- 1
def map(&block)
- 15
new_attributes = attributes.transform_values(&block)
- 15
AttributeSet.new(new_attributes)
end
- 1
def ==(other)
- 3
attributes == other.attributes
end
- 1
protected
- 1
attr_reader :attributes
- 1
private
- 1
def default_attribute(name)
- 7
Attribute.null(name)
end
end
end
# frozen_string_literal: true
- 1
require "active_model/attribute"
- 1
module ActiveModel
- 1
class AttributeSet # :nodoc:
- 1
class Builder # :nodoc:
- 1
attr_reader :types, :default_attributes
- 1
def initialize(types, default_attributes = {})
- 26
@types = types
- 26
@default_attributes = default_attributes
end
- 1
def build_from_database(values = {}, additional_types = {})
- 27
LazyAttributeSet.new(values, types, additional_types, default_attributes)
end
end
end
- 1
class LazyAttributeSet < AttributeSet # :nodoc:
- 1
def initialize(values, types, additional_types, default_attributes, attributes = {})
- 27
super(attributes)
- 27
@values = values
- 27
@types = types
- 27
@additional_types = additional_types
- 27
@default_attributes = default_attributes
- 27
@casted_values = {}
- 27
@materialized = false
end
- 1
def key?(name)
- 4
(values.key?(name) || types.key?(name) || @attributes.key?(name)) && self[name].initialized?
end
- 1
def keys
- 7
keys = values.keys | types.keys | @attributes.keys
- 19
keys.keep_if { |name| self[name].initialized? }
end
- 1
def fetch_value(name, &block)
- 11
if attr = @attributes[name]
- 3
return attr.value(&block)
end
- 8
@casted_values.fetch(name) do
- 8
value_present = true
- 14
value = values.fetch(name) { value_present = false }
- 8
if value_present
- 2
type = additional_types.fetch(name, types[name])
- 2
@casted_values[name] = type.deserialize(value)
else
- 6
attr = default_attribute(name, value_present, value)
- 6
attr.value(&block)
end
end
end
- 1
protected
- 1
def attributes
- 10
unless @materialized
- 23
values.each_key { |key| self[key] }
- 23
types.each_key { |key| self[key] }
- 8
@materialized = true
end
- 10
@attributes
end
- 1
private
- 1
attr_reader :values, :types, :additional_types, :default_attributes
- 1
def default_attribute(
name,
value_present = true,
- 7
value = values.fetch(name) { value_present = false }
)
- 43
type = additional_types.fetch(name, types[name])
- 43
if value_present
- 30
@attributes[name] = Attribute.from_database(name, value, type, @casted_values[name])
- 13
elsif types.key?(name)
- 9
if attr = default_attributes[name]
- 1
@attributes[name] = attr.dup
else
- 8
@attributes[name] = Attribute.uninitialized(name, type)
end
else
- 4
Attribute.null(name)
end
end
end
- 1
class LazyAttributeHash # :nodoc:
- 1
delegate :transform_values, :each_value, :fetch, :except, to: :materialize
- 1
def initialize(types, values, additional_types, default_attributes, delegate_hash = {})
- 2
@types = types
- 2
@values = values
- 2
@additional_types = additional_types
- 2
@materialized = false
- 2
@delegate_hash = delegate_hash
- 2
@default_attributes = default_attributes
end
- 1
def key?(key)
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
end
- 1
def [](key)
- 4
delegate_hash[key] || assign_default_value(key)
end
- 1
def []=(key, value)
delegate_hash[key] = value
end
- 1
def deep_dup
dup.tap do |copy|
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
end
end
- 1
def initialize_dup(_)
@delegate_hash = Hash[delegate_hash]
super
end
- 1
def each_key(&block)
- 1
keys = types.keys | values.keys | delegate_hash.keys
- 1
keys.each(&block)
end
- 1
def ==(other)
if other.is_a?(LazyAttributeHash)
materialize == other.materialize
else
materialize == other
end
end
- 1
def marshal_dump
[@types, @values, @additional_types, @default_attributes, @delegate_hash]
end
- 1
def marshal_load(values)
- 1
if values.is_a?(Hash)
- 1
ActiveSupport::Deprecation.warn(<<~MSG)
Marshalling load from legacy attributes format is deprecated and will be removed in Rails 6.2.
MSG
- 1
empty_hash = {}.freeze
- 1
initialize(empty_hash, empty_hash, empty_hash, empty_hash, values)
- 1
@materialized = true
else
initialize(*values)
end
end
- 1
protected
- 1
def materialize
- 1
unless @materialized
- 2
values.each_key { |key| self[key] }
- 2
types.each_key { |key| self[key] }
- 1
unless frozen?
- 1
@materialized = true
end
end
- 1
delegate_hash
end
- 1
private
- 1
attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
- 1
def assign_default_value(name)
- 1
type = additional_types.fetch(name, types[name])
- 1
value_present = true
- 1
value = values.fetch(name) { value_present = false }
- 1
if value_present
- 1
delegate_hash[name] = Attribute.from_database(name, value, type)
elsif types.key?(name)
attr = default_attributes[name]
if attr
delegate_hash[name] = attr.dup
else
delegate_hash[name] = Attribute.uninitialized(name, type)
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
class AttributeSet
# Attempts to do more intelligent YAML dumping of an
# ActiveModel::AttributeSet to reduce the size of the resulting string
- 1
class YAMLEncoder # :nodoc:
- 1
def initialize(default_types)
@default_types = default_types
end
- 1
def encode(attribute_set, coder)
coder["concise_attributes"] = attribute_set.each_value.map do |attr|
if attr.type.equal?(default_types[attr.name])
attr.with_type(nil)
else
attr
end
end
end
- 1
def decode(coder)
if coder["attributes"]
coder["attributes"]
else
attributes_hash = Hash[coder["concise_attributes"].map do |attr|
if attr.type.nil?
attr = attr.with_type(default_types[attr.name])
end
[attr.name, attr]
end]
AttributeSet.new(attributes_hash)
end
end
- 1
private
- 1
attr_reader :default_types
end
end
end
# frozen_string_literal: true
- 1
require "active_model/attribute_set"
- 1
require "active_model/attribute/user_provided_default"
- 1
module ActiveModel
- 1
module Attributes #:nodoc:
- 1
extend ActiveSupport::Concern
- 1
include ActiveModel::AttributeMethods
- 1
included do
- 2
attribute_method_suffix "="
- 2
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
- 2
self.attribute_types = Hash.new(Type.default_value)
- 2
self._default_attributes = AttributeSet.new({})
end
- 1
module ClassMethods
- 1
def attribute(name, type = Type::Value.new, **options)
- 10
name = name.to_s
- 10
if type.is_a?(Symbol)
- 10
type = ActiveModel::Type.lookup(type, **options.except(:default))
end
- 10
self.attribute_types = attribute_types.merge(name => type)
- 10
define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
- 10
define_attribute_method(name)
end
# Returns an array of attribute names as strings
#
# class Person
# include ActiveModel::Attributes
#
# attribute :name, :string
# attribute :age, :integer
# end
#
# Person.attribute_names
# # => ["name", "age"]
- 1
def attribute_names
- 1
attribute_types.keys
end
- 1
private
- 1
def define_method_attribute=(name, owner:)
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
owner, name, writer: true,
- 10
) do |temp_method_name, attr_name_expr|
owner <<
"def #{temp_method_name}(value)" <<
- 10
" _write_attribute(#{attr_name_expr}, value)" <<
"end"
end
end
- 1
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
- 1
private_constant :NO_DEFAULT_PROVIDED
- 1
def define_default_attribute(name, value, type)
- 10
self._default_attributes = _default_attributes.deep_dup
- 10
if value == NO_DEFAULT_PROVIDED
- 8
default_attribute = _default_attributes[name].with_type(type)
else
- 2
default_attribute = Attribute::UserProvidedDefault.new(
name,
value,
type,
- 2
_default_attributes.fetch(name.to_s) { nil },
)
end
- 10
_default_attributes[name] = default_attribute
end
end
- 1
def initialize(*)
- 29
@attributes = self.class._default_attributes.deep_dup
- 29
super
end
- 1
def initialize_dup(other) # :nodoc:
- 1
@attributes = @attributes.deep_dup
- 1
super
end
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
#
# class Person
# include ActiveModel::Attributes
#
# attribute :name, :string
# attribute :age, :integer
# end
#
# person = Person.new(name: 'Francesco', age: 22)
# person.attributes
# # => {"name"=>"Francesco", "age"=>22}
- 1
def attributes
- 5
@attributes.to_hash
end
# Returns an array of attribute names as strings
#
# class Person
# include ActiveModel::Attributes
#
# attribute :name, :string
# attribute :age, :integer
# end
#
# person = Person.new
# person.attribute_names
# # => ["name", "age"]
- 1
def attribute_names
- 1
@attributes.keys
end
- 1
def freeze
- 1
@attributes = @attributes.clone.freeze
- 1
super
end
- 1
private
- 1
def write_attribute(attr_name, value)
name = attr_name.to_s
name = self.class.attribute_aliases[name] || name
@attributes.write_from_user(name, value)
end
- 1
def _write_attribute(attr_name, value)
- 55
@attributes.write_from_user(attr_name, value)
end
- 1
alias :attribute= :_write_attribute
- 1
def read_attribute(attr_name)
name = attr_name.to_s
name = self.class.attribute_aliases[name] || name
@attributes.fetch_value(name)
end
- 1
def attribute(attr_name)
- 21
@attributes.fetch_value(attr_name)
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/array/extract_options"
- 1
require "active_support/core_ext/hash/keys"
- 1
module ActiveModel
# == Active \Model \Callbacks
#
# Provides an interface for any class to have Active Record like callbacks.
#
# Like the Active Record methods, the callback chain is aborted as soon as
# one of the methods throws +:abort+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
# class MyModel
# extend ActiveModel::Callbacks
# end
#
# Then define a list of methods that you want callbacks attached to:
#
# define_model_callbacks :create, :update
#
# This will provide all three standard callbacks (before, around and after)
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
# you need to wrap the methods you want callbacks on in a block so that the
# callbacks get a chance to fire:
#
# def create
# run_callbacks :create do
# # Your create action methods here
# end
# end
#
# Then in your class, you can use the +before_create+, +after_create+ and
# +around_create+ methods, just as you would in an Active Record model.
#
# before_create :action_before_create
#
# def action_before_create
# # Your code here
# end
#
# When defining an around callback remember to yield to the block, otherwise
# it won't be executed:
#
# around_create :log_status
#
# def log_status
# puts 'going to call the block...'
# yield
# puts 'block successfully called.'
# end
#
# You can choose to have only specific callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
# define_model_callbacks :create, only: [:after, :before]
#
# Would only create the +after_create+ and +before_create+ callback methods in
# your class.
#
# NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
#
- 1
module Callbacks
- 1
def self.extended(base) #:nodoc:
- 18
base.class_eval do
- 18
include ActiveSupport::Callbacks
end
end
# define_model_callbacks accepts the same options +define_callbacks+ does,
# in case you want to overwrite a default. Besides that, it also accepts an
# <tt>:only</tt> option, where you can choose if you want all types (before,
# around or after) or just some.
#
# define_model_callbacks :initializer, only: :after
#
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
# on that method call. To get around this you can call the define_model_callbacks
# method as many times as you need.
#
# define_model_callbacks :create, only: :after
# define_model_callbacks :update, only: :before
# define_model_callbacks :destroy, only: :around
#
# Would create +after_create+, +before_update+ and +around_destroy+ methods
# only.
#
# You can pass in a class to before_<type>, after_<type> and around_<type>,
# in which case the callback will call that class's <action>_<type> method
# passing the object that the callback is being called on.
#
# class MyModel
# extend ActiveModel::Callbacks
# define_model_callbacks :create
#
# before_create AnotherClass
# end
#
# class AnotherClass
# def self.before_create( obj )
# # obj is the MyModel instance that the callback is being called on
# end
# end
#
# NOTE: +method_name+ passed to define_model_callbacks must not end with
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
- 1
def define_model_callbacks(*callbacks)
- 7
options = callbacks.extract_options!
- 7
options = {
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
}.merge!(options)
- 7
types = Array(options.delete(:only))
- 7
callbacks.each do |callback|
- 7
define_callbacks(callback, options)
- 7
types.each do |type|
- 15
send("_define_#{type}_model_callback", self, callback)
end
end
end
- 1
private
- 1
def _define_before_model_callback(klass, callback)
- 5
klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
- 1
options.assert_valid_keys(:if, :unless, :prepend)
- 1
set_callback(:"#{callback}", :before, *args, options, &block)
end
end
- 1
def _define_around_model_callback(klass, callback)
- 5
klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
- 1
options.assert_valid_keys(:if, :unless, :prepend)
- 1
set_callback(:"#{callback}", :around, *args, options, &block)
end
end
- 1
def _define_after_model_callback(klass, callback)
- 5
klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
- 5
options.assert_valid_keys(:if, :unless, :prepend)
- 5
options[:prepend] = true
- 5
conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
- 12
v != false
}
- 5
options[:if] = Array(options[:if]) << conditional
- 5
set_callback(:"#{callback}", :after, *args, options, &block)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# == Active \Model \Conversion
#
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
#
# Let's take for example this non-persisted object.
#
# class ContactMessage
# include ActiveModel::Conversion
#
# # ContactMessage are never persisted in the DB
# def persisted?
# false
# end
# end
#
# cm = ContactMessage.new
# cm.to_model == cm # => true
# cm.to_key # => nil
# cm.to_param # => nil
# cm.to_partial_path # => "contact_messages/contact_message"
- 1
module Conversion
- 1
extend ActiveSupport::Concern
# If your object is already designed to implement all of the \Active \Model
# you can use the default <tt>:to_model</tt> implementation, which simply
# returns +self+.
#
# class Person
# include ActiveModel::Conversion
# end
#
# person = Person.new
# person.to_model == person # => true
#
# If your model does not act like an \Active \Model object, then you should
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
# your object with \Active \Model compliant methods.
- 1
def to_model
- 42
self
end
# Returns an Array of all key attributes if any of the attributes is set, whether or not
# the object is persisted. Returns +nil+ if there are no key attributes.
#
# class Person
# include ActiveModel::Conversion
# attr_accessor :id
#
# def initialize(id)
# @id = id
# end
# end
#
# person = Person.new(1)
# person.to_key # => [1]
- 1
def to_key
- 7
key = respond_to?(:id) && id
- 7
key ? [key] : nil
end
# Returns a +string+ representing the object's key suitable for use in URLs,
# or +nil+ if <tt>persisted?</tt> is +false+.
#
# class Person
# include ActiveModel::Conversion
# attr_accessor :id
#
# def initialize(id)
# @id = id
# end
#
# def persisted?
# true
# end
# end
#
# person = Person.new(1)
# person.to_param # => "1"
- 1
def to_param
- 6
(persisted? && key = to_key) ? key.join("-") : nil
end
# Returns a +string+ identifying the path associated with the object.
# ActionPack uses this to find a suitable partial to represent the object.
#
# class Person
# include ActiveModel::Conversion
# end
#
# person = Person.new
# person.to_partial_path # => "people/person"
- 1
def to_partial_path
- 5
self.class._to_partial_path
end
- 1
module ClassMethods #:nodoc:
# Provide a class level cache for #to_partial_path. This is an
# internal method and should not be accessed directly.
- 1
def _to_partial_path #:nodoc:
- 5
@_to_partial_path ||= begin
- 5
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
- 5
collection = ActiveSupport::Inflector.tableize(name)
- 5
"#{collection}/#{element}"
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_model/attribute_mutation_tracker"
- 1
module ActiveModel
# == Active \Model \Dirty
#
# Provides a way to track changes in your object in the same way as
# Active Record does.
#
# The requirements for implementing ActiveModel::Dirty are:
#
# * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
# track.
# * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
# attribute.
# * Call <tt>changes_applied</tt> after the changes are persisted.
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
# information.
# * Call <tt>restore_attributes</tt> when you want to restore previous data.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::Dirty
#
# define_attribute_methods :name
#
# def initialize
# @name = nil
# end
#
# def name
# @name
# end
#
# def name=(val)
# name_will_change! unless val == @name
# @name = val
# end
#
# def save
# # do persistence work
#
# changes_applied
# end
#
# def reload!
# # get the values from the persistence layer
#
# clear_changes_information
# end
#
# def rollback!
# restore_attributes
# end
# end
#
# A newly instantiated +Person+ object is unchanged:
#
# person = Person.new
# person.changed? # => false
#
# Change the name:
#
# person.name = 'Bob'
# person.changed? # => true
# person.name_changed? # => true
# person.name_changed?(from: nil, to: "Bob") # => true
# person.name_was # => nil
# person.name_change # => [nil, "Bob"]
# person.name = 'Bill'
# person.name_change # => [nil, "Bill"]
#
# Save the changes:
#
# person.save
# person.changed? # => false
# person.name_changed? # => false
#
# Reset the changes:
#
# person.previous_changes # => {"name" => [nil, "Bill"]}
# person.name_previously_changed? # => true
# person.name_previously_changed?(from: nil, to: "Bill") # => true
# person.name_previous_change # => [nil, "Bill"]
# person.name_previously_was # => nil
# person.reload!
# person.previous_changes # => {}
#
# Rollback the changes:
#
# person.name = "Uncle Bob"
# person.rollback!
# person.name # => "Bill"
# person.name_changed? # => false
#
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
# person.name_changed? # => false
# person.name_change # => nil
#
# Which attributes have changed?
#
# person.name = 'Bob'
# person.changed # => ["name"]
# person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of
# <tt>[attribute_name]_will_change!</tt> to mark that the attribute is changing.
# Otherwise \Active \Model can't track changes to in-place attributes. Note
# that Active Record can detect in-place modifications automatically. You do
# not need to call <tt>[attribute_name]_will_change!</tt> on Active Record models.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
# person.name_change # => ["Bill", "Billy"]
- 1
module Dirty
- 1
extend ActiveSupport::Concern
- 1
include ActiveModel::AttributeMethods
- 1
included do
- 2
attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
- 2
attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
- 2
attribute_method_affix prefix: "restore_", suffix: "!"
- 2
attribute_method_affix prefix: "clear_", suffix: "_change"
end
- 1
def initialize_dup(other) # :nodoc:
- 1
super
- 1
if self.class.respond_to?(:_default_attributes)
@attributes = self.class._default_attributes.map do |attr|
attr.with_value_from_user(@attributes.fetch_value(attr.name))
end
end
- 1
@mutations_from_database = nil
end
# Clears dirty data and moves +changes+ to +previous_changes+ and
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
- 1
def changes_applied
- 25
unless defined?(@attributes)
- 12
mutations_from_database.finalize_changes
end
- 25
@mutations_before_last_save = mutations_from_database
- 25
forget_attribute_assignments
- 25
@mutations_from_database = nil
end
# Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
- 1
def changed?
- 18
mutations_from_database.any_changes?
end
# Returns an array with the name of the attributes with unsaved changes.
#
# person.changed # => []
# person.name = 'bob'
# person.changed # => ["name"]
- 1
def changed
- 6
mutations_from_database.changed_attribute_names
end
# Dispatch target for <tt>*_changed?</tt> attribute methods.
- 1
def attribute_changed?(attr_name, **options) # :nodoc:
- 48
mutations_from_database.changed?(attr_name.to_s, **options)
end
# Dispatch target for <tt>*_was</tt> attribute methods.
- 1
def attribute_was(attr_name) # :nodoc:
- 10
mutations_from_database.original_value(attr_name.to_s)
end
# Dispatch target for <tt>*_previously_changed?</tt> attribute methods.
- 1
def attribute_previously_changed?(attr_name, **options) # :nodoc:
- 8
mutations_before_last_save.changed?(attr_name.to_s, **options)
end
# Dispatch target for <tt>*_previously_was</tt> attribute methods.
- 1
def attribute_previously_was(attr_name) # :nodoc:
mutations_before_last_save.original_value(attr_name.to_s)
end
# Restore all previous data of the provided attributes.
- 1
def restore_attributes(attr_names = changed)
- 10
attr_names.each { |attr_name| restore_attribute!(attr_name) }
end
# Clears all dirty data: current changes and previous changes.
- 1
def clear_changes_information
- 2
@mutations_before_last_save = nil
- 2
forget_attribute_assignments
- 2
@mutations_from_database = nil
end
- 1
def clear_attribute_changes(attr_names)
attr_names.each do |attr_name|
clear_attribute_change(attr_name)
end
end
# Returns a hash of the attributes with unsaved changes indicating their original
# values like <tt>attr => original value</tt>.
#
# person.name # => "bob"
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
- 1
def changed_attributes
- 10
mutations_from_database.changed_values
end
# Returns a hash of changed attributes indicating their original
# and new values like <tt>attr => [original value, new value]</tt>.
#
# person.changes # => {}
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
- 1
def changes
- 9
mutations_from_database.changes
end
# Returns a hash of attributes that were changed before the model was saved.
#
# person.name # => "bob"
# person.name = 'robert'
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
- 1
def previous_changes
- 8
mutations_before_last_save.changes
end
- 1
def attribute_changed_in_place?(attr_name) # :nodoc:
mutations_from_database.changed_in_place?(attr_name.to_s)
end
- 1
private
- 1
def clear_attribute_change(attr_name)
- 8
mutations_from_database.forget_change(attr_name.to_s)
end
- 1
def mutations_from_database
- 193
@mutations_from_database ||= if defined?(@attributes)
- 31
ActiveModel::AttributeMutationTracker.new(@attributes)
else
- 30
ActiveModel::ForcedMutationTracker.new(self)
end
end
- 1
def forget_attribute_assignments
- 27
@attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
end
- 1
def mutations_before_last_save
- 18
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
end
# Dispatch target for <tt>*_change</tt> attribute methods.
- 1
def attribute_change(attr_name)
- 3
mutations_from_database.change_to_attribute(attr_name.to_s)
end
# Dispatch target for <tt>*_previous_change</tt> attribute methods.
- 1
def attribute_previous_change(attr_name)
- 2
mutations_before_last_save.change_to_attribute(attr_name.to_s)
end
# Dispatch target for <tt>*_will_change!</tt> attribute methods.
- 1
def attribute_will_change!(attr_name)
- 44
mutations_from_database.force_change(attr_name.to_s)
end
# Dispatch target for <tt>restore_*!</tt> attribute methods.
- 1
def restore_attribute!(attr_name)
- 8
attr_name = attr_name.to_s
- 8
if attribute_changed?(attr_name)
- 8
__send__("#{attr_name}=", attribute_was(attr_name))
- 8
clear_attribute_change(attr_name)
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/class/attribute"
- 1
module ActiveModel
# == Active \Model \Error
#
# Represents one single error
- 1
class Error
- 1
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
- 1
MESSAGE_OPTIONS = [:message]
- 1
class_attribute :i18n_customize_full_message, default: false
- 1
def self.full_message(attribute, message, base_class) # :nodoc:
- 60
return message if attribute == :base
- 56
attribute = attribute.to_s
- 56
if i18n_customize_full_message && base_class.respond_to?(:i18n_scope)
- 18
attribute = attribute.remove(/\[\d+\]/)
- 18
parts = attribute.split(".")
- 18
attribute_name = parts.pop
- 18
namespace = parts.join("/") unless parts.empty?
- 18
attributes_scope = "#{base_class.i18n_scope}.errors.models"
- 18
if namespace
- 11
defaults = base_class.lookup_ancestors.map do |klass|
- 22
[
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
]
end
else
- 7
defaults = base_class.lookup_ancestors.map do |klass|
- 14
[
:"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
:"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
]
end
end
- 18
defaults.flatten!
else
- 38
defaults = []
end
- 56
defaults << :"errors.format"
- 56
defaults << "%{attribute} %{message}"
- 56
attr_name = attribute.tr(".", "_").humanize
- 56
attr_name = base_class.human_attribute_name(attribute, default: attr_name)
- 56
I18n.t(defaults.shift,
default: defaults,
attribute: attr_name,
message: message)
end
- 1
def self.generate_message(attribute, type, base, options) # :nodoc:
- 559
type = options.delete(:message) if options[:message].is_a?(Symbol)
- 559
value = (attribute != :base ? base.send(:read_attribute_for_validation, attribute) : nil)
- 559
options = {
model: base.model_name.human,
attribute: base.class.human_attribute_name(attribute),
value: value,
object: base
}.merge!(options)
- 559
if base.class.respond_to?(:i18n_scope)
- 513
i18n_scope = base.class.i18n_scope.to_s
- 513
attribute = attribute.to_s.remove(/\[\d+\]/)
- 513
defaults = base.class.lookup_ancestors.flat_map do |klass|
- 564
[ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
:"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
end
- 513
defaults << :"#{i18n_scope}.errors.messages.#{type}"
catch(:exception) do
- 433
translation = I18n.translate(defaults.first, **options.merge(default: defaults.drop(1), throw: true))
- 29
return translation unless translation.nil?
- 513
end unless options[:message]
else
- 46
defaults = []
end
- 530
defaults << :"errors.attributes.#{attribute}.#{type}"
- 530
defaults << :"errors.messages.#{type}"
- 530
key = defaults.shift
- 530
defaults = options.delete(:message) if options[:message]
- 530
options[:default] = defaults
- 530
I18n.translate(key, **options)
end
- 1
def initialize(base, attribute, type = :invalid, **options)
- 704
@base = base
- 704
@attribute = attribute
- 704
@raw_type = type
- 704
@type = type || :invalid
- 704
@options = options
end
- 1
def initialize_dup(other) # :nodoc:
- 4
@attribute = @attribute.dup
- 4
@raw_type = @raw_type.dup
- 4
@type = @type.dup
- 4
@options = @options.deep_dup
end
# The object which the error belongs to
- 1
attr_reader :base
# The attribute of +base+ which the error belongs to
- 1
attr_reader :attribute
# The type of error, defaults to `:invalid` unless specified
- 1
attr_reader :type
# The raw value provided as the second parameter when calling `errors#add`
- 1
attr_reader :raw_type
# The options provided when calling `errors#add`
- 1
attr_reader :options
# Returns the error message.
#
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
# error.message
# # => "is too short (minimum is 5 characters)"
- 1
def message
- 705
case raw_type
when Symbol
- 600
self.class.generate_message(attribute, raw_type, @base, options.except(*CALLBACKS_OPTIONS))
else
- 105
raw_type
end
end
# Returns the error detail.
#
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
# error.detail
# # => { error: :too_short, count: 5 }
- 1
def detail
- 20
{ error: raw_type }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
end
# Returns the full error message.
#
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
# error.full_message
# # => "Name is too short (minimum is 5 characters)"
- 1
def full_message
- 37
self.class.full_message(attribute, message, @base.class)
end
# See if error matches provided +attribute+, +type+ and +options+.
#
# Omitted params are not checked for a match.
- 1
def match?(attribute, type = nil, **options)
- 650
if @attribute != attribute || (type && @type != type)
- 72
return false
end
- 578
options.each do |key, value|
- 4
if @options[key] != value
- 2
return false
end
end
- 576
true
end
# See if error matches provided +attribute+, +type+ and +options+ exactly.
#
# All params must be equal to Error's own attributes to be considered a
# strict match.
- 1
def strict_match?(attribute, type, **options)
- 18
return false unless match?(attribute, type)
- 15
options == @options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)
end
- 1
def ==(other) # :nodoc:
- 7
other.is_a?(self.class) && attributes_for_hash == other.attributes_for_hash
end
- 1
alias eql? ==
- 1
def hash # :nodoc:
attributes_for_hash.hash
end
- 1
def inspect # :nodoc:
"#<#{self.class.name} attribute=#{@attribute}, type=#{@type}, options=#{@options.inspect}>"
end
- 1
protected
- 1
def attributes_for_hash
- 12
[@base, @attribute, @raw_type, @options.except(*CALLBACKS_OPTIONS)]
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/array/conversions"
- 1
require "active_support/core_ext/string/inflections"
- 1
require "active_support/core_ext/object/deep_dup"
- 1
require "active_support/core_ext/string/filters"
- 1
require "active_model/error"
- 1
require "active_model/nested_error"
- 1
require "forwardable"
- 1
module ActiveModel
# == Active \Model \Errors
#
# Provides error related functionalities you can include in your object
# for handling error messages and interacting with Action View helpers.
#
# A minimal implementation could be:
#
# class Person
# # Required dependency for ActiveModel::Errors
# extend ActiveModel::Naming
#
# def initialize
# @errors = ActiveModel::Errors.new(self)
# end
#
# attr_accessor :name
# attr_reader :errors
#
# def validate!
# errors.add(:name, :blank, message: "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
#
# def read_attribute_for_validation(attr)
# send(attr)
# end
#
# def self.human_attribute_name(attr, options = {})
# attr
# end
#
# def self.lookup_ancestors
# [self]
# end
# end
#
# The last three methods are required in your object for +Errors+ to be
# able to generate error messages correctly and also handle multiple
# languages. Of course, if you extend your object with <tt>ActiveModel::Translation</tt>
# you will not need to implement the last two. Likewise, using
# <tt>ActiveModel::Validations</tt> will handle the validation related methods
# for you.
#
# The above allows you to do:
#
# person = Person.new
# person.validate! # => ["cannot be nil"]
# person.errors.full_messages # => ["name cannot be nil"]
# # etc..
- 1
class Errors
- 1
include Enumerable
- 1
extend Forwardable
- 1
def_delegators :@errors, :size, :clear, :blank?, :empty?, :uniq!, :any?
# TODO: forward all enumerable methods after `each` deprecation is removed.
- 1
def_delegators :@errors, :count
- 1
LEGACY_ATTRIBUTES = [:messages, :details].freeze
- 1
private_constant :LEGACY_ATTRIBUTES
# The actual array of +Error+ objects
# This method is aliased to <tt>objects</tt>.
- 1
attr_reader :errors
- 1
alias :objects :errors
# Pass in the instance of the object that is using the errors object.
#
# class Person
# def initialize
# @errors = ActiveModel::Errors.new(self)
# end
# end
- 1
def initialize(base)
- 650
@base = base
- 650
@errors = []
end
- 1
def initialize_dup(other) # :nodoc:
- 2
@errors = other.errors.deep_dup
- 2
super
end
# Copies the errors from <tt>other</tt>.
# For copying errors but keep <tt>@base</tt> as is.
#
# other - The ActiveModel::Errors instance.
#
# Examples
#
# person.errors.copy!(other)
- 1
def copy!(other) # :nodoc:
- 2
@errors = other.errors.deep_dup
- 2
@errors.each { |error|
- 2
error.instance_variable_set(:@base, @base)
}
end
# Imports one error
# Imported errors are wrapped as a NestedError,
# providing access to original error object.
# If attribute or type needs to be overridden, use `override_options`.
#
# override_options - Hash
# @option override_options [Symbol] :attribute Override the attribute the error belongs to
# @option override_options [Symbol] :type Override type of the error.
- 1
def import(error, override_options = {})
- 3
[:attribute, :type].each do |key|
- 6
if override_options.key?(key)
- 1
override_options[key] = override_options[key].to_sym
end
end
- 3
@errors.append(NestedError.new(@base, error, override_options))
end
# Merges the errors from <tt>other</tt>,
# each <tt>Error</tt> wrapped as <tt>NestedError</tt>.
#
# other - The ActiveModel::Errors instance.
#
# Examples
#
# person.errors.merge!(other)
- 1
def merge!(other)
- 2
other.errors.each { |error|
- 2
import(error)
}
end
# Removes all errors except the given keys. Returns a hash containing the removed errors.
#
# person.errors.keys # => [:name, :age, :gender, :city]
# person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
# person.errors.keys # => [:age, :gender]
- 1
def slice!(*keys)
- 2
deprecation_removal_warning(:slice!)
- 2
keys = keys.map(&:to_sym)
- 2
results = messages.dup.slice!(*keys)
- 2
@errors.keep_if do |error|
- 8
keys.include?(error.attribute)
end
- 2
results
end
# Search for errors matching +attribute+, +type+ or +options+.
#
# Only supplied params will be matched.
#
# person.errors.where(:name) # => all name errors.
# person.errors.where(:name, :too_short) # => all name errors being too short
# person.errors.where(:name, :too_short, minimum: 2) # => all name errors being too short and minimum is 2
- 1
def where(attribute, type = nil, **options)
- 790
attribute, type, options = normalize_arguments(attribute, type, **options)
- 790
@errors.select { |error|
- 616
error.match?(attribute, type, **options)
}
end
# Returns +true+ if the error messages include an error for the given key
# +attribute+, +false+ otherwise.
#
# person.errors.messages # => {:name=>["cannot be nil"]}
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
- 1
def include?(attribute)
- 10
@errors.any? { |error|
- 6
error.match?(attribute.to_sym)
}
end
- 1
alias :has_key? :include?
- 1
alias :key? :include?
# Delete messages for +key+. Returns the deleted messages.
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors.delete(:name) # => ["cannot be nil"]
# person.errors[:name] # => []
- 1
def delete(attribute, type = nil, **options)
- 10
attribute, type, options = normalize_arguments(attribute, type, **options)
- 10
matches = where(attribute, type, **options)
- 10
matches.each do |error|
- 6
@errors.delete(error)
end
- 10
matches.map(&:message).presence
end
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
# person.errors[:name] # => ["cannot be nil"]
# person.errors['name'] # => ["cannot be nil"]
- 1
def [](attribute)
- 747
DeprecationHandlingMessageArray.new(messages_for(attribute), self, attribute)
end
# Iterates through each error object.
#
# person.errors.add(:name, :too_short, count: 2)
# person.errors.each do |error|
# # Will yield <#ActiveModel::Error attribute=name, type=too_short,
# options={:count=>3}>
# end
#
# To be backward compatible with past deprecated hash-like behavior,
# when block accepts two parameters instead of one, it
# iterates through each error key, value pair in the error messages hash.
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
#
# person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.each do |attribute, message|
# # Will yield :name and "can't be blank"
# end
#
# person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.each do |attribute, message|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
- 1
def each(&block)
- 4
if block.arity <= 1
- 3
@errors.each(&block)
else
- 1
ActiveSupport::Deprecation.warn(<<~MSG)
Enumerating ActiveModel::Errors as a hash has been deprecated.
In Rails 6.1, `errors` is an array of Error objects,
therefore it should be accessed by a block with a single block
parameter like this:
person.errors.each do |error|
attribute = error.attribute
message = error.message
end
You are passing a block expecting two parameters,
so the old hash behavior is simulated. As this is deprecated,
this will result in an ArgumentError in Rails 6.2.
MSG
@errors.
- 1
sort { |a, b| a.attribute <=> b.attribute }.
- 3
each { |error| yield error.attribute, error.message }
end
end
# Returns all message values.
#
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.values # => [["cannot be nil", "must be specified"]]
- 1
def values
- 2
deprecation_removal_warning(:values, "errors.map { |error| error.message }")
- 2
@errors.map(&:message).freeze
end
# Returns all message keys.
#
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.keys # => [:name]
- 1
def keys
- 7
deprecation_removal_warning(:keys, "errors.attribute_names")
- 7
keys = @errors.map(&:attribute)
- 7
keys.uniq!
- 7
keys.freeze
end
# Returns all error attribute names
#
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
# person.errors.attribute_names # => [:name]
- 1
def attribute_names
- 3
@errors.map(&:attribute).uniq.freeze
end
# Returns an xml formatted representation of the Errors hash.
#
# person.errors.add(:name, :blank, message: "can't be blank")
# person.errors.add(:name, :not_specified, message: "must be specified")
# person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# # <errors>
# # <error>name can't be blank</error>
# # <error>name must be specified</error>
# # </errors>
- 1
def to_xml(options = {})
- 1
deprecation_removal_warning(:to_xml)
- 1
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
end
# Returns a Hash that can be used as the JSON representation for this
# object. You can pass the <tt>:full_messages</tt> option. This determines
# if the json object should contain full messages or not (false by default).
#
# person.errors.as_json # => {:name=>["cannot be nil"]}
# person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
- 1
def as_json(options = nil)
- 5
to_hash(options && options[:full_messages])
end
# Returns a Hash of attributes with their error messages. If +full_messages+
# is +true+, it will contain full messages (see +full_message+).
#
# person.errors.to_hash # => {:name=>["cannot be nil"]}
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
- 1
def to_hash(full_messages = false)
- 114
hash = {}
- 114
message_method = full_messages ? :full_message : :message
- 114
group_by_attribute.each do |attribute, errors|
- 108
hash[attribute] = errors.map(&message_method)
end
- 114
hash
end
- 1
def to_h
- 1
ActiveSupport::Deprecation.warn(<<~EOM)
ActiveModel::Errors#to_h is deprecated and will be removed in Rails 6.2.
Please use `ActiveModel::Errors.to_hash` instead. The values in the hash
returned by `ActiveModel::Errors.to_hash` is an array of error messages.
EOM
- 2
to_hash.transform_values { |values| values.last }
end
# Returns a Hash of attributes with an array of their error messages.
#
# Updating this hash would still update errors state for backward
# compatibility, but this behavior is deprecated.
- 1
def messages
- 102
DeprecationHandlingMessageHash.new(self)
end
# Returns a Hash of attributes with an array of their error details.
#
# Updating this hash would still update errors state for backward
# compatibility, but this behavior is deprecated.
- 1
def details
- 20
hash = {}
- 20
group_by_attribute.each do |attribute, errors|
- 18
hash[attribute] = errors.map(&:detail)
end
- 20
DeprecationHandlingDetailsHash.new(hash)
end
# Returns a Hash of attributes with an array of their Error objects.
#
# person.errors.group_by_attribute
# # => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}
- 1
def group_by_attribute
- 135
@errors.group_by(&:attribute)
end
# Adds a new error of +type+ on +attribute+.
# More than one error can be added to the same +attribute+.
# If no +type+ is supplied, <tt>:invalid</tt> is assumed.
#
# person.errors.add(:name)
# # Adds <#ActiveModel::Error attribute=name, type=invalid>
# person.errors.add(:name, :not_implemented, message: "must be implemented")
# # Adds <#ActiveModel::Error attribute=name, type=not_implemented,
# options={:message=>"must be implemented"}>
#
# person.errors.messages
# # => {:name=>["is invalid", "must be implemented"]}
#
# If +type+ is a string, it will be used as error message.
#
# If +type+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
#
# If +type+ is a proc, it will be called, allowing for things like
# <tt>Time.now</tt> to be used within an error.
#
# If the <tt>:strict</tt> option is set to +true+, it will raise
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: Name is invalid
# person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: Name is invalid
#
# person.errors.messages # => {}
#
# +attribute+ should be set to <tt>:base</tt> if the error is not
# directly associated with a single attribute.
#
# person.errors.add(:base, :name_or_email_blank,
# message: "either name or email must be present")
# person.errors.messages
# # => {:base=>["either name or email must be present"]}
# person.errors.details
# # => {:base=>[{error: :name_or_email_blank}]}
- 1
def add(attribute, type = :invalid, **options)
- 669
attribute, type, options = normalize_arguments(attribute, type, **options)
- 669
error = Error.new(@base, attribute, type, **options)
- 669
if exception = options[:strict]
- 6
exception = ActiveModel::StrictValidationFailed if exception == true
- 6
raise exception, error.full_message
end
- 663
@errors.append(error)
- 663
error
end
# Returns +true+ if an error matches provided +attribute+ and +type+,
# or +false+ otherwise. +type+ is treated the same as for +add+.
#
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
# person.errors.added? :name, "can't be blank" # => true
#
# If the error requires options, then it returns +true+ with
# the correct options, or +false+ with incorrect or missing options.
#
# person.errors.add :name, :too_long, { count: 25 }
# person.errors.added? :name, :too_long, count: 25 # => true
# person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
# person.errors.added? :name, :too_long, count: 24 # => false
# person.errors.added? :name, :too_long # => false
# person.errors.added? :name, "is too long" # => false
- 1
def added?(attribute, type = :invalid, options = {})
- 28
attribute, type, options = normalize_arguments(attribute, type, **options)
- 28
if type.is_a? Symbol
- 19
@errors.any? { |error|
- 18
error.strict_match?(attribute, type, **options)
}
else
- 9
messages_for(attribute).include?(type)
end
end
# Returns +true+ if an error on the attribute with the given type is
# present, or +false+ otherwise. +type+ is treated the same as for +add+.
#
# person.errors.add :age
# person.errors.add :name, :too_long, { count: 25 }
# person.errors.of_kind? :age # => true
# person.errors.of_kind? :name # => false
# person.errors.of_kind? :name, :too_long # => true
# person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
# person.errors.of_kind? :name, :not_too_long # => false
# person.errors.of_kind? :name, "is too long" # => false
- 1
def of_kind?(attribute, type = :invalid)
- 16
attribute, type = normalize_arguments(attribute, type)
- 16
if type.is_a? Symbol
- 8
!where(attribute, type).empty?
else
- 8
messages_for(attribute).include?(type)
end
end
# Returns all the full error messages in an array.
#
# class Person
# validates_presence_of :name, :address, :email
# validates_length_of :name, in: 5..30
# end
#
# person = Person.create(address: '123 First St.')
# person.errors.full_messages
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
- 1
def full_messages
- 22
@errors.map(&:full_message)
end
- 1
alias :to_a :full_messages
# Returns all the full error messages for a given attribute in an array.
#
# class Person
# validates_presence_of :name, :email
# validates_length_of :name, in: 5..30
# end
#
# person = Person.create()
# person.errors.full_messages_for(:name)
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
- 1
def full_messages_for(attribute)
- 4
where(attribute).map(&:full_message).freeze
end
# Returns all the error messages for a given attribute in an array.
#
# class Person
# validates_presence_of :name, :email
# validates_length_of :name, in: 5..30
# end
#
# person = Person.create()
# person.errors.messages_for(:name)
# # => ["is too short (minimum is 5 characters)", "can't be blank"]
- 1
def messages_for(attribute)
- 768
where(attribute).map(&:message)
end
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
- 1
def full_message(attribute, message)
- 23
Error.full_message(attribute, message, @base.class)
end
# Translates an error message in its default scope
# (<tt>activemodel.errors.messages</tt>).
#
# Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
# if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
# that is not there also, it returns the translation of the default message
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
# name, translated attribute name and the value are available for
# interpolation.
#
# When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
# <tt>class Admin < User; end</tt> and you wanted the translation for
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
# it looks for these translations:
#
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
# * <tt>activemodel.errors.models.admin.blank</tt>
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
# * <tt>activemodel.errors.models.user.blank</tt>
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
# * <tt>activemodel.errors.messages.blank</tt>
# * <tt>errors.attributes.title.blank</tt>
# * <tt>errors.messages.blank</tt>
- 1
def generate_message(attribute, type = :invalid, options = {})
- 34
Error.generate_message(attribute, type, @base, options)
end
- 1
def marshal_load(array) # :nodoc:
# Rails 5
- 2
@errors = []
- 2
@base = array[0]
- 2
add_from_legacy_details_hash(array[2])
end
- 1
def init_with(coder) # :nodoc:
- 6
data = coder.map
- 6
data.each { |k, v|
- 14
next if LEGACY_ATTRIBUTES.include?(k.to_sym)
- 8
instance_variable_set(:"@#{k}", v)
}
- 6
@errors ||= []
# Legacy support Rails 5.x details hash
- 6
add_from_legacy_details_hash(data["details"]) if data.key?("details")
end
- 1
private
- 1
def normalize_arguments(attribute, type, **options)
# Evaluate proc first
- 1513
if type.respond_to?(:call)
- 7
type = type.call(@base, options)
end
- 1513
[attribute.to_sym, type, options]
end
- 1
def add_from_legacy_details_hash(details)
- 4
details.each { |attribute, errors|
- 2
errors.each { |error|
- 2
type = error.delete(:error)
- 2
add(attribute, type, **error)
}
}
end
- 1
def deprecation_removal_warning(method_name, alternative_message = nil)
- 12
message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 6.2."
- 12
if alternative_message
- 9
message << "\n\nTo achieve the same use:\n\n "
- 9
message << alternative_message
end
- 12
ActiveSupport::Deprecation.warn(message)
end
- 1
def deprecation_rename_warning(old_method_name, new_method_name)
ActiveSupport::Deprecation.warn("ActiveModel::Errors##{old_method_name} is deprecated. Please call ##{new_method_name} instead.")
end
end
- 1
class DeprecationHandlingMessageHash < SimpleDelegator
- 1
def initialize(errors)
- 102
@errors = errors
- 102
super(prepare_content)
end
- 1
def []=(attribute, value)
- 4
ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.")
- 4
@errors.delete(attribute)
- 4
Array(value).each do |message|
- 4
@errors.add(attribute, message)
end
- 4
__setobj__ prepare_content
end
- 1
def delete(attribute)
- 1
ActiveSupport::Deprecation.warn("Calling `delete` to an ActiveModel::Errors messages hash is deprecated. Please call `ActiveModel::Errors#delete` instead.")
- 1
@errors.delete(attribute)
end
- 1
private
- 1
def prepare_content
- 106
content = @errors.to_hash
- 106
content.each do |attribute, value|
- 100
content[attribute] = DeprecationHandlingMessageArray.new(value, @errors, attribute)
end
- 106
content.default_proc = proc do |hash, attribute|
- 8
hash = hash.dup
- 8
hash[attribute] = DeprecationHandlingMessageArray.new([], @errors, attribute)
- 8
__setobj__ hash.freeze
- 8
hash[attribute]
end
- 106
content.freeze
end
end
- 1
class DeprecationHandlingMessageArray < SimpleDelegator
- 1
def initialize(content, errors, attribute)
- 855
@errors = errors
- 855
@attribute = attribute
- 855
super(content.freeze)
end
- 1
def <<(message)
- 4
ActiveSupport::Deprecation.warn("Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.")
- 4
@errors.add(@attribute, message)
- 4
__setobj__ @errors.messages_for(@attribute)
- 4
self
end
- 1
def clear
- 1
ActiveSupport::Deprecation.warn("Calling `clear` to an ActiveModel::Errors message array in order to delete all errors is deprecated. Please call `ActiveModel::Errors#delete` instead.")
- 1
@errors.delete(@attribute)
end
end
- 1
class DeprecationHandlingDetailsHash < SimpleDelegator
- 1
def initialize(details)
- 20
details.default = []
- 20
details.freeze
- 20
super(details)
end
end
# Raised when a validation cannot be corrected by end users and are considered
# exceptional.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
#
# validates_presence_of :name, strict: true
# end
#
# person = Person.new
# person.name = nil
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name can't be blank
- 1
class StrictValidationFailed < StandardError
end
# Raised when attribute values are out of range.
- 1
class RangeError < ::RangeError
end
# Raised when unknown attributes are supplied via mass assignment.
#
# class Person
# include ActiveModel::AttributeAssignment
# include ActiveModel::Validations
# end
#
# person = Person.new
# person.assign_attributes(name: 'Gorby')
# # => ActiveModel::UnknownAttributeError: unknown attribute 'name' for Person.
- 1
class UnknownAttributeError < NoMethodError
- 1
attr_reader :record, :attribute
- 1
def initialize(record, attribute)
- 4
@record = record
- 4
@attribute = attribute
- 4
super("unknown attribute '#{attribute}' for #{@record.class}.")
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# Raised when forbidden attributes are used for mass assignment.
#
# class Person < ActiveRecord::Base
# end
#
# params = ActionController::Parameters.new(name: 'Bob')
# Person.new(params)
# # => ActiveModel::ForbiddenAttributesError
#
# params.permit!
# Person.new(params)
# # => #<Person id: nil, name: "Bob">
- 1
class ForbiddenAttributesError < StandardError
end
- 1
module ForbiddenAttributesProtection # :nodoc:
- 1
private
- 1
def sanitize_for_mass_assignment(attributes)
- 22
if attributes.respond_to?(:permitted?)
- 4
raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
- 2
attributes.to_h
else
- 18
attributes
end
end
- 1
alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
- 1
def self.gem_version
Gem::Version.new VERSION::STRING
end
- 1
module VERSION
- 1
MAJOR = 6
- 1
MINOR = 1
- 1
TINY = 0
- 1
PRE = "alpha"
- 1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Lint
# == Active \Model \Lint \Tests
#
# You can test whether an object is compliant with the Active \Model API by
# including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
# include tests that tell you whether your object is fully compliant,
# or if not, which aspects of the API are not implemented.
#
# Note an object is not required to implement all APIs in order to work
# with Action Pack. This module only intends to provide guidance in case
# you want all features out of the box.
#
# These tests do not attempt to determine the semantic correctness of the
# returned values. For instance, you could implement <tt>valid?</tt> to
# always return +true+, and the tests would pass. It is up to you to ensure
# that the values are semantically meaningful.
#
# Objects you pass in are expected to return a compliant object from a call
# to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return
# +self+.
- 1
module Tests
# Passes if the object's model responds to <tt>to_key</tt> and if calling
# this method returns +nil+ when the object is not persisted.
# Fails otherwise.
#
# <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
# of the model, and is used to a generate unique DOM id for the object.
- 1
def test_to_key
- 2
assert_respond_to model, :to_key
- 2
def model.persisted?() false end
- 2
assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
end
# Passes if the object's model responds to <tt>to_param</tt> and if
# calling this method returns +nil+ when the object is not persisted.
# Fails otherwise.
#
# <tt>to_param</tt> is used to represent the object's key in URLs.
# Implementers can decide to either raise an exception or provide a
# default in case the record uses a composite primary key. There are no
# tests for this behavior in lint because it doesn't make sense to force
# any of the possible implementation strategies on the implementer.
- 1
def test_to_param
- 2
assert_respond_to model, :to_param
- 2
def model.to_key() [1] end
- 4
def model.persisted?() false end
- 2
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
# Passes if the object's model responds to <tt>to_partial_path</tt> and if
# calling this method returns a string. Fails otherwise.
#
# <tt>to_partial_path</tt> is used for looking up partials. For example,
# a BlogPost model might return "blog_posts/blog_post".
- 1
def test_to_partial_path
- 2
assert_respond_to model, :to_partial_path
- 2
assert_kind_of String, model.to_partial_path
end
# Passes if the object's model responds to <tt>persisted?</tt> and if
# calling this method returns either +true+ or +false+. Fails otherwise.
#
# <tt>persisted?</tt> is used when calculating the URL for an object.
# If the object is not persisted, a form for that object, for instance,
# will route to the create action. If it is persisted, a form for the
# object will route to the update action.
- 1
def test_persisted?
- 2
assert_respond_to model, :persisted?
- 2
assert_boolean model.persisted?, "persisted?"
end
# Passes if the object's model responds to <tt>model_name</tt> both as
# an instance method and as a class method, and if calling this method
# returns a string with some convenience methods: <tt>:human</tt>,
# <tt>:singular</tt> and <tt>:plural</tt>.
#
# Check ActiveModel::Naming for more information.
- 1
def test_model_naming
- 2
assert_respond_to model.class, :model_name
- 2
model_name = model.class.model_name
- 2
assert_respond_to model_name, :to_str
- 2
assert_respond_to model_name.human, :to_str
- 2
assert_respond_to model_name.singular, :to_str
- 2
assert_respond_to model_name.plural, :to_str
- 2
assert_respond_to model, :model_name
- 2
assert_equal model.model_name, model.class.model_name
end
# Passes if the object's model responds to <tt>errors</tt> and if calling
# <tt>[](attribute)</tt> on the result of this method returns an array.
# Fails otherwise.
#
# <tt>errors[attribute]</tt> is used to retrieve the errors of a model
# for a given attribute. If errors are present, the method should return
# an array of strings that are the errors for the attribute in question.
# If localization is used, the strings should be localized for the current
# locale. If no error is present, the method should return an empty array.
- 1
def test_errors_aref
- 2
assert_respond_to model, :errors
- 2
assert_equal [], model.errors[:hello], "errors#[] should return an empty Array"
end
- 1
private
- 1
def model
- 36
assert_respond_to @model, :to_model
- 36
@model.to_model
end
- 1
def assert_boolean(result, name)
- 2
assert result == true || result == false, "#{name} should be a boolean"
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# == Active \Model \Basic \Model
#
# Includes the required interface for an object to interact with
# Action Pack and Action View, using different Active Model modules.
# It includes model name introspections, conversions, translations and
# validations. Besides that, it allows you to initialize the object with a
# hash of attributes, pretty much like Active Record does.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::Model
# attr_accessor :name, :age
# end
#
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
# person.age # => "18"
#
# Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
# to return +false+, which is the most common case. You may want to override
# it in your class to simulate a different scenario:
#
# class Person
# include ActiveModel::Model
# attr_accessor :id, :name
#
# def persisted?
# self.id == 1
# end
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.persisted? # => true
#
# Also, if for some reason you need to run code on <tt>initialize</tt>, make
# sure you call +super+ if you want the attributes hash initialization to
# happen.
#
# class Person
# include ActiveModel::Model
# attr_accessor :id, :name, :omg
#
# def initialize(attributes={})
# super
# @omg ||= true
# end
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.omg # => true
#
# For more detailed information on other functionalities available, please
# refer to the specific modules included in <tt>ActiveModel::Model</tt>
# (see below).
- 1
module Model
- 1
extend ActiveSupport::Concern
- 1
include ActiveModel::AttributeAssignment
- 1
include ActiveModel::Validations
- 1
include ActiveModel::Conversion
- 1
included do
- 5
extend ActiveModel::Naming
- 5
extend ActiveModel::Translation
end
# Initializes a new model with the given +params+.
#
# class Person
# include ActiveModel::Model
# attr_accessor :name, :age
# end
#
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
# person.age # => "18"
- 1
def initialize(attributes = {})
- 52
assign_attributes(attributes) if attributes
- 50
super()
end
# Indicates if the model is persisted. Default is +false+.
#
# class Person
# include ActiveModel::Model
# attr_accessor :id, :name
# end
#
# person = Person.new(id: 1, name: 'bob')
# person.persisted? # => false
- 1
def persisted?
- 2
false
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/hash/except"
- 1
require "active_support/core_ext/module/introspection"
- 1
require "active_support/core_ext/module/redefine_method"
- 1
module ActiveModel
- 1
class Name
- 1
include Comparable
- 1
attr_reader :singular, :plural, :element, :collection,
:singular_route_key, :route_key, :param_key, :i18n_key,
:name
- 1
alias_method :cache_key, :collection
##
# :method: ==
#
# :call-seq:
# ==(other)
#
# Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
# +other+ are equal, otherwise +false+.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name == 'BlogPost' # => true
# BlogPost.model_name == 'Blog Post' # => false
##
# :method: ===
#
# :call-seq:
# ===(other)
#
# Equivalent to <tt>#==</tt>.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name === 'BlogPost' # => true
# BlogPost.model_name === 'Blog Post' # => false
##
# :method: <=>
#
# :call-seq:
# <=>(other)
#
# Equivalent to <tt>String#<=></tt>.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name <=> 'BlogPost' # => 0
# BlogPost.model_name <=> 'Blog' # => 1
# BlogPost.model_name <=> 'BlogPosts' # => -1
##
# :method: =~
#
# :call-seq:
# =~(regexp)
#
# Equivalent to <tt>String#=~</tt>. Match the class name against the given
# regexp. Returns the position where the match starts or +nil+ if there is
# no match.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name =~ /Post/ # => 4
# BlogPost.model_name =~ /\d/ # => nil
##
# :method: !~
#
# :call-seq:
# !~(regexp)
#
# Equivalent to <tt>String#!~</tt>. Match the class name against the given
# regexp. Returns +true+ if there is no match, otherwise +false+.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name !~ /Post/ # => false
# BlogPost.model_name !~ /\d/ # => true
##
# :method: eql?
#
# :call-seq:
# eql?(other)
#
# Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
# +other+ have the same length and content, otherwise +false+.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name.eql?('BlogPost') # => true
# BlogPost.model_name.eql?('Blog Post') # => false
##
# :method: match?
#
# :call-seq:
# match?(regexp)
#
# Equivalent to <tt>String#match?</tt>. Match the class name against the
# given regexp. Returns +true+ if there is a match, otherwise +false+.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name.match?(/Post/) # => true
# BlogPost.model_name.match?(/\d/) # => false
##
# :method: to_s
#
# :call-seq:
# to_s()
#
# Returns the class name.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name.to_s # => "BlogPost"
##
# :method: to_str
#
# :call-seq:
# to_str()
#
# Equivalent to +to_s+.
- 1
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :match?, :to_s,
:to_str, :as_json, to: :name
# Returns a new ActiveModel::Name instance. By default, the +namespace+
# and +name+ option will take the namespace and name of the given class
# respectively.
#
# module Foo
# class Bar
# end
# end
#
# ActiveModel::Name.new(Foo::Bar).to_s
# # => "Foo::Bar"
- 1
def initialize(klass, namespace = nil, name = nil)
- 118
@name = name || klass.name
- 118
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
- 117
@unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
- 117
@klass = klass
- 117
@singular = _singularize(@name)
- 117
@plural = ActiveSupport::Inflector.pluralize(@singular)
- 117
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
- 117
@human = ActiveSupport::Inflector.humanize(@element)
- 117
@collection = ActiveSupport::Inflector.tableize(@name)
- 117
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
- 117
@i18n_key = @name.underscore.to_sym
- 117
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
- 117
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
- 117
@route_key << "_index" if @plural == @singular
end
# Transform the model name into a more human format, using I18n. By default,
# it will underscore then humanize the class name.
#
# class BlogPost
# extend ActiveModel::Naming
# end
#
# BlogPost.model_name.human # => "Blog post"
#
# Specify +options+ with additional translating options.
- 1
def human(options = {})
- 571
return @human unless @klass.respond_to?(:lookup_ancestors) &&
@klass.respond_to?(:i18n_scope)
- 519
defaults = @klass.lookup_ancestors.map do |klass|
- 572
klass.model_name.i18n_key
end
- 519
defaults << options[:default] if options[:default]
- 519
defaults << @human
- 519
options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default))
- 519
I18n.translate(defaults.shift, **options)
end
- 1
private
- 1
def _singularize(string)
- 126
ActiveSupport::Inflector.underscore(string).tr("/", "_")
end
end
# == Active \Model \Naming
#
# Creates a +model_name+ method on your object.
#
# To implement, just extend ActiveModel::Naming in your object:
#
# class BookCover
# extend ActiveModel::Naming
# end
#
# BookCover.model_name.name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
# BookCover.model_name.i18n_key # => :book_cover
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the \Active \Model Lint test. So either extending the
# provided method below, or rolling your own is required.
- 1
module Naming
- 1
def self.extended(base) #:nodoc:
- 27
base.silence_redefinition_of_method :model_name
- 27
base.delegate :model_name, to: :class
end
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information
# (See ActiveModel::Name for more information).
#
# class Person
# extend ActiveModel::Naming
# end
#
# Person.model_name.name # => "Person"
# Person.model_name.class # => ActiveModel::Name
# Person.model_name.singular # => "person"
# Person.model_name.plural # => "people"
- 1
def model_name
- 3048
@_model_name ||= begin
- 84
namespace = module_parents.detect do |n|
- 158
n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
end
- 84
ActiveModel::Name.new(self, namespace)
end
end
# Returns the plural class name of a record or class.
#
# ActiveModel::Naming.plural(post) # => "posts"
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
- 1
def self.plural(record_or_class)
- 5
model_name_from_record_or_class(record_or_class).plural
end
# Returns the singular class name of a record or class.
#
# ActiveModel::Naming.singular(post) # => "post"
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
- 1
def self.singular(record_or_class)
- 4
model_name_from_record_or_class(record_or_class).singular
end
# Identifies whether the class name of a record or class is uncountable.
#
# ActiveModel::Naming.uncountable?(Sheep) # => true
# ActiveModel::Naming.uncountable?(Post) # => false
- 1
def self.uncountable?(record_or_class)
- 2
plural(record_or_class) == singular(record_or_class)
end
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "post"
#
# # For shared engine:
# ActiveModel::Naming.singular_route_key(Blog::Post) # => "blog_post"
- 1
def self.singular_route_key(record_or_class)
- 3
model_name_from_record_or_class(record_or_class).singular_route_key
end
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
# ActiveModel::Naming.route_key(Blog::Post) # => "posts"
#
# # For shared engine:
# ActiveModel::Naming.route_key(Blog::Post) # => "blog_posts"
#
# The route key also considers if the noun is uncountable and, in
# such cases, automatically appends _index.
- 1
def self.route_key(record_or_class)
- 3
model_name_from_record_or_class(record_or_class).route_key
end
# Returns string to use for params names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
# ActiveModel::Naming.param_key(Blog::Post) # => "post"
#
# # For shared engine:
# ActiveModel::Naming.param_key(Blog::Post) # => "blog_post"
- 1
def self.param_key(record_or_class)
- 2
model_name_from_record_or_class(record_or_class).param_key
end
- 1
def self.model_name_from_record_or_class(record_or_class) #:nodoc:
- 17
if record_or_class.respond_to?(:to_model)
- 6
record_or_class.to_model.model_name
else
- 11
record_or_class.model_name
end
end
- 1
private_class_method :model_name_from_record_or_class
end
end
# frozen_string_literal: true
- 1
require "active_model/error"
- 1
require "forwardable"
- 1
module ActiveModel
- 1
class NestedError < Error
- 1
def initialize(base, inner_error, override_options = {})
- 7
@base = base
- 7
@inner_error = inner_error
- 12
@attribute = override_options.fetch(:attribute) { inner_error.attribute }
- 13
@type = override_options.fetch(:type) { inner_error.type }
- 7
@raw_type = inner_error.raw_type
- 7
@options = inner_error.options
end
- 1
attr_reader :inner_error
- 1
extend Forwardable
- 1
def_delegators :@inner_error, :message
end
end
# frozen_string_literal: true
require "active_model"
require "rails"
module ActiveModel
class Railtie < Rails::Railtie # :nodoc:
config.eager_load_namespaces << ActiveModel
config.active_model = ActiveSupport::OrderedOptions.new
initializer "active_model.secure_password" do
ActiveModel::SecurePassword.min_cost = Rails.env.test?
end
initializer "active_model.i18n_customize_full_message" do
ActiveModel::Error.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module SecurePassword
- 1
extend ActiveSupport::Concern
# BCrypt hash function can handle maximum 72 bytes, and if we pass
# password of length more than 72 bytes it ignores extra characters.
# Hence need to put a restriction on password length.
- 1
MAX_PASSWORD_LENGTH_ALLOWED = 72
- 1
class << self
- 1
attr_accessor :min_cost # :nodoc:
end
- 1
self.min_cost = false
- 1
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a +XXX_digest+ attribute.
# Where +XXX+ is the attribute name of your desired password.
#
# The following validations are added automatically:
# * Password must be present on creation
# * Password length should be less than or equal to 72 bytes
# * Confirmation of password (using a +XXX_confirmation+ attribute)
#
# If confirmation validation is not needed, simply leave out the
# value for +XXX_confirmation+ (i.e. don't provide a form field for
# it). When this attribute has a +nil+ value, the validation will not be
# triggered.
#
# For further customizability, it is possible to suppress the default
# validations by passing <tt>validations: false</tt> as an argument.
#
# Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
#
# gem 'bcrypt', '~> 3.1.7'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
# # Schema: User(name:string, password_digest:string, recovery_password_digest:string)
# class User < ActiveRecord::Base
# has_secure_password
# has_secure_password :recovery_password, validations: false
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
# user.save # => false, password required
# user.password = 'mUc3m00RsqyRe'
# user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
# user.save # => true
# user.recovery_password = "42password"
# user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
# user.save # => true
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
# user.authenticate_recovery_password('42password') # => user
# User.find_by(name: 'david')&.authenticate('notright') # => false
# User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
- 1
def has_secure_password(attribute = :password, validations: true)
# Load bcrypt gem only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
- 3
begin
- 3
require "bcrypt"
rescue LoadError
$stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install"
raise
end
- 3
include InstanceMethodsOnActivation.new(attribute)
- 3
if validations
- 1
include ActiveModel::Validations
# This ensures the model has a password by checking whether the password_digest
# is present, so that this works with both new and existing records. However,
# when there is an error, the message is added to the password attribute instead
# so that the error message will make sense to the end-user.
- 1
validate do |record|
- 22
record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
end
- 1
validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
- 1
validates_confirmation_of attribute, allow_blank: true
end
end
end
- 1
class InstanceMethodsOnActivation < Module
- 1
def initialize(attribute)
- 3
attr_reader attribute
- 3
define_method("#{attribute}=") do |unencrypted_password|
- 28
if unencrypted_password.nil?
- 3
self.send("#{attribute}_digest=", nil)
- 25
elsif !unencrypted_password.empty?
- 21
instance_variable_set("@#{attribute}", unencrypted_password)
- 21
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
- 21
self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
end
end
- 3
define_method("#{attribute}_confirmation=") do |unencrypted_password|
- 13
instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
end
# Returns +self+ if the password is correct, otherwise +false+.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
# end
#
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate_password('notright') # => false
# user.authenticate_password('mUc3m00RsqyRe') # => user
- 3
define_method("authenticate_#{attribute}") do |unencrypted_password|
- 6
attribute_digest = send("#{attribute}_digest")
- 6
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
end
- 3
alias_method :authenticate, :authenticate_password if attribute == :password
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# == Active \Model \Serialization
#
# Provides a basic serialization to a serializable_hash for your objects.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::Serialization
#
# attr_accessor :name
#
# def attributes
# {'name' => nil}
# end
# end
#
# Which would provide you with:
#
# person = Person.new
# person.serializable_hash # => {"name"=>nil}
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
#
# An +attributes+ hash must be defined and should contain any attributes you
# need to be serialized. Attributes must be strings, not symbols.
# When called, serializable hash will use instance methods that match the name
# of the attributes hash's keys. In order to override this behavior, take a look
# at the private method +read_attribute_for_serialization+.
#
# ActiveModel::Serializers::JSON module automatically includes
# the <tt>ActiveModel::Serialization</tt> module, so there is no need to
# explicitly include <tt>ActiveModel::Serialization</tt>.
#
# A minimal implementation including JSON would be:
#
# class Person
# include ActiveModel::Serializers::JSON
#
# attr_accessor :name
#
# def attributes
# {'name' => nil}
# end
# end
#
# Which would provide you with:
#
# person = Person.new
# person.serializable_hash # => {"name"=>nil}
# person.as_json # => {"name"=>nil}
# person.to_json # => "{\"name\":null}"
#
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
# person.as_json # => {"name"=>"Bob"}
# person.to_json # => "{\"name\":\"Bob\"}"
#
# Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
# <tt>:include</tt>. The following are all valid examples:
#
# person.serializable_hash(only: 'name')
# person.serializable_hash(include: :address)
# person.serializable_hash(include: { address: { only: 'city' }})
- 1
module Serialization
# Returns a serialized hash of your object.
#
# class Person
# include ActiveModel::Serialization
#
# attr_accessor :name, :age
#
# def attributes
# {'name' => nil, 'age' => nil}
# end
#
# def capitalized_name
# name.capitalize
# end
# end
#
# person = Person.new
# person.name = 'bob'
# person.age = 22
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
# person.serializable_hash(only: :name) # => {"name"=>"bob"}
# person.serializable_hash(except: :name) # => {"age"=>22}
# person.serializable_hash(methods: :capitalized_name)
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
#
# Example with <tt>:include</tt> option
#
# class User
# include ActiveModel::Serializers::JSON
# attr_accessor :name, :notes # Emulate has_many :notes
# def attributes
# {'name' => nil}
# end
# end
#
# class Note
# include ActiveModel::Serializers::JSON
# attr_accessor :title, :text
# def attributes
# {'title' => nil, 'text' => nil}
# end
# end
#
# note = Note.new
# note.title = 'Battle of Austerlitz'
# note.text = 'Some text here'
#
# user = User.new
# user.name = 'Napoleon'
# user.notes = [note]
#
# user.serializable_hash
# # => {"name" => "Napoleon"}
# user.serializable_hash(include: { notes: { only: 'title' }})
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
- 1
def serializable_hash(options = nil)
- 58
attribute_names = attributes.keys
- 58
return serializable_attributes(attribute_names) if options.blank?
- 39
if only = options[:only]
- 15
attribute_names &= Array(only).map(&:to_s)
- 24
elsif except = options[:except]
- 7
attribute_names -= Array(except).map(&:to_s)
end
- 39
hash = serializable_attributes(attribute_names)
- 49
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
- 38
serializable_add_includes(options) do |association, records, opts|
- 16
hash[association.to_s] = if records.respond_to?(:to_ary)
- 28
records.to_ary.map { |a| a.serializable_hash(opts) }
else
- 5
records.serializable_hash(opts)
end
end
- 38
hash
end
- 1
private
# Hook method defining how an attribute value should be retrieved for
# serialization. By default this is assumed to be an instance named after
# the attribute. Override this method in subclasses should you need to
# retrieve the value for a given attribute differently:
#
# class MyClass
# include ActiveModel::Serialization
#
# def initialize(data = {})
# @data = data
# end
#
# def read_attribute_for_serialization(key)
# @data[key]
# end
# end
- 1
alias :read_attribute_for_serialization :send
- 1
def serializable_attributes(attribute_names)
- 221
attribute_names.index_with { |n| read_attribute_for_serialization(n) }
end
# Add associations specified via the <tt>:include</tt> option.
#
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
- 1
def serializable_add_includes(options = {}) #:nodoc:
- 38
return unless includes = options[:include]
- 13
unless includes.is_a?(Hash)
- 20
includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
end
- 13
includes.each do |association, opts|
- 16
if records = send(association)
- 16
yield association, records, opts
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/json"
- 1
module ActiveModel
- 1
module Serializers
# == Active \Model \JSON \Serializer
- 1
module JSON
- 1
extend ActiveSupport::Concern
- 1
include ActiveModel::Serialization
- 1
included do
- 1
extend ActiveModel::Naming
- 1
class_attribute :include_root_in_json, instance_writer: false, default: false
end
# Returns a hash representing the model. Some configuration can be
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
# of +as_json+. If +true+, +as_json+ will emit a single root node named
# after the object's type. The default value for <tt>include_root_in_json</tt>
# option is +false+.
#
# user = User.find(1)
# user.as_json
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true}
#
# ActiveRecord::Base.include_root_in_json = true
#
# user.as_json
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
#
# This behavior can also be achieved by setting the <tt>:root</tt> option
# to +true+ as in:
#
# user = User.find(1)
# user.as_json(root: true)
# # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
#
# If you prefer, <tt>:root</tt> may also be set to a custom string key instead as in:
#
# user = User.find(1)
# user.as_json(root: "author")
# # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
#
# Without any +options+, the returned Hash will include all the model's
# attributes.
#
# user = User.find(1)
# user.as_json
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
#
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
# the attributes included, and work similar to the +attributes+ method.
#
# user.as_json(only: [:id, :name])
# # => { "id" => 1, "name" => "Konata Izumi" }
#
# user.as_json(except: [:id, :created_at, :age])
# # => { "name" => "Konata Izumi", "awesome" => true }
#
# To include the result of some method calls on the model use <tt>:methods</tt>:
#
# user.as_json(methods: :permalink)
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
# # "permalink" => "1-konata-izumi" }
#
# To include associations use <tt>:include</tt>:
#
# user.as_json(include: :posts)
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
# # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
# # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
#
# Second level and higher order associations work as well:
#
# user.as_json(include: { posts: {
# include: { comments: {
# only: :body } },
# only: :title } })
# # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
# # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
# # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
# # "title" => "Welcome to the weblog" },
# # { "comments" => [ { "body" => "Don't think too hard" } ],
# # "title" => "So I was thinking" } ] }
- 1
def as_json(options = nil)
- 16
root = if options && options.key?(:root)
- 4
options[:root]
else
- 12
include_root_in_json
end
- 16
hash = serializable_hash(options).as_json
- 16
if root
- 5
root = model_name.element if root == true
- 5
{ root => hash }
else
- 11
hash
end
end
# Sets the model +attributes+ from a JSON string. Returns +self+.
#
# class Person
# include ActiveModel::Serializers::JSON
#
# attr_accessor :name, :age, :awesome
#
# def attributes=(hash)
# hash.each do |key, value|
# send("#{key}=", value)
# end
# end
#
# def attributes
# instance_values
# end
# end
#
# json = { name: 'bob', age: 22, awesome:true }.to_json
# person = Person.new
# person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
# person.name # => "bob"
# person.age # => 22
# person.awesome # => true
#
# The default value for +include_root+ is +false+. You can change it to
# +true+ if the given JSON string includes a single root node.
#
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
# person = Person.new
# person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
# person.name # => "bob"
# person.age # => 22
# person.awesome # => true
- 1
def from_json(json, include_root = include_root_in_json)
- 3
hash = ActiveSupport::JSON.decode(json)
- 3
hash = hash.values.first if include_root
- 3
self.attributes = hash
- 3
self
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# == Active \Model \Translation
#
# Provides integration between your object and the Rails internationalization
# (i18n) framework.
#
# A minimal implementation could be:
#
# class TranslatedPerson
# extend ActiveModel::Translation
# end
#
# TranslatedPerson.human_attribute_name('my_attribute')
# # => "My attribute"
#
# This also provides the required class methods for hooking into the
# Rails internationalization API, including being able to define a
# class based +i18n_scope+ and +lookup_ancestors+ to find translations in
# parent classes.
- 1
module Translation
- 1
include ActiveModel::Naming
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
- 1
def i18n_scope
- 1636
:activemodel
end
# When localizing a string, it goes through the lookup returned by this
# method, which is used in ActiveModel::Name#human,
# ActiveModel::Errors#full_messages and
# ActiveModel::Translation#human_attribute_name.
- 1
def lookup_ancestors
- 23163
ancestors.select { |x| x.respond_to?(:model_name) }
end
# Transforms attribute names into a more human format, such as "First name"
# instead of "first_name".
#
# Person.human_attribute_name("first_name") # => "First name"
#
# Specify +options+ with additional translating options.
- 1
def human_attribute_name(attribute, options = {})
- 586
options = { count: 1 }.merge!(options)
- 586
parts = attribute.to_s.split(".")
- 586
attribute = parts.pop
- 586
namespace = parts.join("/") unless parts.empty?
- 586
attributes_scope = "#{i18n_scope}.attributes"
- 586
if namespace
- 19
defaults = lookup_ancestors.map do |klass|
- 34
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
end
- 19
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
else
- 567
defaults = lookup_ancestors.map do |klass|
- 641
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
end
end
- 586
defaults << :"attributes.#{attribute}"
- 586
defaults << options.delete(:default) if options[:default]
- 586
defaults << attribute.humanize
- 586
options[:default] = defaults
- 586
I18n.translate(defaults.shift, **options)
end
end
end
# frozen_string_literal: true
- 1
require "active_model/type/helpers"
- 1
require "active_model/type/value"
- 1
require "active_model/type/big_integer"
- 1
require "active_model/type/binary"
- 1
require "active_model/type/boolean"
- 1
require "active_model/type/date"
- 1
require "active_model/type/date_time"
- 1
require "active_model/type/decimal"
- 1
require "active_model/type/float"
- 1
require "active_model/type/immutable_string"
- 1
require "active_model/type/integer"
- 1
require "active_model/type/string"
- 1
require "active_model/type/time"
- 1
require "active_model/type/registry"
- 1
module ActiveModel
- 1
module Type
- 1
@registry = Registry.new
- 1
class << self
- 1
attr_accessor :registry # :nodoc:
# Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup
- 1
def register(type_name, klass = nil, **options, &block)
- 11
registry.register(type_name, klass, **options, &block)
end
- 1
def lookup(*args, **kwargs) # :nodoc:
- 10
registry.lookup(*args, **kwargs)
end
- 1
def default_value # :nodoc:
- 13
@default_value ||= Value.new
end
end
- 1
register(:big_integer, Type::BigInteger)
- 1
register(:binary, Type::Binary)
- 1
register(:boolean, Type::Boolean)
- 1
register(:date, Type::Date)
- 1
register(:datetime, Type::DateTime)
- 1
register(:decimal, Type::Decimal)
- 1
register(:float, Type::Float)
- 1
register(:immutable_string, Type::ImmutableString)
- 1
register(:integer, Type::Integer)
- 1
register(:string, Type::String)
- 1
register(:time, Type::Time)
end
end
# frozen_string_literal: true
- 1
require "active_model/type/integer"
- 1
module ActiveModel
- 1
module Type
- 1
class BigInteger < Integer # :nodoc:
- 1
private
- 1
def max_value
- 6
::Float::INFINITY
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class Binary < Value # :nodoc:
- 1
def type
:binary
end
- 1
def binary?
true
end
- 1
def cast(value)
- 3
if value.is_a?(Data)
value.to_s
else
- 3
super
end
end
- 1
def serialize(value)
return if value.nil?
Data.new(super)
end
- 1
def changed_in_place?(raw_old_value, value)
old_value = deserialize(raw_old_value)
old_value != value
end
- 1
class Data # :nodoc:
- 1
def initialize(value)
@value = value.to_s
end
- 1
def to_s
@value
end
- 1
alias_method :to_str, :to_s
- 1
def hex
@value.unpack1("H*")
end
- 1
def ==(other)
other == to_s || super
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
# == Active \Model \Type \Boolean
#
# A class that behaves like a boolean type, including rules for coercion of user input.
#
# === Coercion
# Values set from user input will first be coerced into the appropriate ruby type.
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
#
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
# - Empty strings are coerced to +nil+
# - All other values will be coerced to +true+
- 1
class Boolean < Value
- 1
FALSE_VALUES = [
false, 0,
"0", :"0",
"f", :f,
"F", :F,
"false", :false,
"FALSE", :FALSE,
"off", :off,
"OFF", :OFF,
].to_set.freeze
- 1
def type # :nodoc:
:boolean
end
- 1
def serialize(value) # :nodoc:
cast(value)
end
- 1
private
- 1
def cast_value(value)
- 40
if value == ""
nil
else
- 39
!FALSE_VALUES.include?(value)
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class Date < Value # :nodoc:
- 1
include Helpers::Timezone
- 1
include Helpers::AcceptsMultiparameterTime.new
- 1
def type
:date
end
- 1
def type_cast_for_schema(value)
value.to_s(:db).inspect
end
- 1
private
- 1
def cast_value(value)
- 8
if value.is_a?(::String)
- 4
return if value.empty?
- 3
fast_string_to_date(value) || fallback_string_to_date(value)
- 4
elsif value.respond_to?(:to_date)
- 4
value.to_date
else
value
end
end
- 1
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
- 1
def fast_string_to_date(string)
- 3
if string =~ ISO_DATE
- 1
new_date $1.to_i, $2.to_i, $3.to_i
end
end
- 1
def fallback_string_to_date(string)
- 2
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
end
- 1
def new_date(year, mon, mday)
- 5
unless year.nil? || (year == 0 && mon == 0 && mday == 0)
- 3
::Date.new(year, mon, mday) rescue nil
end
end
- 1
def value_from_multiparameter_assignment(*)
- 2
time = super
- 2
time && new_date(time.year, time.mon, time.mday)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class DateTime < Value # :nodoc:
- 1
include Helpers::Timezone
- 1
include Helpers::TimeValue
- 1
include Helpers::AcceptsMultiparameterTime.new(
defaults: { 4 => 0, 5 => 0 }
)
- 1
def type
:datetime
end
- 1
private
- 1
def cast_value(value)
- 6
return apply_seconds_precision(value) unless value.is_a?(::String)
- 6
return if value.empty?
- 5
fast_string_to_time(value) || fallback_string_to_time(value)
end
# '0.123456' -> 123456
# '1.123456' -> 123456
- 1
def microseconds(time)
- 4
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
end
- 1
def fallback_string_to_time(string)
- 4
time_hash = ::Date._parse(string)
- 4
time_hash[:sec_fraction] = microseconds(time_hash)
- 4
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
- 1
def value_from_multiparameter_assignment(values_hash)
- 8
missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
- 2
unless missing_parameters.empty?
- 1
raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
end
- 1
super
end
end
end
end
# frozen_string_literal: true
- 1
require "bigdecimal/util"
- 1
module ActiveModel
- 1
module Type
- 1
class Decimal < Value # :nodoc:
- 1
include Helpers::Numeric
- 1
BIGDECIMAL_PRECISION = 18
- 1
def type
:decimal
end
- 1
def type_cast_for_schema(value)
value.to_s.inspect
end
- 1
private
- 1
def cast_value(value)
- 17
casted_value = \
case value
when ::Float
- 5
convert_float_to_big_decimal(value)
when ::Numeric
- 4
BigDecimal(value, precision || BIGDECIMAL_PRECISION)
when ::String
- 6
begin
- 6
value.to_d
rescue ArgumentError
BigDecimal(0)
end
else
- 2
if value.respond_to?(:to_d)
- 1
value.to_d
else
- 1
cast_value(value.to_s)
end
end
- 17
apply_scale(casted_value)
end
- 1
def convert_float_to_big_decimal(value)
- 5
if precision
- 2
BigDecimal(apply_scale(value), float_precision)
else
- 3
value.to_d
end
end
- 1
def float_precision
- 2
if precision.to_i > ::Float::DIG + 1
- 1
::Float::DIG + 1
else
- 1
precision.to_i
end
end
- 1
def apply_scale(value)
- 19
if scale
- 4
value.round(scale)
else
- 15
value
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/object/try"
- 1
module ActiveModel
- 1
module Type
- 1
class Float < Value # :nodoc:
- 1
include Helpers::Numeric
- 1
def type
:float
end
- 1
def type_cast_for_schema(value)
return "::Float::NAN" if value.try(:nan?)
case value
when ::Float::INFINITY then "::Float::INFINITY"
when -::Float::INFINITY then "-::Float::INFINITY"
else super
end
end
- 1
private
- 1
def cast_value(value)
- 9
case value
when ::Float then value
when "Infinity" then ::Float::INFINITY
when "-Infinity" then -::Float::INFINITY
when "NaN" then ::Float::NAN
- 9
else value.to_f
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_model/type/helpers/accepts_multiparameter_time"
- 1
require "active_model/type/helpers/numeric"
- 1
require "active_model/type/helpers/mutable"
- 1
require "active_model/type/helpers/time_value"
- 1
require "active_model/type/helpers/timezone"
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
module Helpers # :nodoc: all
- 1
class AcceptsMultiparameterTime < Module
- 1
module InstanceMethods
- 1
def serialize(value)
super(cast(value))
end
- 1
def cast(value)
- 27
if value.is_a?(Hash)
- 5
value_from_multiparameter_assignment(value)
else
- 22
super(value)
end
end
- 1
def assert_valid_value(value)
if value.is_a?(Hash)
value_from_multiparameter_assignment(value)
else
super(value)
end
end
- 1
def value_constructed_by_mass_assignment?(value)
value.is_a?(Hash)
end
end
- 1
def initialize(defaults: {})
- 3
include InstanceMethods
- 3
define_method(:value_from_multiparameter_assignment) do |values_hash|
- 4
defaults.each do |k, v|
- 7
values_hash[k] ||= v
end
- 4
return unless values_hash[1] && values_hash[2] && values_hash[3]
- 4
values = values_hash.sort.map!(&:last)
- 4
::Time.send(default_timezone, *values)
end
- 3
private :value_from_multiparameter_assignment
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
module Helpers # :nodoc: all
- 1
module Mutable
- 1
def cast(value)
deserialize(serialize(value))
end
# +raw_old_value+ will be the `_before_type_cast` version of the
# value (likely a string). +new_value+ will be the current, type
# cast value.
- 1
def changed_in_place?(raw_old_value, new_value)
raw_old_value != serialize(new_value)
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
module Helpers # :nodoc: all
- 1
module Numeric
- 1
def serialize(value)
- 34
cast(value)
end
- 1
def cast(value)
# Checks whether the value is numeric. Spaceship operator
# will return nil if value is not numeric.
- 93
value = if value <=> 0
- 37
value
else
- 56
case value
- 2
when true then 1
- 2
when false then 0
- 52
else value.presence
end
end
- 93
super(value)
end
- 1
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
- 28
super || number_to_non_number?(old_value, new_value_before_type_cast)
end
- 1
private
- 1
def number_to_non_number?(old_value, new_value_before_type_cast)
- 21
old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
end
- 1
def non_numeric_string?(value)
# 'wibble'.to_i will give zero, we want to make sure
# that we aren't marking int zero to string zero as
# changed.
- 24
!NUMERIC_REGEX.match?(value)
end
- 1
NUMERIC_REGEX = /\A\s*[+-]?\d/
- 1
private_constant :NUMERIC_REGEX
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/string/zones"
- 1
require "active_support/core_ext/time/zones"
- 1
module ActiveModel
- 1
module Type
- 1
module Helpers # :nodoc: all
- 1
module TimeValue
- 1
def serialize(value)
value = apply_seconds_precision(value)
if value.acts_like?(:time)
if is_utc?
value = value.getutc if value.respond_to?(:getutc) && !value.utc?
else
value = value.getlocal if value.respond_to?(:getlocal)
end
end
value
end
- 1
def apply_seconds_precision(value)
return value unless precision && value.respond_to?(:nsec)
number_of_insignificant_digits = 9 - precision
round_power = 10**number_of_insignificant_digits
rounded_off_nsec = value.nsec % round_power
if rounded_off_nsec > 0
value.change(nsec: value.nsec - rounded_off_nsec)
else
value
end
end
- 1
def type_cast_for_schema(value)
value.to_s(:db).inspect
end
- 1
def user_input_in_time_zone(value)
- 2
value.in_time_zone
end
- 1
private
- 1
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
# Treat 0000-00-00 00:00:00 as nil.
- 8
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
- 6
if offset
- 4
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
- 4
return unless time
- 4
time -= offset unless offset == 0
- 4
is_utc? ? time : time.getlocal
- 2
elsif is_utc?
- 2
::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
else
::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil
end
end
- 1
ISO_DATETIME = /
\A
(\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T
(\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456
(?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00
\z
/x
- 1
def fast_string_to_time(string)
- 9
return unless ISO_DATETIME =~ string
- 3
usec = $7.to_i
- 3
usec_len = $7&.length
- 3
if usec_len&.< 6
usec *= 10 ** (6 - usec_len)
end
- 3
if $8
- 1
offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
end
- 3
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/time/zones"
- 1
module ActiveModel
- 1
module Type
- 1
module Helpers # :nodoc: all
- 1
module Timezone
- 1
def is_utc?
- 10
::Time.zone_default.nil? || ::Time.zone_default.match?("UTC")
end
- 1
def default_timezone
- 4
is_utc? ? :utc : :local
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class ImmutableString < Value # :nodoc:
- 1
def initialize(**args)
- 17
@true = -(args.delete(:true)&.to_s || "t")
- 17
@false = -(args.delete(:false)&.to_s || "f")
- 17
super
end
- 1
def type
:string
end
- 1
def serialize(value)
- 36
case value
when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s
when true then @true
when false then @false
- 36
else super
end
end
- 1
private
- 1
def cast_value(value)
- 3
case value
when true then @true
when false then @false
- 3
else value.to_s.freeze
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class Integer < Value # :nodoc:
- 1
include Helpers::Numeric
# Column storage size in bytes.
# 4 bytes means an integer as opposed to smallint etc.
- 1
DEFAULT_LIMIT = 4
- 1
def initialize(**)
- 55
super
- 55
@range = min_value...max_value
end
- 1
def type
:integer
end
- 1
def deserialize(value)
- 20
return if value.blank?
- 18
value.to_i
end
- 1
def serialize(value)
- 36
return if value.is_a?(::String) && non_numeric_string?(value)
- 34
ensure_in_range(super)
end
- 1
def serializable?(value)
cast_value = cast(value)
in_range?(cast_value) && super
end
- 1
private
- 1
attr_reader :range
- 1
def in_range?(value)
- 34
!value || range.member?(value)
end
- 51
def cast_value(value)
- 6
value.to_i rescue nil
end
- 1
def ensure_in_range(value)
- 34
unless in_range?(value)
- 6
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
end
- 28
value
end
- 1
def max_value
- 104
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
end
- 1
def min_value
- 55
-max_value
end
- 1
def _limit
- 110
limit || DEFAULT_LIMIT
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
# :stopdoc:
- 1
module Type
- 1
class Registry
- 1
def initialize
- 4
@registrations = []
end
- 1
def initialize_dup(other)
@registrations = @registrations.dup
super
end
- 1
def register(type_name, klass = nil, **options, &block)
- 16
unless block_given?
- 25
block = proc { |_, *args| klass.new(*args) }
- 13
block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
end
- 16
registrations << registration_klass.new(type_name, block, **options)
end
- 1
def lookup(symbol, *args, **kwargs)
- 17
registration = find_registration(symbol, *args, **kwargs)
- 17
if registration
- 16
registration.call(self, symbol, *args, **kwargs)
else
- 1
raise ArgumentError, "Unknown type #{symbol.inspect}"
end
end
- 1
private
- 1
attr_reader :registrations
- 1
def registration_klass
- 16
Registration
end
- 1
def find_registration(symbol, *args, **kwargs)
- 108
registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
end
end
- 1
class Registration
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
- 1
def initialize(name, block, **)
- 16
@name = name
- 16
@block = block
end
- 1
def call(_registry, *args, **kwargs)
- 16
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
- 1
block.call(*args, **kwargs)
else
- 15
block.call(*args)
end
end
- 1
def matches?(type_name, *args, **kwargs)
- 91
type_name == name
end
- 1
private
- 1
attr_reader :name, :block
end
end
# :startdoc:
end
# frozen_string_literal: true
- 1
require "active_model/type/immutable_string"
- 1
module ActiveModel
- 1
module Type
- 1
class String < ImmutableString # :nodoc:
- 1
def changed_in_place?(raw_old_value, new_value)
- 6
if new_value.is_a?(::String)
- 5
raw_old_value != new_value
end
end
- 1
def to_immutable_string
ImmutableString.new(
true: @true,
false: @false,
limit: limit,
precision: precision,
scale: scale,
)
end
- 1
private
- 1
def cast_value(value)
- 78
case value
- 74
when ::String then ::String.new(value)
- 1
when true then @true
- 1
when false then @false
- 2
else value.to_s
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class Time < Value # :nodoc:
- 1
include Helpers::Timezone
- 1
include Helpers::TimeValue
- 1
include Helpers::AcceptsMultiparameterTime.new(
defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
)
- 1
def type
:time
end
- 1
def user_input_in_time_zone(value)
- 5
return unless value.present?
- 3
case value
when ::String
- 3
value = "2000-01-01 #{value}"
- 3
time_hash = ::Date._parse(value)
- 3
return if time_hash[:hour].nil?
when ::Time
value = value.change(year: 2000, day: 1, month: 1)
end
- 2
super(value)
end
- 1
private
- 1
def cast_value(value)
- 5
return apply_seconds_precision(value) unless value.is_a?(::String)
- 5
return if value.empty?
- 4
dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
- 4
fast_string_to_time(dummy_time_value) || begin
- 2
time_hash = ::Date._parse(dummy_time_value)
- 2
return if time_hash[:hour].nil?
- 1
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Type
- 1
class Value
- 1
attr_reader :precision, :scale, :limit
- 1
def initialize(precision: nil, limit: nil, scale: nil)
- 130
@precision = precision
- 130
@scale = scale
- 130
@limit = limit
end
# Returns true if this type can convert +value+ to a type that is usable
# by the database. For example a boolean type can return +true+ if the
# value parameter is a Ruby boolean, but may return +false+ if the value
# parameter is some other object.
- 1
def serializable?(value)
true
end
- 1
def type # :nodoc:
end
# Converts a value from database input to the appropriate ruby type. The
# return value of this method will be returned from
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
# implementation just calls Value#cast.
#
# +value+ The raw input, as provided from the database.
- 1
def deserialize(value)
- 45
cast(value)
end
# Type casts a value from user input (e.g. from a setter). This value may
# be a string from the form builder, or a ruby object passed to a setter.
# There is currently no way to differentiate between which source it came
# from.
#
# The return value of this method will be returned from
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
# Value#cast_value.
#
# +value+ The raw input, as provided to the attribute setter.
- 1
def cast(value)
- 253
cast_value(value) unless value.nil?
end
# Casts a value from the ruby type to a type that the database knows how
# to understand. The returned value from this method should be a
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
# +nil+.
- 1
def serialize(value)
- 36
value
end
# Type casts a value for schema dumping. This method is private, as we are
# hoping to remove it entirely.
- 1
def type_cast_for_schema(value) # :nodoc:
value.inspect
end
# These predicates are not documented, as I need to look further into
# their use, and see if they can be removed entirely.
- 1
def binary? # :nodoc:
false
end
# Determines whether a value has changed for dirty checking. +old_value+
# and +new_value+ will always be type-cast. Types should not need to
# override this method.
- 1
def changed?(old_value, new_value, _new_value_before_type_cast)
- 65
old_value != new_value
end
# Determines whether the mutable value has been modified since it was
# read. Returns +false+ by default. If your type returns an object
# which could be mutated, you should override this method. You will need
# to either:
#
# - pass +new_value+ to Value#serialize and compare it to
# +raw_old_value+
#
# or
#
# - pass +raw_old_value+ to Value#deserialize and compare it to
# +new_value+
#
# +raw_old_value+ The original value, before being passed to
# +deserialize+.
#
# +new_value+ The current value, after type casting.
- 1
def changed_in_place?(raw_old_value, new_value)
- 3
false
end
- 1
def value_constructed_by_mass_assignment?(_value) # :nodoc:
false
end
- 1
def force_equality?(_value) # :nodoc:
false
end
- 1
def map(value) # :nodoc:
yield value
end
- 1
def ==(other)
- 13
self.class == other.class &&
precision == other.precision &&
scale == other.scale &&
limit == other.limit
end
- 1
alias eql? ==
- 1
def hash
[self.class, precision, scale, limit].hash
end
- 1
def assert_valid_value(_)
end
- 1
private
# Convenience method for types which do not need separate type casting
# behavior for user and database inputs. Called by Value#cast for
# values except +nil+.
- 1
def cast_value(value) # :doc:
- 11
value
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/array/extract_options"
- 1
module ActiveModel
# == Active \Model \Validations
#
# Provides a full validation framework to your objects.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :first_name, :last_name
#
# validates_each :first_name, :last_name do |record, attr, value|
# record.errors.add attr, "starts with z." if value.start_with?("z")
# end
# end
#
# Which provides you with the full standard validation stack that you
# know from Active Record:
#
# person = Person.new
# person.valid? # => true
# person.invalid? # => false
#
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
# person.errors.messages # => {first_name:["starts with z."]}
#
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
# object, so there is no need for you to do this manually.
- 1
module Validations
- 1
extend ActiveSupport::Concern
- 1
included do
- 14
extend ActiveModel::Naming
- 14
extend ActiveModel::Callbacks
- 14
extend ActiveModel::Translation
- 14
extend HelperMethods
- 14
include HelperMethods
- 14
attr_accessor :validation_context
- 14
private :validation_context=
- 14
define_callbacks :validate, scope: :name
- 367
class_attribute :_validators, instance_writer: false, default: Hash.new { |h, k| h[k] = [] }
end
- 1
module ClassMethods
# Validates each attribute against a block.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :first_name, :last_name
#
# validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
# record.errors.add attr, "starts with z." if value.start_with?("z")
# end
# end
#
# Options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
# Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or +false+
# value.
- 1
def validates_each(*attr_names, &block)
- 2
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
- 1
VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze # :nodoc:
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
#
# This can be done with a symbol pointing to a method:
#
# class Comment
# include ActiveModel::Validations
#
# validate :must_be_friends
#
# def must_be_friends
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
# With a block which is passed with the current record to be validated:
#
# class Comment
# include ActiveModel::Validations
#
# validate do |comment|
# comment.must_be_friends
# end
#
# def must_be_friends
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
# Or with a block where self points to the current record to be validated:
#
# class Comment
# include ActiveModel::Validations
#
# validate do
# errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
# Note that the return value of validation methods is not relevant.
# It's not possible to halt the validate callback chain.
#
# Options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
# Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
# determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or +false+
# value.
#
# NOTE: Calling +validate+ multiple times on the same method will overwrite previous definitions.
#
- 1
def validate(*args, &block)
- 372
options = args.extract_options!
- 739
if args.all? { |arg| arg.is_a?(Symbol) }
- 15
options.each_key do |k|
- 7
unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
- 1
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
end
end
end
- 371
if options.key?(:on)
- 11
options = options.dup
- 11
options[:on] = Array(options[:on])
- 11
options[:if] = Array(options[:if])
- 11
options[:if].unshift ->(o) {
- 38
!(options[:on] & Array(o.validation_context)).empty?
}
end
- 371
set_callback(:validate, *args, options, &block)
end
# List all validators that are being used to validate the model using
# +validates_with+ method.
#
# class Person
# include ActiveModel::Validations
#
# validates_with MyValidator
# validates_with OtherValidator, on: :create
# validates_with StrictValidator, strict: true
# end
#
# Person.validators
# # => [
# # #<MyValidator:0x007fbff403e808 @options={}>,
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
- 1
def validators
- 2
_validators.values.flatten.uniq
end
# Clears all of the validators and validations.
#
# Note that this will clear anything that is being used to validate
# the model for both the +validates_with+ and +validate+ methods.
# It clears the validators that are created with an invocation of
# +validates_with+ and the callbacks that are set by an invocation
# of +validate+.
#
# class Person
# include ActiveModel::Validations
#
# validates_with MyValidator
# validates_with OtherValidator, on: :create
# validates_with StrictValidator, strict: true
# validate :cannot_be_robot
#
# def cannot_be_robot
# errors.add(:base, 'A person cannot be a robot') if person_is_robot
# end
# end
#
# Person.validators
# # => [
# # #<MyValidator:0x007fbff403e808 @options={}>,
# # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
#
# If one runs <tt>Person.clear_validators!</tt> and then checks to see what
# validators this class has, you would obtain:
#
# Person.validators # => []
#
# Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
# so that:
#
# Person._validate_callbacks.empty? # => true
#
- 1
def clear_validators!
- 658
reset_callbacks(:validate)
- 658
_validators.clear
end
# List all validators that are being used to validate a specific attribute.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name , :age
#
# validates_presence_of :name
# validates_inclusion_of :age, in: 0..99
# end
#
# Person.validators_on(:name)
# # => [
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
# # ]
- 1
def validators_on(*attributes)
- 7
attributes.flat_map do |attribute|
- 8
_validators[attribute.to_sym]
end
end
# Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# end
#
# User.attribute_method?(:name) # => true
# User.attribute_method?(:age) # => false
- 1
def attribute_method?(attribute)
- 22
method_defined?(attribute)
end
# Copy validators on inheritance.
- 1
def inherited(base) #:nodoc:
- 158
dup = _validators.dup
- 158
base._validators = dup.each { |k, v| dup[k] = v.dup }
- 158
super
end
end
# Clean the +Errors+ object if instance is duped.
- 1
def initialize_dup(other) #:nodoc:
- 2
@errors = nil
- 2
super
end
# Returns the +Errors+ object that holds all information about attribute
# error messages.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates_presence_of :name
# end
#
# person = Person.new
# person.valid? # => false
# person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
- 1
def errors
- 3292
@errors ||= Errors.new(self)
end
# Runs all the specified validations and returns +true+ if no errors were
# added otherwise +false+.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates_presence_of :name
# end
#
# person = Person.new
# person.name = ''
# person.valid? # => false
# person.name = 'david'
# person.valid? # => true
#
# Context can optionally be supplied to define which callbacks to test
# against (the context is defined on the validations using <tt>:on</tt>).
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates_presence_of :name, on: :new
# end
#
# person = Person.new
# person.valid? # => true
# person.valid?(:new) # => false
- 1
def valid?(context = nil)
- 918
current_context, self.validation_context = validation_context, context
- 918
errors.clear
- 918
run_validations!
ensure
- 918
self.validation_context = current_context
end
- 1
alias_method :validate, :valid?
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
# added, +false+ otherwise.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates_presence_of :name
# end
#
# person = Person.new
# person.name = ''
# person.invalid? # => true
# person.name = 'david'
# person.invalid? # => false
#
# Context can optionally be supplied to define which callbacks to test
# against (the context is defined on the validations using <tt>:on</tt>).
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates_presence_of :name, on: :new
# end
#
# person = Person.new
# person.invalid? # => false
# person.invalid?(:new) # => true
- 1
def invalid?(context = nil)
- 358
!valid?(context)
end
# Runs all the validations within the specified context. Returns +true+ if
# no errors are found, raises +ValidationError+ otherwise.
#
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
# some <tt>:on</tt> option will only run in the specified context.
- 1
def validate!(context = nil)
- 3
valid?(context) || raise_validation_error
end
# Hook method defining how an attribute value should be retrieved. By default
# this is assumed to be an instance named after the attribute. Override this
# method in subclasses should you need to retrieve the value for a given
# attribute differently:
#
# class MyClass
# include ActiveModel::Validations
#
# def initialize(data = {})
# @data = data
# end
#
# def read_attribute_for_validation(key)
# @data[key]
# end
# end
- 1
alias :read_attribute_for_validation :send
- 1
private
- 1
def run_validations!
- 917
_run_validate_callbacks
- 910
errors.empty?
end
- 1
def raise_validation_error # :doc:
- 2
raise(ValidationError.new(self))
end
end
# = Active Model ValidationError
#
# Raised by <tt>validate!</tt> when the model is invalid. Use the
# +model+ method to retrieve the record which did not validate.
#
# begin
# complex_operation_that_internally_calls_validate!
# rescue ActiveModel::ValidationError => invalid
# puts invalid.model.errors
# end
- 1
class ValidationError < StandardError
- 1
attr_reader :model
- 1
def initialize(model)
- 2
@model = model
- 2
errors = @model.errors.full_messages.join(", ")
- 2
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
end
end
end
- 15
Dir[File.expand_path("validations/*.rb", __dir__)].each { |file| require file }
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
# == \Active \Model Absence Validator
- 1
class AbsenceValidator < EachValidator #:nodoc:
- 1
def validate_each(record, attr_name, value)
- 13
record.errors.add(attr_name, :present, **options) if value.present?
end
end
- 1
module HelperMethods
# Validates that the specified attributes are blank (as defined by
# Object#present?). Happens by default on save.
#
# class Person < ActiveRecord::Base
# validates_absence_of :first_name
# end
#
# The first_name attribute must be in the object and it must be blank.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_absence_of(*attr_names)
- 5
validates_with AbsenceValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
class AcceptanceValidator < EachValidator # :nodoc:
- 1
def initialize(options)
- 22
super({ allow_nil: true, accept: ["1", true] }.merge!(options))
- 22
setup!(options[:class])
end
- 1
def validate_each(record, attribute, value)
- 20
unless acceptable_option?(value)
- 13
record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
end
end
- 1
private
- 1
def setup!(klass)
- 22
define_attributes = LazilyDefineAttributes.new(attributes)
- 22
klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
end
- 1
def acceptable_option?(value)
- 20
Array(options[:accept]).include?(value)
end
- 1
class LazilyDefineAttributes < Module
- 1
def initialize(attributes)
- 22
@attributes = attributes.map(&:to_s)
end
- 1
def included(klass)
- 20
@lock = Mutex.new
- 20
mod = self
- 20
define_method(:respond_to_missing?) do |method_name, include_private = false|
- 4
mod.define_on(klass)
- 4
super(method_name, include_private) || mod.matches?(method_name)
end
- 20
define_method(:method_missing) do |method_name, *args, &block|
- 7
mod.define_on(klass)
- 7
if mod.matches?(method_name)
- 7
send(method_name, *args, &block)
else
super(method_name, *args, &block)
end
end
end
- 1
def matches?(method_name)
- 11
attr_name = method_name.to_s.chomp("=")
- 22
attributes.any? { |name| name == attr_name }
end
- 1
def define_on(klass)
- 11
@lock&.synchronize do
- 11
return unless @lock
- 22
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
- 22
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
- 11
attr_reader(*attr_readers)
- 11
attr_writer(*attr_writers)
- 11
remove_method :respond_to_missing?
- 11
remove_method :method_missing
- 11
@lock = nil
end
end
- 1
def ==(other)
- 4
self.class == other.class && attributes == other.attributes
end
- 1
protected
- 1
attr_reader :attributes
end
end
- 1
module HelperMethods
# Encapsulates the pattern of wanting to validate the acceptance of a
# terms of service check box (or similar agreement).
#
# class Person < ActiveRecord::Base
# validates_acceptance_of :terms_of_service
# validates_acceptance_of :eula, message: 'must be abided'
# end
#
# If the database column does not exist, the +terms_of_service+ attribute
# is entirely virtual. This check is performed only if +terms_of_service+
# is not +nil+ and by default on save.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
# Also accepts an array of possible values. The default value is
# an array ["1", true], which makes it easy to relate to an HTML
# checkbox. This should be set to, or include, +true+ if you are validating
# a database column, since the attribute is typecast from "1" to +true+
# before validation.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information.
- 1
def validates_acceptance_of(*attr_names)
- 22
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
# == Active \Model \Validation \Callbacks
#
# Provides an interface for any class to have +before_validation+ and
# +after_validation+ callbacks.
#
# First, include ActiveModel::Validations::Callbacks from the class you are
# creating:
#
# class MyModel
# include ActiveModel::Validations::Callbacks
#
# before_validation :do_stuff_before_validation
# after_validation :do_stuff_after_validation
# end
#
# Like other <tt>before_*</tt> callbacks if +before_validation+ throws
# +:abort+ then <tt>valid?</tt> will not be called.
- 1
module Callbacks
- 1
extend ActiveSupport::Concern
- 1
included do
- 2
include ActiveSupport::Callbacks
- 2
define_callbacks :validation,
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
- 1
module ClassMethods
# Defines a callback that will get called right before validation.
#
# class Person
# include ActiveModel::Validations
# include ActiveModel::Validations::Callbacks
#
# attr_accessor :name
#
# validates_length_of :name, maximum: 6
#
# before_validation :remove_whitespaces
#
# private
#
# def remove_whitespaces
# name.strip!
# end
# end
#
# person = Person.new
# person.name = ' bob '
# person.valid? # => true
# person.name # => "bob"
- 1
def before_validation(*args, &block)
- 14
options = args.extract_options!
- 14
if options.key?(:on)
- 3
options = options.dup
- 3
options[:on] = Array(options[:on])
- 3
options[:if] = Array(options[:if])
- 3
options[:if].unshift ->(o) {
- 13
!(options[:on] & Array(o.validation_context)).empty?
}
end
- 14
set_callback(:validation, :before, *args, options, &block)
end
# Defines a callback that will get called right after validation.
#
# class Person
# include ActiveModel::Validations
# include ActiveModel::Validations::Callbacks
#
# attr_accessor :name, :status
#
# validates_presence_of :name
#
# after_validation :set_status
#
# private
#
# def set_status
# self.status = errors.empty?
# end
# end
#
# person = Person.new
# person.name = ''
# person.valid? # => false
# person.status # => false
# person.name = 'bob'
# person.valid? # => true
# person.status # => true
- 1
def after_validation(*args, &block)
- 10
options = args.extract_options!
- 10
options = options.dup
- 10
options[:prepend] = true
- 10
if options.key?(:on)
- 3
options[:on] = Array(options[:on])
- 3
options[:if] = Array(options[:if])
- 3
options[:if].unshift ->(o) {
- 13
!(options[:on] & Array(o.validation_context)).empty?
}
end
- 10
set_callback(:validation, :after, *args, options, &block)
end
end
- 1
private
# Overwrite run validations to include callbacks.
- 1
def run_validations!
- 1447
_run_validation_callbacks { super }
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/range"
- 1
module ActiveModel
- 1
module Validations
- 1
module Clusivity #:nodoc:
- 1
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
"and must be supplied as the :in (or :within) option of the configuration hash"
- 1
def check_validity!
- 52
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
- 2
raise ArgumentError, ERROR_MESSAGE
end
end
- 1
private
- 1
def include?(record, value)
- 86
members = if delimiter.respond_to?(:call)
- 4
delimiter.call(record)
- 82
elsif delimiter.respond_to?(:to_sym)
- 4
record.send(delimiter)
else
- 78
delimiter
end
- 86
if value.is_a?(Array)
- 5
value.all? { |v| members.send(inclusion_method(members), v) }
else
- 84
members.send(inclusion_method(members), value)
end
end
- 1
def delimiter
- 316
@delimiter ||= options[:in] || options[:within]
end
# After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
# possible values in the range for equality, which is slower but more accurate.
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
# or DateTime ranges.
- 1
def inclusion_method(enumerable)
- 87
if enumerable.is_a? Range
- 29
case enumerable.first
when Numeric, Time, DateTime, Date
- 21
:cover?
else
- 8
:include?
end
else
- 58
:include?
end
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
class ConfirmationValidator < EachValidator # :nodoc:
- 1
def initialize(options)
- 19
super({ case_sensitive: true }.merge!(options))
- 19
setup!(options[:class])
end
- 1
def validate_each(record, attribute, value)
- 35
unless (confirmed = record.send("#{attribute}_confirmation")).nil?
- 30
unless confirmation_value_equal?(record, attribute, value, confirmed)
- 19
human_attribute_name = record.class.human_attribute_name(attribute)
- 19
record.errors.add(:"#{attribute}_confirmation", :confirmation, **options.except(:case_sensitive).merge!(attribute: human_attribute_name))
end
end
end
- 1
private
- 1
def setup!(klass)
- 19
klass.attr_reader(*attributes.map do |attribute|
- 19
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
end.compact)
- 19
klass.attr_writer(*attributes.map do |attribute|
- 19
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
end.compact)
end
- 1
def confirmation_value_equal?(record, attribute, value, confirmed)
- 30
if !options[:case_sensitive] && value.is_a?(String)
- 1
value.casecmp(confirmed) == 0
else
- 29
value == confirmed
end
end
end
- 1
module HelperMethods
# Encapsulates the pattern of wanting to validate a password or email
# address field with a confirmation.
#
# Model:
# class Person < ActiveRecord::Base
# validates_confirmation_of :user_name, :password
# validates_confirmation_of :email_address,
# message: 'should match confirmation'
# end
#
# View:
# <%= password_field "person", "password" %>
# <%= password_field "person", "password_confirmation" %>
#
# The added +password_confirmation+ attribute is virtual; it exists only
# as an in-memory attribute for validating the password. To achieve this,
# the validation adds accessors to the model for the confirmation
# attribute.
#
# NOTE: This check is performed only if +password_confirmation+ is not
# +nil+. To require confirmation, make sure to add a presence check for
# the confirmation attribute:
#
# validates_presence_of :password_confirmation, if: :password_changed?
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
# <tt>%{translated_attribute_name}</tt>").
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
# non-text columns (+true+ by default).
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_confirmation_of(*attr_names)
- 18
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
require "active_model/validations/clusivity"
- 1
module ActiveModel
- 1
module Validations
- 1
class ExclusionValidator < EachValidator # :nodoc:
- 1
include Clusivity
- 1
def validate_each(record, attribute, value)
- 29
if include?(record, value)
- 22
record.errors.add(attribute, :exclusion, **options.except(:in, :within).merge!(value: value))
end
end
end
- 1
module HelperMethods
# Validates that the value of the specified attribute is not in a
# particular enumerable object.
#
# class Person < ActiveRecord::Base
# validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
# validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
# validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
# validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
# message: 'should not be the same as your username or first name'
# validates_exclusion_of :karma, in: :reserved_karmas
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't
# be part of. This can be supplied as a proc, lambda or symbol which returns an
# enumerable. If the enumerable is a numerical, time or datetime range the test
# is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
# using a proc or lambda the instance under validation is passed as an argument.
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_exclusion_of(*attr_names)
- 21
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
class FormatValidator < EachValidator # :nodoc:
- 1
def validate_each(record, attribute, value)
- 37
if options[:with]
- 33
regexp = option_call(record, :with)
- 33
record_error(record, attribute, :with, value) unless regexp.match?(value.to_s)
- 4
elsif options[:without]
- 4
regexp = option_call(record, :without)
- 4
record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
end
end
- 1
def check_validity!
- 25
unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or"
- 3
raise ArgumentError, "Either :with or :without must be supplied (but not both)"
end
- 22
check_options_validity :with
- 20
check_options_validity :without
end
- 1
private
- 1
def option_call(record, name)
- 37
option = options[name]
- 37
option.respond_to?(:call) ? option.call(record) : option
end
- 1
def record_error(record, attribute, name, value)
- 21
record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
end
- 1
def check_options_validity(name)
- 42
if option = options[name]
- 22
if option.is_a?(Regexp)
- 18
if options[:multiline] != true && regexp_using_multiline_anchors?(option)
- 1
raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
"which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
":multiline => true option?"
end
- 4
elsif !option.respond_to?(:call)
- 2
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
end
end
end
- 1
def regexp_using_multiline_anchors?(regexp)
- 17
source = regexp.source
- 17
source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$"))
end
end
- 1
module HelperMethods
# Validates whether the value of the specified attribute is of the correct
# form, going by the regular expression provided. You can require that the
# attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
# end
#
# Alternatively, you can require that the specified attribute does _not_
# match the regular expression:
#
# class Person < ActiveRecord::Base
# validates_format_of :email, without: /NOSPAM/
# end
#
# You can also provide a proc or lambda which will determine the regular
# expression that will be used to validate the attribute.
#
# class Person < ActiveRecord::Base
# # Admin can have number as a first letter in their screen name
# validates_format_of :screen_name,
# with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
# Note: use <tt>\A</tt> and <tt>\z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
# Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
# the <tt>multiline: true</tt> option in case you use any of these two
# anchors in the provided regular expression. In most cases, you should be
# using <tt>\A</tt> and <tt>\z</tt>.
#
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
# In addition, both must be a regular expression or a proc or lambda, or
# else an exception will be raised.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:with</tt> - Regular expression that if the attribute matches will
# result in a successful validation. This can be provided as a proc or
# lambda returning regular expression which will be called at runtime.
# * <tt>:without</tt> - Regular expression that if the attribute does not
# match will result in a successful validation. This can be provided as
# a proc or lambda returning regular expression which will be called at
# runtime.
# * <tt>:multiline</tt> - Set to true if your regular expression contains
# anchors that match the beginning or end of lines as opposed to the
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_format_of(*attr_names)
- 23
validates_with FormatValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
module HelperMethods # :nodoc:
- 1
private
- 1
def _merge_attributes(attr_names)
- 323
options = attr_names.extract_options!.symbolize_keys
- 323
attr_names.flatten!
- 323
options[:attributes] = attr_names
- 323
options
end
end
end
end
# frozen_string_literal: true
- 1
require "active_model/validations/clusivity"
- 1
module ActiveModel
- 1
module Validations
- 1
class InclusionValidator < EachValidator # :nodoc:
- 1
include Clusivity
- 1
def validate_each(record, attribute, value)
- 57
unless include?(record, value)
- 35
record.errors.add(attribute, :inclusion, **options.except(:in, :within).merge!(value: value))
end
end
end
- 1
module HelperMethods
# Validates whether the value of the specified attribute is available in a
# particular enumerable object.
#
# class Person < ActiveRecord::Base
# validates_inclusion_of :role, in: %w( admin contributor )
# validates_inclusion_of :age, in: 0..99
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
# validates_inclusion_of :karma, in: :available_karmas
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
# supplied as a proc, lambda or symbol which returns an enumerable. If the
# enumerable is a numerical, time or datetime range the test is performed
# with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When using
# a proc or lambda the instance under validation is passed as an argument.
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# not included in the list").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_inclusion_of(*attr_names)
- 30
validates_with InclusionValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
class LengthValidator < EachValidator # :nodoc:
- 1
MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
- 1
CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
- 1
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :too_short, :too_long]
- 1
def initialize(options)
- 100
if range = (options.delete(:in) || options.delete(:within))
- 29
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
- 27
options[:minimum], options[:maximum] = range.min, range.max
end
- 98
if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
- 1
options[:minimum] = 1
end
- 98
super
end
- 1
def check_validity!
- 98
keys = CHECKS.keys & options.keys
- 98
if keys.empty?
raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option."
end
- 98
keys.each do |key|
- 128
value = options[key]
- 128
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc)
- 4
raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
end
end
end
- 1
def validate_each(record, attribute, value)
- 143
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
- 143
errors_options = options.except(*RESERVED_OPTIONS)
- 143
CHECKS.each do |key, validity_check|
- 429
next unless check_value = options[key]
- 191
if !value.nil? || skip_nil_check?(key)
- 160
case check_value
when Proc
- 6
check_value = check_value.call(record)
when Symbol
check_value = record.send(check_value)
end
- 160
next if value_length.send(validity_check, check_value)
end
- 84
errors_options[:count] = check_value
- 84
default_message = options[MESSAGES[key]]
- 84
errors_options[:message] ||= default_message if default_message
- 84
record.errors.add(attribute, MESSAGES[key], **errors_options)
end
end
- 1
private
- 1
def skip_nil_check?(key)
- 54
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
end
end
- 1
module HelperMethods
# Validates that the specified attributes match the length restrictions
# supplied. Only one constraint option can be used at a time apart from
# +:minimum+ and +:maximum+ that can be combined together:
#
# class Person < ActiveRecord::Base
# validates_length_of :first_name, maximum: 30
# validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
# validates_length_of :fax, in: 7..32, allow_nil: true
# validates_length_of :phone, in: 7..32, allow_blank: true
# validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
# validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
# validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
# validates_length_of :words_in_essay, minimum: 100, too_short: 'Your essay must be at least 100 words.'
#
# private
#
# def words_in_essay
# essay.scan(/\w+/)
# end
# end
#
# Constraint options:
#
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
# default if not used with +:minimum+.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
# the attribute.
# * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
#
# Other options:
#
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the
# maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the
# minimum (default is: "is too short (minimum is %{count} characters)").
# * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
# method and the attribute is the wrong size (default is: "is the wrong
# length (should be %{count} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_length_of(*attr_names)
- 95
validates_with LengthValidator, _merge_attributes(attr_names)
end
- 1
alias_method :validates_size_of, :validates_length_of
end
end
end
# frozen_string_literal: true
- 1
require "bigdecimal/util"
- 1
module ActiveModel
- 1
module Validations
- 1
class NumericalityValidator < EachValidator # :nodoc:
- 1
CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
odd: :odd?, even: :even?, other_than: :!= }.freeze
- 1
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
- 1
INTEGER_REGEX = /\A[+-]?\d+\z/
- 1
HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
- 1
def check_validity!
- 76
keys = CHECKS.keys - [:odd, :even]
- 76
options.slice(*keys).each do |option, value|
- 38
unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
- 5
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
end
end
end
- 1
def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil)
- 482
unless is_number?(value, precision, scale)
- 132
record.errors.add(attr_name, :not_a_number, **filtered_options(value))
- 132
return
end
- 350
if allow_only_integer?(record) && !is_integer?(value)
- 46
record.errors.add(attr_name, :not_an_integer, **filtered_options(value))
- 46
return
end
- 304
value = parse_as_number(value, precision, scale)
- 304
options.slice(*CHECKS.keys).each do |option, option_value|
- 136
case option
when :odd, :even
- 20
unless value.to_i.send(CHECKS[option])
- 14
record.errors.add(attr_name, option, **filtered_options(value))
end
else
- 116
case option_value
when Proc
- 4
option_value = option_value.call(record)
when Symbol
- 3
option_value = record.send(option_value)
end
- 116
option_value = parse_as_number(option_value, precision, scale)
- 116
unless value.send(CHECKS[option], option_value)
- 61
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
end
end
end
end
- 1
private
- 1
def parse_as_number(raw_value, precision, scale)
- 902
if raw_value.is_a?(Float)
- 102
parse_float(raw_value, precision, scale)
- 800
elsif raw_value.is_a?(Numeric)
- 328
raw_value
- 472
elsif is_integer?(raw_value)
- 162
raw_value.to_i
- 310
elsif !is_hexadecimal_literal?(raw_value)
- 275
parse_float(Kernel.Float(raw_value), precision, scale)
end
end
- 1
def parse_float(raw_value, precision, scale)
- 280
(scale ? raw_value.truncate(scale) : raw_value).to_d(precision)
end
- 1
def is_number?(raw_value, precision, scale)
- 482
!parse_as_number(raw_value, precision, scale).nil?
rescue ArgumentError, TypeError
- 97
false
end
- 1
def is_integer?(raw_value)
- 556
INTEGER_REGEX.match?(raw_value.to_s)
end
- 1
def is_hexadecimal_literal?(raw_value)
- 310
HEXADECIMAL_REGEX.match?(raw_value.to_s)
end
- 1
def filtered_options(value)
- 253
filtered = options.except(*RESERVED_OPTIONS)
- 253
filtered[:value] = value
- 253
filtered
end
- 1
def allow_only_integer?(record)
- 350
case options[:only_integer]
when Symbol
- 30
record.send(options[:only_integer])
when Proc
- 30
options[:only_integer].call(record)
else
- 290
options[:only_integer]
end
end
- 1
def read_attribute_for_validation(record, attr_name)
- 488
return super if record_attribute_changed_in_place?(record, attr_name)
- 488
came_from_user = :"#{attr_name}_came_from_user?"
- 488
if record.respond_to?(came_from_user)
if record.public_send(came_from_user)
raw_value = record.read_attribute_before_type_cast(attr_name)
elsif record.respond_to?(:read_attribute)
raw_value = record.read_attribute(attr_name)
end
else
- 488
before_type_cast = :"#{attr_name}_before_type_cast"
- 488
if record.respond_to?(before_type_cast)
- 2
raw_value = record.public_send(before_type_cast)
end
end
- 488
raw_value || super
end
- 1
def record_attribute_changed_in_place?(record, attr_name)
- 488
record.respond_to?(:attribute_changed_in_place?) &&
record.attribute_changed_in_place?(attr_name.to_s)
end
end
- 1
module HelperMethods
# Validates whether the value of the specified attribute is numeric by
# trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
# is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
# (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
# are guaranteed up to 15 digits.
#
# class Person < ActiveRecord::Base
# validates_numericality_of :value, on: :create
# end
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
# integer, e.g. an integral value (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
# +false+). Notice that for Integer and Float columns empty strings are
# converted to +nil+.
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
# supplied value.
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
# greater than or equal the supplied value.
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
# value.
# * <tt>:less_than</tt> - Specifies the value must be less than the
# supplied value.
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
# than or equal the supplied value.
# * <tt>:other_than</tt> - Specifies the value must be other than the
# supplied value.
# * <tt>:odd</tt> - Specifies the value must be an odd number.
# * <tt>:even</tt> - Specifies the value must be an even number.
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
# See <tt>ActiveModel::Validations#validates</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which
# corresponds to a method:
#
# * <tt>:greater_than</tt>
# * <tt>:greater_than_or_equal_to</tt>
# * <tt>:equal_to</tt>
# * <tt>:less_than</tt>
# * <tt>:less_than_or_equal_to</tt>
# * <tt>:only_integer</tt>
#
# For example:
#
# class Person < ActiveRecord::Base
# validates_numericality_of :width, less_than: ->(person) { person.height }
# validates_numericality_of :width, greater_than: :minimum_weight
# end
- 1
def validates_numericality_of(*attr_names)
- 73
validates_with NumericalityValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
module ActiveModel
- 1
module Validations
- 1
class PresenceValidator < EachValidator # :nodoc:
- 1
def validate_each(record, attr_name, value)
- 65
record.errors.add(attr_name, :blank, **options) if value.blank?
end
end
- 1
module HelperMethods
# Validates that the specified attributes are not blank (as defined by
# Object#blank?). Happens by default on save.
#
# class Person < ActiveRecord::Base
# validates_presence_of :first_name
# end
#
# The first_name attribute must be in the object and it cannot be blank.
#
# If you want to validate the presence of a boolean field (where the real
# values are +true+ and +false+), you will want to use
# <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
#
# This is due to the way Object#blank? handles boolean values:
# <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validations#validates</tt> for more information
- 1
def validates_presence_of(*attr_names)
- 34
validates_with PresenceValidator, _merge_attributes(attr_names)
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/hash/slice"
- 1
module ActiveModel
- 1
module Validations
- 1
module ClassMethods
# This method is a shortcut to all default validators and any custom
# validator classes ending in 'Validator'. Note that Rails default
# validators can be overridden inside specific classes by creating
# custom validator classes in their place such as PresenceValidator.
#
# Examples of using the default rails validators:
#
# validates :username, absence: true
# validates :terms, acceptance: true
# validates :password, confirmation: true
# validates :username, exclusion: { in: %w(admin superuser) }
# validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
# validates :age, inclusion: { in: 0..9 }
# validates :first_name, length: { maximum: 30 }
# validates :age, numericality: true
# validates :username, presence: true
#
# The power of the +validates+ method comes when using custom validators
# and default validators in one call for a given attribute.
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
# record.errors.add attribute, (options[:message] || "is not an email") unless
# /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.match?(value)
# end
# end
#
# class Person
# include ActiveModel::Validations
# attr_accessor :name, :email
#
# validates :name, presence: true, length: { maximum: 100 }
# validates :email, presence: true, email: true
# end
#
# Validator classes may also exist within the class being validated
# allowing custom modules of validators to be included as needed.
#
# class Film
# include ActiveModel::Validations
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
# record.errors.add attribute, "must start with 'the'" unless /\Athe/i.match?(value)
# end
# end
#
# validates :name, title: true
# end
#
# Additionally validator classes may be in another namespace and still
# used within any class.
#
# validates :name, :'film/title' => true
#
# The validators hash can also handle regular expressions, ranges, arrays
# and strings in shortcut form.
#
# validates :email, format: /@/
# validates :role, inclusion: %w(admin contributor)
# validates :password, length: 6..20
#
# When using shortcut form, ranges and arrays are passed to your
# validator's initializer as <tt>options[:in]</tt> while other types
# including regular expressions and strings are passed as <tt>options[:with]</tt>.
#
# There is also a list of options that could be used along with validators:
#
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
# Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
# proc or string should return or evaluate to a +true+ or +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
# if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
# or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a +true+ or
# +false+ value.
# * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
# * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
# Example:
#
# validates :password, presence: true, confirmation: true, if: :password_required?
# validates :token, length: 24, strict: TokenLengthException
#
#
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
# and +:message+ can be given to one specific validator, as a hash:
#
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
- 1
def validates(*attributes)
- 37
defaults = attributes.extract_options!.dup
- 37
validations = defaults.slice!(*_validates_default_keys)
- 37
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
- 37
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
- 37
defaults[:attributes] = attributes
- 37
validations.each do |key, options|
- 43
key = "#{key.to_s.camelize}Validator"
- 43
begin
- 43
validator = key.include?("::") ? key.constantize : const_get(key)
rescue NameError
- 2
raise ArgumentError, "Unknown validator: '#{key}'"
end
- 41
next unless options
- 40
validates_with(validator, defaults.merge(_parse_validates_options(options)))
end
end
# This method is used to define validations that cannot be corrected by end
# users and are considered exceptional. So each validator defined with bang
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
# when validation fails. See <tt>validates</tt> for more information about
# the validation itself.
#
# class Person
# include ActiveModel::Validations
#
# attr_accessor :name
# validates! :name, presence: true
# end
#
# person = Person.new
# person.name = ''
# person.valid?
# # => ActiveModel::StrictValidationFailed: Name can't be blank
- 1
def validates!(*attributes)
- 1
options = attributes.extract_options!
- 1
options[:strict] = true
- 1
validates(*(attributes << options))
end
- 1
private
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
- 1
def _validates_default_keys
- 37
[:if, :unless, :on, :allow_blank, :allow_nil, :strict]
end
- 1
def _parse_validates_options(options)
- 40
case options
when TrueClass
- 22
{}
when Hash
- 11
options
when Range, Array
- 2
{ in: options }
else
- 5
{ with: options }
end
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/array/extract_options"
- 1
module ActiveModel
- 1
module Validations
- 1
class WithValidator < EachValidator # :nodoc:
- 1
def validate_each(record, attr, val)
- 4
method_name = options[:with]
- 4
if record.method(method_name).arity == 0
- 2
record.send method_name
else
- 2
record.send method_name, attr
end
end
end
- 1
module ClassMethods
# Passes the record off to the class or classes specified and allows them
# to add errors based on more complex conditions.
#
# class Person
# include ActiveModel::Validations
# validates_with MyValidator
# end
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
# record.errors.add :base, 'This record is invalid'
# end
# end
#
# private
# def some_complex_logic
# # ...
# end
# end
#
# You may also pass it multiple classes, like so:
#
# class Person
# include ActiveModel::Validations
# validates_with MyValidator, MyOtherValidator, on: :create
# end
#
# Configuration options:
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
# Runs in all validation contexts by default +nil+. You can pass a symbol
# or an array of symbols. (e.g. <tt>on: :create</tt> or
# <tt>on: :custom_validation_context</tt> or
# <tt>on: [:create, :custom_validation_context]</tt>)
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
# if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
# or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
# The method, proc or string should return or evaluate to a +true+ or
# +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
# determine if the validation should not occur
# (e.g. <tt>unless: :skip_validation</tt>, or
# <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a +true+ or
# +false+ value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validations#validates!</tt> for more information.
#
# If you pass any additional configuration options, they will be passed
# to the class and available as +options+:
#
# class Person
# include ActiveModel::Validations
# validates_with MyValidator, my_custom_key: 'my custom value'
# end
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
# options[:my_custom_key] # => "my custom value"
# end
# end
- 1
def validates_with(*args, &block)
- 377
options = args.extract_options!
- 377
options[:class] = self
- 377
args.each do |klass|
- 378
validator = klass.new(options, &block)
- 357
if validator.respond_to?(:attributes) && !validator.attributes.empty?
- 345
validator.attributes.each do |attribute|
- 369
_validators[attribute.to_sym] << validator
end
else
- 12
_validators[nil] << validator
end
- 357
validate(validator, options)
end
end
end
# Passes the record off to the class or classes specified and allows them
# to add errors based on more complex conditions.
#
# class Person
# include ActiveModel::Validations
#
# validate :instance_validations
#
# def instance_validations
# validates_with MyValidator
# end
# end
#
# Please consult the class method documentation for more information on
# creating your own validator.
#
# You may also pass it multiple classes, like so:
#
# class Person
# include ActiveModel::Validations
#
# validate :instance_validations, on: :create
#
# def instance_validations
# validates_with MyValidator, MyOtherValidator
# end
# end
#
# Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
# <tt>:unless</tt>), which are available on the class version of
# +validates_with+, should instead be placed on the +validates+ method
# as these are applied and tested in the callback.
#
# If you pass any additional configuration options, they will be passed
# to the class and available as +options+, please refer to the
# class version of this method for more information.
- 1
def validates_with(*args, &block)
- 2
options = args.extract_options!
- 2
options[:class] = self.class
- 2
args.each do |klass|
- 2
validator = klass.new(options, &block)
- 2
validator.validate(self)
end
end
end
end
# frozen_string_literal: true
- 1
require "active_support/core_ext/module/anonymous"
- 1
module ActiveModel
# == Active \Model \Validator
#
# A simple base class that can be used along with
# ActiveModel::Validations::ClassMethods.validates_with
#
# class Person
# include ActiveModel::Validations
# validates_with MyValidator
# end
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
# record.errors.add(:base, "This record is invalid")
# end
# end
#
# private
# def some_complex_logic
# # ...
# end
# end
#
# Any class that inherits from ActiveModel::Validator must implement a method
# called +validate+ which accepts a +record+.
#
# class Person
# include ActiveModel::Validations
# validates_with MyValidator
# end
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
# record # => The person instance being validated
# options # => Any non-standard options passed to validates_with
# end
# end
#
# To cause a validation error, you must add to the +record+'s errors directly
# from within the validators message.
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
# record.errors.add :base, "This is some custom error message"
# record.errors.add :first_name, "This is some complex validation"
# # etc...
# end
# end
#
# To add behavior to the initialize method, use the following signature:
#
# class MyValidator < ActiveModel::Validator
# def initialize(options)
# super
# @my_custom_field = options[:field_name] || :first_name
# end
# end
#
# Note that the validator is initialized only once for the whole application
# life cycle, and not on each validation run.
#
# The easiest way to add custom validators for validating individual attributes
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
# record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
# end
# end
#
# This can now be used in combination with the +validates+ method
# (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
#
# class Person
# include ActiveModel::Validations
# attr_accessor :title
#
# validates :title, presence: true, title: true
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
# as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
# To set up your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
# def initialize(options={})
# super
# options[:class].attr_accessor :custom_attribute
# end
# end
- 1
class Validator
- 1
attr_reader :options
# Returns the kind of the validator.
#
# PresenceValidator.kind # => :presence
# AcceptanceValidator.kind # => :acceptance
- 1
def self.kind
- 5
@kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous?
end
# Accepts options that will be made available through the +options+ reader.
- 1
def initialize(options = {})
- 376
@options = options.except(:class).freeze
end
# Returns the kind for this validator.
#
# PresenceValidator.new(attributes: [:username]).kind # => :presence
# AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance
- 1
def kind
- 5
self.class.kind
end
# Override this method in subclasses with validation logic, adding errors
# to the records +errors+ array where necessary.
- 1
def validate(record)
raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
end
# +EachValidator+ is a validator which iterates through the attributes given
# in the options hash invoking the <tt>validate_each</tt> method passing in the
# record, attribute and value.
#
# All \Active \Model validations are built on top of this validator.
- 1
class EachValidator < Validator #:nodoc:
- 1
attr_reader :attributes
# Returns a new validator instance. All options will be available via the
# +options+ reader, however the <tt>:attributes</tt> option will be removed
# and instead be made available through the +attributes+ reader.
- 1
def initialize(options)
- 366
@attributes = Array(options.delete(:attributes))
- 366
raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
- 365
super
- 365
check_validity!
end
# Performs validation on the supplied record. By default this will call
# +validate_each+ to determine validity therefore subclasses should
# override +validate_each+ with validation logic.
- 1
def validate(record)
- 900
attributes.each do |attribute|
- 939
value = read_attribute_for_validation(record, attribute)
- 939
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
- 902
validate_each(record, attribute, value)
end
end
# Override this method in subclasses with the validation logic, adding
# errors to the records +errors+ array where necessary.
- 1
def validate_each(record, attribute, value)
raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
end
# Hook method that gets called by the initializer allowing verification
# that the arguments supplied are valid. You could for example raise an
# +ArgumentError+ when invalid options are supplied.
- 1
def check_validity!
end
- 1
private
- 1
def read_attribute_for_validation(record, attr_name)
- 937
record.read_attribute_for_validation(attr_name)
end
end
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
# and call this block for each attribute being validated. +validates_each+ uses this validator.
- 1
class BlockValidator < EachValidator #:nodoc:
- 1
def initialize(options, &block)
- 2
@block = block
- 2
super
end
- 1
private
- 1
def validate_each(record, attribute, value)
- 8
@block.call(record, attribute, value)
end
end
end
# frozen_string_literal: true
- 1
require_relative "gem_version"
- 1
module ActiveModel
# Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
- 1
def self.version
gem_version
end
end