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