activemodel 4.2.0 → 6.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Numeric
|
7
|
+
def serialize(value)
|
8
|
+
cast(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def cast(value)
|
12
|
+
# Checks whether the value is numeric. Spaceship operator
|
13
|
+
# will return nil if value is not numeric.
|
14
|
+
value = if value <=> 0
|
15
|
+
value
|
16
|
+
else
|
17
|
+
case value
|
18
|
+
when true then 1
|
19
|
+
when false then 0
|
20
|
+
else value.presence
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
super(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
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)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def number_to_non_number?(old_value, new_value_before_type_cast)
|
33
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
def non_numeric_string?(value)
|
37
|
+
# 'wibble'.to_i will give zero, we want to make sure
|
38
|
+
# that we aren't marking int zero to string zero as
|
39
|
+
# changed.
|
40
|
+
!NUMERIC_REGEX.match?(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
NUMERIC_REGEX = /\A\s*[+-]?\d/
|
44
|
+
private_constant :NUMERIC_REGEX
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/zones"
|
4
|
+
require "active_support/core_ext/time/zones"
|
5
|
+
|
6
|
+
module ActiveModel
|
7
|
+
module Type
|
8
|
+
module Helpers # :nodoc: all
|
9
|
+
module TimeValue
|
10
|
+
def serialize(value)
|
11
|
+
value = apply_seconds_precision(value)
|
12
|
+
|
13
|
+
if value.acts_like?(:time)
|
14
|
+
if is_utc?
|
15
|
+
value = value.getutc if value.respond_to?(:getutc) && !value.utc?
|
16
|
+
else
|
17
|
+
value = value.getlocal if value.respond_to?(:getlocal)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_seconds_precision(value)
|
25
|
+
return value unless precision && value.respond_to?(:nsec)
|
26
|
+
|
27
|
+
number_of_insignificant_digits = 9 - precision
|
28
|
+
round_power = 10**number_of_insignificant_digits
|
29
|
+
rounded_off_nsec = value.nsec % round_power
|
30
|
+
|
31
|
+
if rounded_off_nsec > 0
|
32
|
+
value.change(nsec: value.nsec - rounded_off_nsec)
|
33
|
+
else
|
34
|
+
value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def type_cast_for_schema(value)
|
39
|
+
value.to_s(:db).inspect
|
40
|
+
end
|
41
|
+
|
42
|
+
def user_input_in_time_zone(value)
|
43
|
+
value.in_time_zone
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
48
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
49
|
+
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
50
|
+
|
51
|
+
if offset
|
52
|
+
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
53
|
+
return unless time
|
54
|
+
|
55
|
+
time -= offset unless offset == 0
|
56
|
+
is_utc? ? time : time.getlocal
|
57
|
+
elsif is_utc?
|
58
|
+
::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
59
|
+
else
|
60
|
+
::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
ISO_DATETIME = /
|
65
|
+
\A
|
66
|
+
(\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T
|
67
|
+
(\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456
|
68
|
+
(?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00
|
69
|
+
\z
|
70
|
+
/x
|
71
|
+
|
72
|
+
def fast_string_to_time(string)
|
73
|
+
return unless ISO_DATETIME =~ string
|
74
|
+
|
75
|
+
usec = $7.to_i
|
76
|
+
usec_len = $7&.length
|
77
|
+
if usec_len&.< 6
|
78
|
+
usec *= 10**(6 - usec_len)
|
79
|
+
end
|
80
|
+
|
81
|
+
if $8
|
82
|
+
offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
|
83
|
+
end
|
84
|
+
|
85
|
+
new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/time/zones"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
module Helpers # :nodoc: all
|
8
|
+
module Timezone
|
9
|
+
def is_utc?
|
10
|
+
::Time.zone_default.nil? || ::Time.zone_default.match?("UTC")
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_timezone
|
14
|
+
is_utc? ? :utc : :local
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/helpers/accepts_multiparameter_time"
|
4
|
+
require "active_model/type/helpers/numeric"
|
5
|
+
require "active_model/type/helpers/mutable"
|
6
|
+
require "active_model/type/helpers/time_value"
|
7
|
+
require "active_model/type/helpers/timezone"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class ImmutableString < Value # :nodoc:
|
6
|
+
def initialize(**args)
|
7
|
+
@true = -(args.delete(:true)&.to_s || "t")
|
8
|
+
@false = -(args.delete(:false)&.to_s || "f")
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def type
|
13
|
+
:string
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize(value)
|
17
|
+
case value
|
18
|
+
when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s
|
19
|
+
when true then @true
|
20
|
+
when false then @false
|
21
|
+
else super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def cast_value(value)
|
27
|
+
case value
|
28
|
+
when true then @true
|
29
|
+
when false then @false
|
30
|
+
else value.to_s.freeze
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Integer < Value # :nodoc:
|
6
|
+
include Helpers::Numeric
|
7
|
+
|
8
|
+
# Column storage size in bytes.
|
9
|
+
# 4 bytes means an integer as opposed to smallint etc.
|
10
|
+
DEFAULT_LIMIT = 4
|
11
|
+
|
12
|
+
def initialize(**)
|
13
|
+
super
|
14
|
+
@range = min_value...max_value
|
15
|
+
end
|
16
|
+
|
17
|
+
def type
|
18
|
+
:integer
|
19
|
+
end
|
20
|
+
|
21
|
+
def deserialize(value)
|
22
|
+
return if value.blank?
|
23
|
+
value.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize(value)
|
27
|
+
return if value.is_a?(::String) && non_numeric_string?(value)
|
28
|
+
ensure_in_range(super)
|
29
|
+
end
|
30
|
+
|
31
|
+
def serializable?(value)
|
32
|
+
cast_value = cast(value)
|
33
|
+
in_range?(cast_value) && super
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
attr_reader :range
|
38
|
+
|
39
|
+
def in_range?(value)
|
40
|
+
!value || range.member?(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cast_value(value)
|
44
|
+
value.to_i rescue nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensure_in_range(value)
|
48
|
+
unless in_range?(value)
|
49
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
50
|
+
end
|
51
|
+
value
|
52
|
+
end
|
53
|
+
|
54
|
+
def max_value
|
55
|
+
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
56
|
+
end
|
57
|
+
|
58
|
+
def min_value
|
59
|
+
-max_value
|
60
|
+
end
|
61
|
+
|
62
|
+
def _limit
|
63
|
+
limit || DEFAULT_LIMIT
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# :stopdoc:
|
5
|
+
module Type
|
6
|
+
class Registry
|
7
|
+
def initialize
|
8
|
+
@registrations = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_dup(other)
|
12
|
+
@registrations = @registrations.dup
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def register(type_name, klass = nil, **options, &block)
|
17
|
+
unless block_given?
|
18
|
+
block = proc { |_, *args| klass.new(*args) }
|
19
|
+
block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
|
20
|
+
end
|
21
|
+
registrations << registration_klass.new(type_name, block, **options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def lookup(symbol, *args, **kwargs)
|
25
|
+
registration = find_registration(symbol, *args, **kwargs)
|
26
|
+
|
27
|
+
if registration
|
28
|
+
registration.call(self, symbol, *args, **kwargs)
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
attr_reader :registrations
|
36
|
+
|
37
|
+
def registration_klass
|
38
|
+
Registration
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_registration(symbol, *args, **kwargs)
|
42
|
+
registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Registration
|
47
|
+
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
|
48
|
+
def initialize(name, block, **)
|
49
|
+
@name = name
|
50
|
+
@block = block
|
51
|
+
end
|
52
|
+
|
53
|
+
def call(_registry, *args, **kwargs)
|
54
|
+
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
55
|
+
block.call(*args, **kwargs)
|
56
|
+
else
|
57
|
+
block.call(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def matches?(type_name, *args, **kwargs)
|
62
|
+
type_name == name
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
attr_reader :name, :block
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# :startdoc:
|
70
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/immutable_string"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class String < ImmutableString # :nodoc:
|
8
|
+
def changed_in_place?(raw_old_value, new_value)
|
9
|
+
if new_value.is_a?(::String)
|
10
|
+
raw_old_value != new_value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_immutable_string
|
15
|
+
ImmutableString.new(
|
16
|
+
true: @true,
|
17
|
+
false: @false,
|
18
|
+
limit: limit,
|
19
|
+
precision: precision,
|
20
|
+
scale: scale,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def cast_value(value)
|
26
|
+
case value
|
27
|
+
when ::String then ::String.new(value)
|
28
|
+
when true then @true
|
29
|
+
when false then @false
|
30
|
+
else value.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Time < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
7
|
+
include Helpers::TimeValue
|
8
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
9
|
+
defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
10
|
+
)
|
11
|
+
|
12
|
+
def type
|
13
|
+
:time
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_input_in_time_zone(value)
|
17
|
+
return unless value.present?
|
18
|
+
|
19
|
+
case value
|
20
|
+
when ::String
|
21
|
+
value = "2000-01-01 #{value}"
|
22
|
+
time_hash = ::Date._parse(value)
|
23
|
+
return if time_hash[:hour].nil?
|
24
|
+
when ::Time
|
25
|
+
value = value.change(year: 2000, day: 1, month: 1)
|
26
|
+
end
|
27
|
+
|
28
|
+
super(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def cast_value(value)
|
33
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
34
|
+
return if value.empty?
|
35
|
+
|
36
|
+
dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
|
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, :offset))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Value
|
6
|
+
attr_reader :precision, :scale, :limit
|
7
|
+
|
8
|
+
def initialize(precision: nil, limit: nil, scale: nil)
|
9
|
+
@precision = precision
|
10
|
+
@scale = scale
|
11
|
+
@limit = limit
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns true if this type can convert +value+ to a type that is usable
|
15
|
+
# by the database. For example a boolean type can return +true+ if the
|
16
|
+
# value parameter is a Ruby boolean, but may return +false+ if the value
|
17
|
+
# parameter is some other object.
|
18
|
+
def serializable?(value)
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def type # :nodoc:
|
23
|
+
end
|
24
|
+
|
25
|
+
# Converts a value from database input to the appropriate ruby type. The
|
26
|
+
# return value of this method will be returned from
|
27
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. The default
|
28
|
+
# implementation just calls Value#cast.
|
29
|
+
#
|
30
|
+
# +value+ The raw input, as provided from the database.
|
31
|
+
def deserialize(value)
|
32
|
+
cast(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Type casts a value from user input (e.g. from a setter). This value may
|
36
|
+
# be a string from the form builder, or a ruby object passed to a setter.
|
37
|
+
# There is currently no way to differentiate between which source it came
|
38
|
+
# from.
|
39
|
+
#
|
40
|
+
# The return value of this method will be returned from
|
41
|
+
# ActiveRecord::AttributeMethods::Read#read_attribute. See also:
|
42
|
+
# Value#cast_value.
|
43
|
+
#
|
44
|
+
# +value+ The raw input, as provided to the attribute setter.
|
45
|
+
def cast(value)
|
46
|
+
cast_value(value) unless value.nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Casts a value from the ruby type to a type that the database knows how
|
50
|
+
# to understand. The returned value from this method should be a
|
51
|
+
# +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
|
52
|
+
# +nil+.
|
53
|
+
def serialize(value)
|
54
|
+
value
|
55
|
+
end
|
56
|
+
|
57
|
+
# Type casts a value for schema dumping. This method is private, as we are
|
58
|
+
# hoping to remove it entirely.
|
59
|
+
def type_cast_for_schema(value) # :nodoc:
|
60
|
+
value.inspect
|
61
|
+
end
|
62
|
+
|
63
|
+
# These predicates are not documented, as I need to look further into
|
64
|
+
# their use, and see if they can be removed entirely.
|
65
|
+
def binary? # :nodoc:
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Determines whether a value has changed for dirty checking. +old_value+
|
70
|
+
# and +new_value+ will always be type-cast. Types should not need to
|
71
|
+
# override this method.
|
72
|
+
def changed?(old_value, new_value, _new_value_before_type_cast)
|
73
|
+
old_value != new_value
|
74
|
+
end
|
75
|
+
|
76
|
+
# Determines whether the mutable value has been modified since it was
|
77
|
+
# read. Returns +false+ by default. If your type returns an object
|
78
|
+
# which could be mutated, you should override this method. You will need
|
79
|
+
# to either:
|
80
|
+
#
|
81
|
+
# - pass +new_value+ to Value#serialize and compare it to
|
82
|
+
# +raw_old_value+
|
83
|
+
#
|
84
|
+
# or
|
85
|
+
#
|
86
|
+
# - pass +raw_old_value+ to Value#deserialize and compare it to
|
87
|
+
# +new_value+
|
88
|
+
#
|
89
|
+
# +raw_old_value+ The original value, before being passed to
|
90
|
+
# +deserialize+.
|
91
|
+
#
|
92
|
+
# +new_value+ The current value, after type casting.
|
93
|
+
def changed_in_place?(raw_old_value, new_value)
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
def value_constructed_by_mass_assignment?(_value) # :nodoc:
|
98
|
+
false
|
99
|
+
end
|
100
|
+
|
101
|
+
def force_equality?(_value) # :nodoc:
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def map(value) # :nodoc:
|
106
|
+
yield value
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other)
|
110
|
+
self.class == other.class &&
|
111
|
+
precision == other.precision &&
|
112
|
+
scale == other.scale &&
|
113
|
+
limit == other.limit
|
114
|
+
end
|
115
|
+
alias eql? ==
|
116
|
+
|
117
|
+
def hash
|
118
|
+
[self.class, precision, scale, limit].hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def assert_valid_value(_)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
# Convenience method for types which do not need separate type casting
|
126
|
+
# behavior for user and database inputs. Called by Value#cast for
|
127
|
+
# values except +nil+.
|
128
|
+
def cast_value(value) # :doc:
|
129
|
+
value
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/type/helpers"
|
4
|
+
require "active_model/type/value"
|
5
|
+
|
6
|
+
require "active_model/type/big_integer"
|
7
|
+
require "active_model/type/binary"
|
8
|
+
require "active_model/type/boolean"
|
9
|
+
require "active_model/type/date"
|
10
|
+
require "active_model/type/date_time"
|
11
|
+
require "active_model/type/decimal"
|
12
|
+
require "active_model/type/float"
|
13
|
+
require "active_model/type/immutable_string"
|
14
|
+
require "active_model/type/integer"
|
15
|
+
require "active_model/type/string"
|
16
|
+
require "active_model/type/time"
|
17
|
+
|
18
|
+
require "active_model/type/registry"
|
19
|
+
|
20
|
+
module ActiveModel
|
21
|
+
module Type
|
22
|
+
@registry = Registry.new
|
23
|
+
|
24
|
+
class << self
|
25
|
+
attr_accessor :registry # :nodoc:
|
26
|
+
|
27
|
+
# Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup
|
28
|
+
def register(type_name, klass = nil, **options, &block)
|
29
|
+
registry.register(type_name, klass, **options, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def lookup(*args, **kwargs) # :nodoc:
|
33
|
+
registry.lookup(*args, **kwargs)
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_value # :nodoc:
|
37
|
+
@default_value ||= Value.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
register(:big_integer, Type::BigInteger)
|
42
|
+
register(:binary, Type::Binary)
|
43
|
+
register(:boolean, Type::Boolean)
|
44
|
+
register(:date, Type::Date)
|
45
|
+
register(:datetime, Type::DateTime)
|
46
|
+
register(:decimal, Type::Decimal)
|
47
|
+
register(:float, Type::Float)
|
48
|
+
register(:immutable_string, Type::ImmutableString)
|
49
|
+
register(:integer, Type::Integer)
|
50
|
+
register(:string, Type::String)
|
51
|
+
register(:time, Type::Time)
|
52
|
+
end
|
53
|
+
end
|
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveModel
|
2
4
|
module Validations
|
3
|
-
# == Active Model Absence Validator
|
5
|
+
# == \Active \Model Absence Validator
|
4
6
|
class AbsenceValidator < EachValidator #:nodoc:
|
5
7
|
def validate_each(record, attr_name, value)
|
6
|
-
record.errors.add(attr_name, :present, options) if value.present?
|
8
|
+
record.errors.add(attr_name, :present, **options) if value.present?
|
7
9
|
end
|
8
10
|
end
|
9
11
|
|
10
12
|
module HelperMethods
|
11
13
|
# Validates that the specified attributes are blank (as defined by
|
12
|
-
# Object#
|
14
|
+
# Object#present?). Happens by default on save.
|
13
15
|
#
|
14
16
|
# class Person < ActiveRecord::Base
|
15
17
|
# validates_absence_of :first_name
|
@@ -22,7 +24,7 @@ module ActiveModel
|
|
22
24
|
#
|
23
25
|
# There is also a list of default options supported by every validator:
|
24
26
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
|
25
|
-
# See <tt>ActiveModel::
|
27
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
26
28
|
def validates_absence_of(*attr_names)
|
27
29
|
validates_with AbsenceValidator, _merge_attributes(attr_names)
|
28
30
|
end
|