activemodel 5.2.3
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 +7 -0
- data/CHANGELOG.md +114 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +264 -0
- data/lib/active_model.rb +77 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute/user_provided_default.rb +52 -0
- data/lib/active_model/attribute_assignment.rb +57 -0
- data/lib/active_model/attribute_methods.rb +478 -0
- data/lib/active_model/attribute_mutation_tracker.rb +124 -0
- data/lib/active_model/attribute_set.rb +114 -0
- data/lib/active_model/attribute_set/builder.rb +126 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_model/attributes.rb +111 -0
- data/lib/active_model/callbacks.rb +153 -0
- data/lib/active_model/conversion.rb +111 -0
- data/lib/active_model/dirty.rb +343 -0
- data/lib/active_model/errors.rb +517 -0
- data/lib/active_model/forbidden_attributes_protection.rb +31 -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 +36 -0
- data/lib/active_model/model.rb +99 -0
- data/lib/active_model/naming.rb +318 -0
- data/lib/active_model/railtie.rb +14 -0
- data/lib/active_model/secure_password.rb +129 -0
- data/lib/active_model/serialization.rb +192 -0
- data/lib/active_model/serializers/json.rb +146 -0
- data/lib/active_model/translation.rb +70 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/type/big_integer.rb +15 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +38 -0
- data/lib/active_model/type/date.rb +57 -0
- data/lib/active_model/type/date_time.rb +51 -0
- data/lib/active_model/type/decimal.rb +70 -0
- data/lib/active_model/type/float.rb +36 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +37 -0
- data/lib/active_model/type/helpers/time_value.rb +68 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/immutable_string.rb +32 -0
- data/lib/active_model/type/integer.rb +70 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +26 -0
- data/lib/active_model/type/time.rb +51 -0
- data/lib/active_model/type/value.rb +126 -0
- data/lib/active_model/validations.rb +439 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +106 -0
- data/lib/active_model/validations/callbacks.rb +122 -0
- data/lib/active_model/validations/clusivity.rb +54 -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 +114 -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 +129 -0
- data/lib/active_model/validations/numericality.rb +189 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/validates.rb +174 -0
- data/lib/active_model/validations/with.rb +147 -0
- data/lib/active_model/validator.rb +183 -0
- data/lib/active_model/version.rb +10 -0
- metadata +125 -0
@@ -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,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
class AcceptsMultiparameterTime < Module
|
7
|
+
def initialize(defaults: {})
|
8
|
+
define_method(:cast) do |value|
|
9
|
+
if value.is_a?(Hash)
|
10
|
+
value_from_multiparameter_assignment(value)
|
11
|
+
else
|
12
|
+
super(value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method(:assert_valid_value) do |value|
|
17
|
+
if value.is_a?(Hash)
|
18
|
+
value_from_multiparameter_assignment(value)
|
19
|
+
else
|
20
|
+
super(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
define_method(:value_constructed_by_mass_assignment?) do |value|
|
25
|
+
value.is_a?(Hash)
|
26
|
+
end
|
27
|
+
|
28
|
+
define_method(:value_from_multiparameter_assignment) do |values_hash|
|
29
|
+
defaults.each do |k, v|
|
30
|
+
values_hash[k] ||= v
|
31
|
+
end
|
32
|
+
return unless values_hash[1] && values_hash[2] && values_hash[3]
|
33
|
+
values = values_hash.sort.map(&:last)
|
34
|
+
::Time.send(default_timezone, *values)
|
35
|
+
end
|
36
|
+
private :value_from_multiparameter_assignment
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Mutable
|
7
|
+
def cast(value)
|
8
|
+
deserialize(serialize(value))
|
9
|
+
end
|
10
|
+
|
11
|
+
# +raw_old_value+ will be the `_before_type_cast` version of the
|
12
|
+
# value (likely a string). +new_value+ will be the current, type
|
13
|
+
# cast value.
|
14
|
+
def changed_in_place?(raw_old_value, new_value)
|
15
|
+
raw_old_value != serialize(new_value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers # :nodoc: all
|
6
|
+
module Numeric
|
7
|
+
def cast(value)
|
8
|
+
value = \
|
9
|
+
case value
|
10
|
+
when true then 1
|
11
|
+
when false then 0
|
12
|
+
when ::String then value.presence
|
13
|
+
else value
|
14
|
+
end
|
15
|
+
super(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
|
19
|
+
super || number_to_non_number?(old_value, new_value_before_type_cast)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def number_to_non_number?(old_value, new_value_before_type_cast)
|
25
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast)
|
26
|
+
end
|
27
|
+
|
28
|
+
def non_numeric_string?(value)
|
29
|
+
# 'wibble'.to_i will give zero, we want to make sure
|
30
|
+
# that we aren't marking int zero to string zero as
|
31
|
+
# changed.
|
32
|
+
!/\A[-+]?\d+/.match?(value.to_s)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
zone_conversion_method = is_utc? ? :getutc : :getlocal
|
15
|
+
|
16
|
+
if value.respond_to?(zone_conversion_method)
|
17
|
+
value = value.send(zone_conversion_method)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def apply_seconds_precision(value)
|
25
|
+
return value unless precision && value.respond_to?(:usec)
|
26
|
+
number_of_insignificant_digits = 6 - precision
|
27
|
+
round_power = 10**number_of_insignificant_digits
|
28
|
+
value.change(usec: value.usec - value.usec % round_power)
|
29
|
+
end
|
30
|
+
|
31
|
+
def type_cast_for_schema(value)
|
32
|
+
value.to_s(:db).inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
def user_input_in_time_zone(value)
|
36
|
+
value.in_time_zone
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
42
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
43
|
+
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
44
|
+
|
45
|
+
if offset
|
46
|
+
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
47
|
+
return unless time
|
48
|
+
|
49
|
+
time -= offset
|
50
|
+
is_utc? ? time : time.getlocal
|
51
|
+
else
|
52
|
+
::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
57
|
+
|
58
|
+
# Doesn't handle time zones.
|
59
|
+
def fast_string_to_time(string)
|
60
|
+
if string =~ ISO_DATETIME
|
61
|
+
microsec = ($7.to_r * 1_000_000).to_i
|
62
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
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 =~ "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,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class ImmutableString < Value # :nodoc:
|
6
|
+
def type
|
7
|
+
:string
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(value)
|
11
|
+
case value
|
12
|
+
when ::Numeric, ActiveSupport::Duration then value.to_s
|
13
|
+
when true then "t"
|
14
|
+
when false then "f"
|
15
|
+
else super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def cast_value(value)
|
22
|
+
result = \
|
23
|
+
case value
|
24
|
+
when true then "t"
|
25
|
+
when false then "f"
|
26
|
+
else value.to_s
|
27
|
+
end
|
28
|
+
result.freeze
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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.nil?
|
23
|
+
value.to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize(value)
|
27
|
+
result = cast(value)
|
28
|
+
if result
|
29
|
+
ensure_in_range(result)
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
35
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :range
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def cast_value(value)
|
43
|
+
case value
|
44
|
+
when true then 1
|
45
|
+
when false then 0
|
46
|
+
else
|
47
|
+
value.to_i rescue nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_in_range(value)
|
52
|
+
unless range.cover?(value)
|
53
|
+
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def max_value
|
58
|
+
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
59
|
+
end
|
60
|
+
|
61
|
+
def min_value
|
62
|
+
-max_value
|
63
|
+
end
|
64
|
+
|
65
|
+
def _limit
|
66
|
+
limit || DEFAULT_LIMIT
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
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 register(type_name, klass = nil, **options, &block)
|
12
|
+
block ||= proc { |_, *args| klass.new(*args) }
|
13
|
+
registrations << registration_klass.new(type_name, block, **options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def lookup(symbol, *args)
|
17
|
+
registration = find_registration(symbol, *args)
|
18
|
+
|
19
|
+
if registration
|
20
|
+
registration.call(self, symbol, *args)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
27
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
28
|
+
protected
|
29
|
+
|
30
|
+
attr_reader :registrations
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def registration_klass
|
35
|
+
Registration
|
36
|
+
end
|
37
|
+
|
38
|
+
def find_registration(symbol, *args)
|
39
|
+
registrations.find { |r| r.matches?(symbol, *args) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Registration
|
44
|
+
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
|
45
|
+
def initialize(name, block, **)
|
46
|
+
@name = name
|
47
|
+
@block = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def call(_registry, *args, **kwargs)
|
51
|
+
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
52
|
+
block.call(*args, **kwargs)
|
53
|
+
else
|
54
|
+
block.call(*args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def matches?(type_name, *args, **kwargs)
|
59
|
+
type_name == name
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
63
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
64
|
+
protected
|
65
|
+
|
66
|
+
attr_reader :name, :block
|
67
|
+
end
|
68
|
+
end
|
69
|
+
# :startdoc:
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
private
|
15
|
+
|
16
|
+
def cast_value(value)
|
17
|
+
case value
|
18
|
+
when ::String then ::String.new(value)
|
19
|
+
when true then "t".freeze
|
20
|
+
when false then "f".freeze
|
21
|
+
else value.to_s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,51 @@
|
|
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 => 1970, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
10
|
+
)
|
11
|
+
|
12
|
+
def type
|
13
|
+
:time
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize(value)
|
17
|
+
super(cast(value))
|
18
|
+
end
|
19
|
+
|
20
|
+
def user_input_in_time_zone(value)
|
21
|
+
return unless value.present?
|
22
|
+
|
23
|
+
case value
|
24
|
+
when ::String
|
25
|
+
value = "2000-01-01 #{value}"
|
26
|
+
time_hash = ::Date._parse(value)
|
27
|
+
return if time_hash[:hour].nil?
|
28
|
+
when ::Time
|
29
|
+
value = value.change(year: 2000, day: 1, month: 1)
|
30
|
+
end
|
31
|
+
|
32
|
+
super(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cast_value(value)
|
38
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
39
|
+
return if value.empty?
|
40
|
+
|
41
|
+
dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
|
42
|
+
|
43
|
+
fast_string_to_time(dummy_time_value) || begin
|
44
|
+
time_hash = ::Date._parse(dummy_time_value)
|
45
|
+
return if time_hash[:hour].nil?
|
46
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|