activemodel 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activemodel might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +84 -93
- data/MIT-LICENSE +1 -1
- data/README.rdoc +8 -16
- data/lib/active_model.rb +3 -2
- data/lib/active_model/attribute_assignment.rb +52 -0
- data/lib/active_model/attribute_methods.rb +16 -16
- data/lib/active_model/callbacks.rb +3 -3
- data/lib/active_model/conversion.rb +3 -3
- data/lib/active_model/dirty.rb +34 -35
- data/lib/active_model/errors.rb +117 -63
- data/lib/active_model/forbidden_attributes_protection.rb +3 -2
- data/lib/active_model/gem_version.rb +5 -5
- data/lib/active_model/lint.rb +32 -28
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +3 -4
- data/lib/active_model/naming.rb +5 -4
- data/lib/active_model/secure_password.rb +2 -9
- data/lib/active_model/serialization.rb +36 -9
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/type.rb +59 -0
- data/lib/active_model/type/big_integer.rb +13 -0
- data/lib/active_model/type/binary.rb +50 -0
- data/lib/active_model/type/boolean.rb +21 -0
- data/lib/active_model/type/date.rb +50 -0
- data/lib/active_model/type/date_time.rb +44 -0
- data/lib/active_model/type/decimal.rb +52 -0
- data/lib/active_model/type/decimal_without_scale.rb +11 -0
- data/lib/active_model/type/float.rb +25 -0
- data/lib/active_model/type/helpers.rb +4 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
- data/lib/active_model/type/helpers/mutable.rb +18 -0
- data/lib/active_model/type/helpers/numeric.rb +34 -0
- data/lib/active_model/type/helpers/time_value.rb +77 -0
- data/lib/active_model/type/immutable_string.rb +29 -0
- data/lib/active_model/type/integer.rb +66 -0
- data/lib/active_model/type/registry.rb +64 -0
- data/lib/active_model/type/string.rb +19 -0
- data/lib/active_model/type/text.rb +11 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/unsigned_integer.rb +15 -0
- data/lib/active_model/type/value.rb +112 -0
- data/lib/active_model/validations.rb +35 -3
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +61 -9
- data/lib/active_model/validations/callbacks.rb +3 -3
- data/lib/active_model/validations/confirmation.rb +16 -4
- data/lib/active_model/validations/exclusion.rb +3 -1
- data/lib/active_model/validations/format.rb +1 -1
- data/lib/active_model/validations/helper_methods.rb +13 -0
- data/lib/active_model/validations/inclusion.rb +3 -3
- data/lib/active_model/validations/length.rb +48 -17
- data/lib/active_model/validations/numericality.rb +12 -13
- data/lib/active_model/validations/validates.rb +1 -1
- data/lib/active_model/validations/with.rb +0 -10
- data/lib/active_model/validator.rb +6 -2
- data/lib/active_model/version.rb +1 -1
- metadata +34 -9
- data/lib/active_model/serializers/xml.rb +0 -238
@@ -0,0 +1,64 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
# :stopdoc:
|
3
|
+
module Type
|
4
|
+
class Registry
|
5
|
+
def initialize
|
6
|
+
@registrations = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(type_name, klass = nil, **options, &block)
|
10
|
+
block ||= proc { |_, *args| klass.new(*args) }
|
11
|
+
registrations << registration_klass.new(type_name, block, **options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def lookup(symbol, *args)
|
15
|
+
registration = find_registration(symbol, *args)
|
16
|
+
|
17
|
+
if registration
|
18
|
+
registration.call(self, symbol, *args)
|
19
|
+
else
|
20
|
+
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :registrations
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def registration_klass
|
31
|
+
Registration
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_registration(symbol, *args)
|
35
|
+
registrations.find { |r| r.matches?(symbol, *args) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Registration
|
40
|
+
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
|
41
|
+
def initialize(name, block, **)
|
42
|
+
@name = name
|
43
|
+
@block = block
|
44
|
+
end
|
45
|
+
|
46
|
+
def call(_registry, *args, **kwargs)
|
47
|
+
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
48
|
+
block.call(*args, **kwargs)
|
49
|
+
else
|
50
|
+
block.call(*args)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def matches?(type_name, *args, **kwargs)
|
55
|
+
type_name == name
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
attr_reader :name, :block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
# :startdoc:
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "active_model/type/immutable_string"
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class String < ImmutableString # :nodoc:
|
6
|
+
def changed_in_place?(raw_old_value, new_value)
|
7
|
+
if new_value.is_a?(::String)
|
8
|
+
raw_old_value != new_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def cast_value(value)
|
15
|
+
::String.new(super)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Time < Value # :nodoc:
|
4
|
+
include Helpers::TimeValue
|
5
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
6
|
+
defaults: { 1 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
7
|
+
)
|
8
|
+
|
9
|
+
def type
|
10
|
+
:time
|
11
|
+
end
|
12
|
+
|
13
|
+
def user_input_in_time_zone(value)
|
14
|
+
return unless value.present?
|
15
|
+
|
16
|
+
case value
|
17
|
+
when ::String
|
18
|
+
value = "2000-01-01 #{value}"
|
19
|
+
when ::Time
|
20
|
+
value = value.change(year: 2000, day: 1, month: 1)
|
21
|
+
end
|
22
|
+
|
23
|
+
super(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def cast_value(value)
|
29
|
+
return value unless value.is_a?(::String)
|
30
|
+
return if value.empty?
|
31
|
+
|
32
|
+
if value =~ /^2000-01-01/
|
33
|
+
dummy_time_value = value
|
34
|
+
else
|
35
|
+
dummy_time_value = "2000-01-01 #{value}"
|
36
|
+
end
|
37
|
+
|
38
|
+
fast_string_to_time(dummy_time_value) || begin
|
39
|
+
time_hash = ::Date._parse(dummy_time_value)
|
40
|
+
return if time_hash[:hour].nil?
|
41
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Value
|
4
|
+
attr_reader :precision, :scale, :limit
|
5
|
+
|
6
|
+
def initialize(precision: nil, limit: nil, scale: nil)
|
7
|
+
@precision = precision
|
8
|
+
@scale = scale
|
9
|
+
@limit = limit
|
10
|
+
end
|
11
|
+
|
12
|
+
def type # :nodoc:
|
13
|
+
end
|
14
|
+
|
15
|
+
# Converts a value from database input to the appropriate ruby type. The
|
16
|
+
# return value of this method will be returned from
|
17
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
18
|
+
# implementation just calls Value#cast.
|
19
|
+
#
|
20
|
+
# +value+ The raw input, as provided from the database.
|
21
|
+
def deserialize(value)
|
22
|
+
cast(value)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Type casts a value from user input (e.g. from a setter). This value may
|
26
|
+
# be a string from the form builder, or a ruby object passed to a setter.
|
27
|
+
# There is currently no way to differentiate between which source it came
|
28
|
+
# from.
|
29
|
+
#
|
30
|
+
# The return value of this method will be returned from
|
31
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
|
32
|
+
# Value#cast_value.
|
33
|
+
#
|
34
|
+
# +value+ The raw input, as provided to the attribute setter.
|
35
|
+
def cast(value)
|
36
|
+
cast_value(value) unless value.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Casts a value from the ruby type to a type that the database knows how
|
40
|
+
# to understand. The returned value from this method should be a
|
41
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
42
|
+
# +nil+.
|
43
|
+
def serialize(value)
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
# Type casts a value for schema dumping. This method is private, as we are
|
48
|
+
# hoping to remove it entirely.
|
49
|
+
def type_cast_for_schema(value) # :nodoc:
|
50
|
+
value.inspect
|
51
|
+
end
|
52
|
+
|
53
|
+
# These predicates are not documented, as I need to look further into
|
54
|
+
# their use, and see if they can be removed entirely.
|
55
|
+
def binary? # :nodoc:
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Determines whether a value has changed for dirty checking. +old_value+
|
60
|
+
# and +new_value+ will always be type-cast. Types should not need to
|
61
|
+
# override this method.
|
62
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
63
|
+
old_value != new_value
|
64
|
+
end
|
65
|
+
|
66
|
+
# Determines whether the mutable value has been modified since it was
|
67
|
+
# read. Returns +false+ by default. If your type returns an object
|
68
|
+
# which could be mutated, you should override this method. You will need
|
69
|
+
# to either:
|
70
|
+
#
|
71
|
+
# - pass +new_value+ to Value#serialize and compare it to
|
72
|
+
# +raw_old_value+
|
73
|
+
#
|
74
|
+
# or
|
75
|
+
#
|
76
|
+
# - pass +raw_old_value+ to Value#deserialize and compare it to
|
77
|
+
# +new_value+
|
78
|
+
#
|
79
|
+
# +raw_old_value+ The original value, before being passed to
|
80
|
+
# +deserialize+.
|
81
|
+
#
|
82
|
+
# +new_value+ The current value, after type casting.
|
83
|
+
def changed_in_place?(raw_old_value, new_value)
|
84
|
+
false
|
85
|
+
end
|
86
|
+
|
87
|
+
def ==(other)
|
88
|
+
self.class == other.class &&
|
89
|
+
precision == other.precision &&
|
90
|
+
scale == other.scale &&
|
91
|
+
limit == other.limit
|
92
|
+
end
|
93
|
+
alias eql? ==
|
94
|
+
|
95
|
+
def hash
|
96
|
+
[self.class, precision, scale, limit].hash
|
97
|
+
end
|
98
|
+
|
99
|
+
def assert_valid_value(*)
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Convenience method for types which do not need separate type casting
|
105
|
+
# behavior for user and database inputs. Called by Value#cast for
|
106
|
+
# values except +nil+.
|
107
|
+
def cast_value(value) # :doc:
|
108
|
+
value
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -47,10 +47,9 @@ module ActiveModel
|
|
47
47
|
include HelperMethods
|
48
48
|
|
49
49
|
attr_accessor :validation_context
|
50
|
-
private :validation_context=
|
51
50
|
define_callbacks :validate, scope: :name
|
52
51
|
|
53
|
-
class_attribute :_validators
|
52
|
+
class_attribute :_validators
|
54
53
|
self._validators = Hash.new { |h,k| h[k] = [] }
|
55
54
|
end
|
56
55
|
|
@@ -163,7 +162,7 @@ module ActiveModel
|
|
163
162
|
options = options.dup
|
164
163
|
options[:if] = Array(options[:if])
|
165
164
|
options[:if].unshift ->(o) {
|
166
|
-
Array(options[:on])
|
165
|
+
!(Array(options[:on]) & Array(o.validation_context)).empty?
|
167
166
|
}
|
168
167
|
end
|
169
168
|
|
@@ -375,6 +374,15 @@ module ActiveModel
|
|
375
374
|
!valid?(context)
|
376
375
|
end
|
377
376
|
|
377
|
+
# Runs all the validations within the specified context. Returns +true+ if
|
378
|
+
# no errors are found, raises +ValidationError+ otherwise.
|
379
|
+
#
|
380
|
+
# Validations with no <tt>:on</tt> option will run no matter the context. Validations with
|
381
|
+
# some <tt>:on</tt> option will only run in the specified context.
|
382
|
+
def validate!(context = nil)
|
383
|
+
valid?(context) || raise_validation_error
|
384
|
+
end
|
385
|
+
|
378
386
|
# Hook method defining how an attribute value should be retrieved. By default
|
379
387
|
# this is assumed to be an instance named after the attribute. Override this
|
380
388
|
# method in subclasses should you need to retrieve the value for a given
|
@@ -399,6 +407,30 @@ module ActiveModel
|
|
399
407
|
_run_validate_callbacks
|
400
408
|
errors.empty?
|
401
409
|
end
|
410
|
+
|
411
|
+
def raise_validation_error
|
412
|
+
raise(ValidationError.new(self))
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# = Active Model ValidationError
|
417
|
+
#
|
418
|
+
# Raised by <tt>validate!</tt> when the model is invalid. Use the
|
419
|
+
# +model+ method to retrieve the record which did not validate.
|
420
|
+
#
|
421
|
+
# begin
|
422
|
+
# complex_operation_that_internally_calls_validate!
|
423
|
+
# rescue ActiveModel::ValidationError => invalid
|
424
|
+
# puts invalid.model.errors
|
425
|
+
# end
|
426
|
+
class ValidationError < StandardError
|
427
|
+
attr_reader :model
|
428
|
+
|
429
|
+
def initialize(model)
|
430
|
+
@model = model
|
431
|
+
errors = @model.errors.full_messages.join(", ")
|
432
|
+
super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :"errors.messages.model_invalid"))
|
433
|
+
end
|
402
434
|
end
|
403
435
|
end
|
404
436
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module ActiveModel
|
2
2
|
module Validations
|
3
|
-
# == Active Model Absence Validator
|
3
|
+
# == \Active \Model Absence Validator
|
4
4
|
class AbsenceValidator < EachValidator #:nodoc:
|
5
5
|
def validate_each(record, attr_name, value)
|
6
6
|
record.errors.add(attr_name, :present, options) if value.present?
|
@@ -3,22 +3,73 @@ module ActiveModel
|
|
3
3
|
module Validations
|
4
4
|
class AcceptanceValidator < EachValidator # :nodoc:
|
5
5
|
def initialize(options)
|
6
|
-
super({ allow_nil: true, accept: "1" }.merge!(options))
|
6
|
+
super({ allow_nil: true, accept: ["1", true] }.merge!(options))
|
7
7
|
setup!(options[:class])
|
8
8
|
end
|
9
9
|
|
10
10
|
def validate_each(record, attribute, value)
|
11
|
-
unless value
|
11
|
+
unless acceptable_option?(value)
|
12
12
|
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
|
+
|
17
18
|
def setup!(klass)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
|
20
|
+
end
|
21
|
+
|
22
|
+
def acceptable_option?(value)
|
23
|
+
Array(options[:accept]).include?(value)
|
24
|
+
end
|
25
|
+
|
26
|
+
class LazilyDefineAttributes < Module
|
27
|
+
def initialize(attribute_definition)
|
28
|
+
define_method(:respond_to_missing?) do |method_name, include_private=false|
|
29
|
+
super(method_name, include_private) || attribute_definition.matches?(method_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
define_method(:method_missing) do |method_name, *args, &block|
|
33
|
+
if attribute_definition.matches?(method_name)
|
34
|
+
attribute_definition.define_on(self.class)
|
35
|
+
send(method_name, *args, &block)
|
36
|
+
else
|
37
|
+
super(method_name, *args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class AttributeDefinition
|
44
|
+
def initialize(attributes)
|
45
|
+
@attributes = attributes.map(&:to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def matches?(method_name)
|
49
|
+
attr_name = convert_to_reader_name(method_name)
|
50
|
+
attributes.include?(attr_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def define_on(klass)
|
54
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
55
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
56
|
+
klass.send(:attr_reader, *attr_readers)
|
57
|
+
klass.send(:attr_writer, *attr_writers)
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
attr_reader :attributes
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def convert_to_reader_name(method_name)
|
67
|
+
attr_name = method_name.to_s
|
68
|
+
if attr_name.end_with?("=")
|
69
|
+
attr_name = attr_name[0..-2]
|
70
|
+
end
|
71
|
+
attr_name
|
72
|
+
end
|
22
73
|
end
|
23
74
|
end
|
24
75
|
|
@@ -38,9 +89,10 @@ module ActiveModel
|
|
38
89
|
# Configuration options:
|
39
90
|
# * <tt>:message</tt> - A custom error message (default is: "must be
|
40
91
|
# accepted").
|
41
|
-
# * <tt>:accept</tt> - Specifies value that is considered accepted.
|
42
|
-
#
|
43
|
-
# an
|
92
|
+
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
|
93
|
+
# Also accepts an array of possible values. The default value is
|
94
|
+
# an array ["1", true], which makes it easy to relate to an HTML
|
95
|
+
# checkbox. This should be set to, or include, +true+ if you are validating
|
44
96
|
# a database column, since the attribute is typecast from "1" to +true+
|
45
97
|
# before validation.
|
46
98
|
#
|
@@ -15,15 +15,15 @@ module ActiveModel
|
|
15
15
|
# after_validation :do_stuff_after_validation
|
16
16
|
# end
|
17
17
|
#
|
18
|
-
# Like other <tt>before_*</tt> callbacks if +before_validation+
|
19
|
-
# +
|
18
|
+
# Like other <tt>before_*</tt> callbacks if +before_validation+ throws
|
19
|
+
# +:abort+ then <tt>valid?</tt> will not be called.
|
20
20
|
module Callbacks
|
21
21
|
extend ActiveSupport::Concern
|
22
22
|
|
23
23
|
included do
|
24
24
|
include ActiveSupport::Callbacks
|
25
25
|
define_callbacks :validation,
|
26
|
-
terminator:
|
26
|
+
terminator: deprecated_false_terminator,
|
27
27
|
skip_after_callbacks_if_terminated: true,
|
28
28
|
scope: [:kind, :name]
|
29
29
|
end
|