omg-activemodel 8.0.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +112 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- metadata +139 -0
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# = Active Model \Value \Type
|
6
|
+
#
|
7
|
+
# The base class for all attribute types. This class also serves as the
|
8
|
+
# default type for attributes that do not specify a type.
|
9
|
+
class Value
|
10
|
+
include SerializeCastValue
|
11
|
+
attr_reader :precision, :scale, :limit
|
12
|
+
|
13
|
+
# Initializes a type with three basic configuration settings: precision,
|
14
|
+
# limit, and scale. The Value base class does not define behavior for
|
15
|
+
# these settings. It uses them for equality comparison and hash key
|
16
|
+
# generation only.
|
17
|
+
def initialize(precision: nil, limit: nil, scale: nil)
|
18
|
+
super()
|
19
|
+
@precision = precision
|
20
|
+
@scale = scale
|
21
|
+
@limit = limit
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if this type can convert +value+ to a type that is usable
|
25
|
+
# by the database. For example a boolean type can return +true+ if the
|
26
|
+
# value parameter is a Ruby boolean, but may return +false+ if the value
|
27
|
+
# parameter is some other object.
|
28
|
+
def serializable?(value)
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the unique type name as a Symbol. Subclasses should override
|
33
|
+
# this method.
|
34
|
+
def type
|
35
|
+
end
|
36
|
+
|
37
|
+
# Converts a value from database input to the appropriate ruby type. The
|
38
|
+
# return value of this method will be returned from
|
39
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
40
|
+
# implementation just calls Value#cast.
|
41
|
+
#
|
42
|
+
# +value+ The raw input, as provided from the database.
|
43
|
+
def deserialize(value)
|
44
|
+
cast(value)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Type casts a value from user input (e.g. from a setter). This value may
|
48
|
+
# be a string from the form builder, or a ruby object passed to a setter.
|
49
|
+
# There is currently no way to differentiate between which source it came
|
50
|
+
# from.
|
51
|
+
#
|
52
|
+
# The return value of this method will be returned from
|
53
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
|
54
|
+
# Value#cast_value.
|
55
|
+
#
|
56
|
+
# +value+ The raw input, as provided to the attribute setter.
|
57
|
+
def cast(value)
|
58
|
+
cast_value(value) unless value.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
# Casts a value from the ruby type to a type that the database knows how
|
62
|
+
# to understand. The returned value from this method should be a
|
63
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
64
|
+
# +nil+.
|
65
|
+
def serialize(value)
|
66
|
+
value
|
67
|
+
end
|
68
|
+
|
69
|
+
# Type casts a value for schema dumping. This method is private, as we are
|
70
|
+
# hoping to remove it entirely.
|
71
|
+
def type_cast_for_schema(value) # :nodoc:
|
72
|
+
value.inspect
|
73
|
+
end
|
74
|
+
|
75
|
+
# These predicates are not documented, as I need to look further into
|
76
|
+
# their use, and see if they can be removed entirely.
|
77
|
+
def binary? # :nodoc:
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
81
|
+
# Determines whether a value has changed for dirty checking. +old_value+
|
82
|
+
# and +new_value+ will always be type-cast. Types should not need to
|
83
|
+
# override this method.
|
84
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
85
|
+
old_value != new_value
|
86
|
+
end
|
87
|
+
|
88
|
+
# Determines whether the mutable value has been modified since it was
|
89
|
+
# read. Returns +false+ by default. If your type returns an object
|
90
|
+
# which could be mutated, you should override this method. You will need
|
91
|
+
# to either:
|
92
|
+
#
|
93
|
+
# - pass +new_value+ to Value#serialize and compare it to
|
94
|
+
# +raw_old_value+
|
95
|
+
#
|
96
|
+
# or
|
97
|
+
#
|
98
|
+
# - pass +raw_old_value+ to Value#deserialize and compare it to
|
99
|
+
# +new_value+
|
100
|
+
#
|
101
|
+
# +raw_old_value+ The original value, before being passed to
|
102
|
+
# +deserialize+.
|
103
|
+
#
|
104
|
+
# +new_value+ The current value, after type casting.
|
105
|
+
def changed_in_place?(raw_old_value, new_value)
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
109
|
+
def value_constructed_by_mass_assignment?(_value) # :nodoc:
|
110
|
+
false
|
111
|
+
end
|
112
|
+
|
113
|
+
def force_equality?(_value) # :nodoc:
|
114
|
+
false
|
115
|
+
end
|
116
|
+
|
117
|
+
def map(value) # :nodoc:
|
118
|
+
yield value
|
119
|
+
end
|
120
|
+
|
121
|
+
def ==(other)
|
122
|
+
self.class == other.class &&
|
123
|
+
precision == other.precision &&
|
124
|
+
scale == other.scale &&
|
125
|
+
limit == other.limit
|
126
|
+
end
|
127
|
+
alias eql? ==
|
128
|
+
|
129
|
+
def hash
|
130
|
+
[self.class, precision, scale, limit].hash
|
131
|
+
end
|
132
|
+
|
133
|
+
def assert_valid_value(_)
|
134
|
+
end
|
135
|
+
|
136
|
+
def serialized? # :nodoc:
|
137
|
+
false
|
138
|
+
end
|
139
|
+
|
140
|
+
def mutable? # :nodoc:
|
141
|
+
false
|
142
|
+
end
|
143
|
+
|
144
|
+
def as_json(*)
|
145
|
+
raise NoMethodError
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
# Convenience method for types which do not need separate type casting
|
150
|
+
# behavior for user and database inputs. Called by Value#cast for
|
151
|
+
# values except +nil+.
|
152
|
+
def cast_value(value) # :doc:
|
153
|
+
value
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/helpers"
|
4
|
+
require "active_model/type/serialize_cast_value"
|
5
|
+
require "active_model/type/value"
|
6
|
+
|
7
|
+
require "active_model/type/big_integer"
|
8
|
+
require "active_model/type/binary"
|
9
|
+
require "active_model/type/boolean"
|
10
|
+
require "active_model/type/date"
|
11
|
+
require "active_model/type/date_time"
|
12
|
+
require "active_model/type/decimal"
|
13
|
+
require "active_model/type/float"
|
14
|
+
require "active_model/type/immutable_string"
|
15
|
+
require "active_model/type/integer"
|
16
|
+
require "active_model/type/string"
|
17
|
+
require "active_model/type/time"
|
18
|
+
|
19
|
+
require "active_model/type/registry"
|
20
|
+
|
21
|
+
module ActiveModel
|
22
|
+
module Type
|
23
|
+
@registry = Registry.new
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :registry # :nodoc:
|
27
|
+
|
28
|
+
# Add a new type to the registry, allowing it to be referenced as a
|
29
|
+
# symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
|
30
|
+
def register(type_name, klass = nil, &block)
|
31
|
+
registry.register(type_name, klass, &block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def lookup(...) # :nodoc:
|
35
|
+
registry.lookup(...)
|
36
|
+
end
|
37
|
+
|
38
|
+
def default_value # :nodoc:
|
39
|
+
@default_value ||= Value.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
register(:big_integer, Type::BigInteger)
|
44
|
+
register(:binary, Type::Binary)
|
45
|
+
register(:boolean, Type::Boolean)
|
46
|
+
register(:date, Type::Date)
|
47
|
+
register(:datetime, Type::DateTime)
|
48
|
+
register(:decimal, Type::Decimal)
|
49
|
+
register(:float, Type::Float)
|
50
|
+
register(:immutable_string, Type::ImmutableString)
|
51
|
+
register(:integer, Type::Integer)
|
52
|
+
register(:string, Type::String)
|
53
|
+
register(:time, Type::Time)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# == \Active \Model Absence Validator
|
6
|
+
class AbsenceValidator < EachValidator # :nodoc:
|
7
|
+
def validate_each(record, attr_name, value)
|
8
|
+
record.errors.add(attr_name, :present, **options) if value.present?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module HelperMethods
|
13
|
+
# Validates that the specified attributes are blank (as defined by
|
14
|
+
# Object#present?).
|
15
|
+
#
|
16
|
+
# class Person < ActiveRecord::Base
|
17
|
+
# validates_absence_of :first_name
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The first_name attribute must be in the object and it must be blank.
|
21
|
+
#
|
22
|
+
# Configuration options:
|
23
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
|
24
|
+
#
|
25
|
+
# There is also a list of default options supported by every validator:
|
26
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
27
|
+
# See ActiveModel::Validations::ClassMethods#validates for more information.
|
28
|
+
def validates_absence_of(*attr_names)
|
29
|
+
validates_with AbsenceValidator, _merge_attributes(attr_names)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
class AcceptanceValidator < EachValidator # :nodoc:
|
6
|
+
def initialize(options)
|
7
|
+
super({ allow_nil: true, accept: ["1", true] }.merge!(options))
|
8
|
+
setup!(options[:class])
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
unless acceptable_option?(value)
|
13
|
+
record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def setup!(klass)
|
19
|
+
define_attributes = LazilyDefineAttributes.new(attributes)
|
20
|
+
klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
|
21
|
+
end
|
22
|
+
|
23
|
+
def acceptable_option?(value)
|
24
|
+
Array(options[:accept]).include?(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
class LazilyDefineAttributes < Module
|
28
|
+
def initialize(attributes)
|
29
|
+
@attributes = attributes.map(&:to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
def included(klass)
|
33
|
+
@lock = Mutex.new
|
34
|
+
mod = self
|
35
|
+
|
36
|
+
define_method(:respond_to_missing?) do |method_name, include_private = false|
|
37
|
+
mod.define_on(klass)
|
38
|
+
super(method_name, include_private) || mod.matches?(method_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method(:method_missing) do |method_name, *args, &block|
|
42
|
+
mod.define_on(klass)
|
43
|
+
if mod.matches?(method_name)
|
44
|
+
send(method_name, *args, &block)
|
45
|
+
else
|
46
|
+
super(method_name, *args, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def matches?(method_name)
|
52
|
+
attr_name = method_name.to_s.chomp("=")
|
53
|
+
attributes.any? { |name| name == attr_name }
|
54
|
+
end
|
55
|
+
|
56
|
+
def define_on(klass)
|
57
|
+
@lock&.synchronize do
|
58
|
+
return unless @lock
|
59
|
+
|
60
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
61
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
62
|
+
|
63
|
+
attr_reader(*attr_readers)
|
64
|
+
attr_writer(*attr_writers)
|
65
|
+
|
66
|
+
remove_method :respond_to_missing?
|
67
|
+
remove_method :method_missing
|
68
|
+
|
69
|
+
@lock = nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def ==(other)
|
74
|
+
self.class == other.class && attributes == other.attributes
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
attr_reader :attributes
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module HelperMethods
|
83
|
+
# Encapsulates the pattern of wanting to validate the acceptance of a
|
84
|
+
# terms of service check box (or similar agreement).
|
85
|
+
#
|
86
|
+
# class Person < ActiveRecord::Base
|
87
|
+
# validates_acceptance_of :terms_of_service
|
88
|
+
# validates_acceptance_of :eula, message: 'must be abided'
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
# If the database column does not exist, the +terms_of_service+ attribute
|
92
|
+
# is entirely virtual. This check is performed only if +terms_of_service+
|
93
|
+
# is not +nil+.
|
94
|
+
#
|
95
|
+
# Configuration options:
|
96
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be
|
97
|
+
# accepted").
|
98
|
+
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
|
99
|
+
# Also accepts an array of possible values. The default value is
|
100
|
+
# an array ["1", true], which makes it easy to relate to an HTML
|
101
|
+
# checkbox. This should be set to, or include, +true+ if you are validating
|
102
|
+
# a database column, since the attribute is typecast from "1" to +true+
|
103
|
+
# before validation.
|
104
|
+
#
|
105
|
+
# There is also a list of default options supported by every validator:
|
106
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
107
|
+
# See ActiveModel::Validations::ClassMethods#validates for more information.
|
108
|
+
def validates_acceptance_of(*attr_names)
|
109
|
+
validates_with AcceptanceValidator, _merge_attributes(attr_names)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
# = Active \Model \Validation \Callbacks
|
6
|
+
#
|
7
|
+
# Provides an interface for any class to have ClassMethods#before_validation and
|
8
|
+
# ClassMethods#after_validation callbacks.
|
9
|
+
#
|
10
|
+
# First, include +ActiveModel::Validations::Callbacks+ from the class you are
|
11
|
+
# creating:
|
12
|
+
#
|
13
|
+
# class MyModel
|
14
|
+
# include ActiveModel::Validations::Callbacks
|
15
|
+
#
|
16
|
+
# before_validation :do_stuff_before_validation
|
17
|
+
# after_validation :do_stuff_after_validation
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# Like other <tt>before_*</tt> callbacks if +before_validation+ throws
|
21
|
+
# +:abort+ then <tt>valid?</tt> will not be called.
|
22
|
+
module Callbacks
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
|
25
|
+
included do
|
26
|
+
include ActiveSupport::Callbacks
|
27
|
+
define_callbacks :validation,
|
28
|
+
skip_after_callbacks_if_terminated: true,
|
29
|
+
scope: [:kind, :name]
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
# Defines a callback that will get called right before validation.
|
34
|
+
#
|
35
|
+
# class Person
|
36
|
+
# include ActiveModel::Validations
|
37
|
+
# include ActiveModel::Validations::Callbacks
|
38
|
+
#
|
39
|
+
# attr_accessor :name
|
40
|
+
#
|
41
|
+
# validates_length_of :name, maximum: 6
|
42
|
+
#
|
43
|
+
# before_validation :remove_whitespaces
|
44
|
+
#
|
45
|
+
# private
|
46
|
+
# def remove_whitespaces
|
47
|
+
# name.strip!
|
48
|
+
# end
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# person = Person.new
|
52
|
+
# person.name = ' bob '
|
53
|
+
# person.valid? # => true
|
54
|
+
# person.name # => "bob"
|
55
|
+
def before_validation(*args, &block)
|
56
|
+
options = args.extract_options!
|
57
|
+
|
58
|
+
set_options_for_callback(options)
|
59
|
+
|
60
|
+
set_callback(:validation, :before, *args, options, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Defines a callback that will get called right after validation.
|
64
|
+
#
|
65
|
+
# class Person
|
66
|
+
# include ActiveModel::Validations
|
67
|
+
# include ActiveModel::Validations::Callbacks
|
68
|
+
#
|
69
|
+
# attr_accessor :name, :status
|
70
|
+
#
|
71
|
+
# validates_presence_of :name
|
72
|
+
#
|
73
|
+
# after_validation :set_status
|
74
|
+
#
|
75
|
+
# private
|
76
|
+
# def set_status
|
77
|
+
# self.status = errors.empty?
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# person = Person.new
|
82
|
+
# person.name = ''
|
83
|
+
# person.valid? # => false
|
84
|
+
# person.status # => false
|
85
|
+
# person.name = 'bob'
|
86
|
+
# person.valid? # => true
|
87
|
+
# person.status # => true
|
88
|
+
def after_validation(*args, &block)
|
89
|
+
options = args.extract_options!
|
90
|
+
options = options.dup
|
91
|
+
options[:prepend] = true
|
92
|
+
|
93
|
+
set_options_for_callback(options)
|
94
|
+
|
95
|
+
set_callback(:validation, :after, *args, options, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def set_options_for_callback(options)
|
100
|
+
if options.key?(:on)
|
101
|
+
options[:on] = Array(options[:on])
|
102
|
+
options[:if] = [
|
103
|
+
->(o) {
|
104
|
+
options[:on].intersect?(Array(o.validation_context))
|
105
|
+
},
|
106
|
+
*options[:if]
|
107
|
+
]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
# Override run_validations! to include callbacks.
|
114
|
+
def run_validations!
|
115
|
+
_run_validation_callbacks { super }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/resolve_value"
|
4
|
+
require "active_support/core_ext/range"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Validations
|
8
|
+
module Clusivity # :nodoc:
|
9
|
+
include ResolveValue
|
10
|
+
|
11
|
+
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
|
12
|
+
"and must be supplied as the :in (or :within) option of the configuration hash"
|
13
|
+
|
14
|
+
def check_validity!
|
15
|
+
unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
|
16
|
+
raise ArgumentError, ERROR_MESSAGE
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def include?(record, value)
|
22
|
+
members = resolve_value(record, delimiter)
|
23
|
+
|
24
|
+
if value.is_a?(Array)
|
25
|
+
value.all? { |v| members.public_send(inclusion_method(members), v) }
|
26
|
+
else
|
27
|
+
members.public_send(inclusion_method(members), value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delimiter
|
32
|
+
@delimiter ||= options[:in] || options[:within]
|
33
|
+
end
|
34
|
+
|
35
|
+
# After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
|
36
|
+
# possible values in the range for equality, which is slower but more accurate.
|
37
|
+
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
|
38
|
+
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
|
39
|
+
# or DateTime ranges.
|
40
|
+
def inclusion_method(enumerable)
|
41
|
+
if enumerable.is_a? Range
|
42
|
+
case enumerable.begin || enumerable.end
|
43
|
+
when Numeric, Time, DateTime, Date
|
44
|
+
:cover?
|
45
|
+
else
|
46
|
+
:include?
|
47
|
+
end
|
48
|
+
else
|
49
|
+
:include?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
module Comparability # :nodoc:
|
6
|
+
COMPARE_CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
7
|
+
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
8
|
+
other_than: :!= }.freeze
|
9
|
+
|
10
|
+
def error_options(value, option_value)
|
11
|
+
options.except(*COMPARE_CHECKS.keys).merge!(
|
12
|
+
count: option_value,
|
13
|
+
value: value
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/comparability"
|
4
|
+
require "active_model/validations/resolve_value"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Validations
|
8
|
+
class ComparisonValidator < EachValidator # :nodoc:
|
9
|
+
include Comparability
|
10
|
+
include ResolveValue
|
11
|
+
|
12
|
+
def check_validity!
|
13
|
+
unless options.keys.intersect?(COMPARE_CHECKS.keys)
|
14
|
+
raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
|
15
|
+
":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_each(record, attr_name, value)
|
20
|
+
options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
|
21
|
+
option_value = resolve_value(record, raw_option_value)
|
22
|
+
|
23
|
+
if value.nil? || value.blank?
|
24
|
+
return record.errors.add(attr_name, :blank, **error_options(value, option_value))
|
25
|
+
end
|
26
|
+
|
27
|
+
unless value.public_send(COMPARE_CHECKS[option], option_value)
|
28
|
+
record.errors.add(attr_name, option, **error_options(value, option_value))
|
29
|
+
end
|
30
|
+
rescue ArgumentError => e
|
31
|
+
record.errors.add(attr_name, e.message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module HelperMethods
|
37
|
+
# Validates the value of a specified attribute fulfills all
|
38
|
+
# defined comparisons with another value, proc, or attribute.
|
39
|
+
#
|
40
|
+
# class Person < ActiveRecord::Base
|
41
|
+
# validates_comparison_of :value, greater_than: 'the sum of its parts'
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# Configuration options:
|
45
|
+
# * <tt>:message</tt> - A custom error message (default is: "failed comparison").
|
46
|
+
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
47
|
+
# supplied value. The default error message for this option is _"must be
|
48
|
+
# greater than %{count}"_.
|
49
|
+
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
50
|
+
# greater than or equal to the supplied value. The default error message
|
51
|
+
# for this option is _"must be greater than or equal to %{count}"_.
|
52
|
+
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
53
|
+
# value. The default error message for this option is _"must be equal to
|
54
|
+
# %{count}"_.
|
55
|
+
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
56
|
+
# supplied value. The default error message for this option is _"must be
|
57
|
+
# less than %{count}"_.
|
58
|
+
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
59
|
+
# than or equal to the supplied value. The default error message for
|
60
|
+
# this option is _"must be less than or equal to %{count}"_.
|
61
|
+
# * <tt>:other_than</tt> - Specifies the value must not be equal to the
|
62
|
+
# supplied value. The default error message for this option is _"must be
|
63
|
+
# other than %{count}"_.
|
64
|
+
#
|
65
|
+
# There is also a list of default options supported by every validator:
|
66
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
67
|
+
# See ActiveModel::Validations::ClassMethods#validates for more information.
|
68
|
+
#
|
69
|
+
# The validator requires at least one of the following checks to be supplied.
|
70
|
+
# Each will accept a proc, value, or a symbol which corresponds to a method:
|
71
|
+
#
|
72
|
+
# * <tt>:greater_than</tt>
|
73
|
+
# * <tt>:greater_than_or_equal_to</tt>
|
74
|
+
# * <tt>:equal_to</tt>
|
75
|
+
# * <tt>:less_than</tt>
|
76
|
+
# * <tt>:less_than_or_equal_to</tt>
|
77
|
+
# * <tt>:other_than</tt>
|
78
|
+
#
|
79
|
+
# For example:
|
80
|
+
#
|
81
|
+
# class Person < ActiveRecord::Base
|
82
|
+
# validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
|
83
|
+
# validates_comparison_of :preferred_name, other_than: :given_name, allow_nil: true
|
84
|
+
# end
|
85
|
+
def validates_comparison_of(*attr_names)
|
86
|
+
validates_with ComparisonValidator, _merge_attributes(attr_names)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|