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,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
# == Active \Model \Translation
|
5
|
+
#
|
6
|
+
# Provides integration between your object and the Rails internationalization
|
7
|
+
# (i18n) framework.
|
8
|
+
#
|
9
|
+
# A minimal implementation could be:
|
10
|
+
#
|
11
|
+
# class TranslatedPerson
|
12
|
+
# extend ActiveModel::Translation
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# TranslatedPerson.human_attribute_name('my_attribute')
|
16
|
+
# # => "My attribute"
|
17
|
+
#
|
18
|
+
# This also provides the required class methods for hooking into the
|
19
|
+
# Rails internationalization API, including being able to define a
|
20
|
+
# class based +i18n_scope+ and +lookup_ancestors+ to find translations in
|
21
|
+
# parent classes.
|
22
|
+
module Translation
|
23
|
+
include ActiveModel::Naming
|
24
|
+
|
25
|
+
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
|
26
|
+
def i18n_scope
|
27
|
+
:activemodel
|
28
|
+
end
|
29
|
+
|
30
|
+
# When localizing a string, it goes through the lookup returned by this
|
31
|
+
# method, which is used in ActiveModel::Name#human,
|
32
|
+
# ActiveModel::Errors#full_messages and
|
33
|
+
# ActiveModel::Translation#human_attribute_name.
|
34
|
+
def lookup_ancestors
|
35
|
+
ancestors.select { |x| x.respond_to?(:model_name) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Transforms attribute names into a more human format, such as "First name"
|
39
|
+
# instead of "first_name".
|
40
|
+
#
|
41
|
+
# Person.human_attribute_name("first_name") # => "First name"
|
42
|
+
#
|
43
|
+
# Specify +options+ with additional translating options.
|
44
|
+
def human_attribute_name(attribute, options = {})
|
45
|
+
options = { count: 1 }.merge!(options)
|
46
|
+
parts = attribute.to_s.split(".")
|
47
|
+
attribute = parts.pop
|
48
|
+
namespace = parts.join("/") unless parts.empty?
|
49
|
+
attributes_scope = "#{i18n_scope}.attributes"
|
50
|
+
|
51
|
+
if namespace
|
52
|
+
defaults = lookup_ancestors.map do |klass|
|
53
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
|
54
|
+
end
|
55
|
+
defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
|
56
|
+
else
|
57
|
+
defaults = lookup_ancestors.map do |klass|
|
58
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
defaults << :"attributes.#{attribute}"
|
63
|
+
defaults << options.delete(:default) if options[:default]
|
64
|
+
defaults << attribute.humanize
|
65
|
+
|
66
|
+
options[:default] = defaults
|
67
|
+
I18n.translate(defaults.shift, options)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Binary < Value # :nodoc:
|
6
|
+
def type
|
7
|
+
:binary
|
8
|
+
end
|
9
|
+
|
10
|
+
def binary?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def cast(value)
|
15
|
+
if value.is_a?(Data)
|
16
|
+
value.to_s
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def serialize(value)
|
23
|
+
return if value.nil?
|
24
|
+
Data.new(super)
|
25
|
+
end
|
26
|
+
|
27
|
+
def changed_in_place?(raw_old_value, value)
|
28
|
+
old_value = deserialize(raw_old_value)
|
29
|
+
old_value != value
|
30
|
+
end
|
31
|
+
|
32
|
+
class Data # :nodoc:
|
33
|
+
def initialize(value)
|
34
|
+
@value = value.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
@value
|
39
|
+
end
|
40
|
+
alias_method :to_str, :to_s
|
41
|
+
|
42
|
+
def hex
|
43
|
+
@value.unpack("H*")[0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def ==(other)
|
47
|
+
other == to_s || super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
# == Active \Model \Type \Boolean
|
6
|
+
#
|
7
|
+
# A class that behaves like a boolean type, including rules for coercion of user input.
|
8
|
+
#
|
9
|
+
# === Coercion
|
10
|
+
# Values set from user input will first be coerced into the appropriate ruby type.
|
11
|
+
# Coercion behavior is roughly mapped to Ruby's boolean semantics.
|
12
|
+
#
|
13
|
+
# - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
|
14
|
+
# - Empty strings are coerced to +nil+
|
15
|
+
# - All other values will be coerced to +true+
|
16
|
+
class Boolean < Value
|
17
|
+
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set
|
18
|
+
|
19
|
+
def type # :nodoc:
|
20
|
+
:boolean
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize(value) # :nodoc:
|
24
|
+
cast(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def cast_value(value)
|
30
|
+
if value == ""
|
31
|
+
nil
|
32
|
+
else
|
33
|
+
!FALSE_VALUES.include?(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Date < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
7
|
+
include Helpers::AcceptsMultiparameterTime.new
|
8
|
+
|
9
|
+
def type
|
10
|
+
:date
|
11
|
+
end
|
12
|
+
|
13
|
+
def serialize(value)
|
14
|
+
cast(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def type_cast_for_schema(value)
|
18
|
+
value.to_s(:db).inspect
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def cast_value(value)
|
24
|
+
if value.is_a?(::String)
|
25
|
+
return if value.empty?
|
26
|
+
fast_string_to_date(value) || fallback_string_to_date(value)
|
27
|
+
elsif value.respond_to?(:to_date)
|
28
|
+
value.to_date
|
29
|
+
else
|
30
|
+
value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
35
|
+
def fast_string_to_date(string)
|
36
|
+
if string =~ ISO_DATE
|
37
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fallback_string_to_date(string)
|
42
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_date(year, mon, mday)
|
46
|
+
unless year.nil? || (year == 0 && mon == 0 && mday == 0)
|
47
|
+
::Date.new(year, mon, mday) rescue nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def value_from_multiparameter_assignment(*)
|
52
|
+
time = super
|
53
|
+
time && new_date(time.year, time.mon, time.mday)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class DateTime < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
7
|
+
include Helpers::TimeValue
|
8
|
+
include Helpers::AcceptsMultiparameterTime.new(
|
9
|
+
defaults: { 4 => 0, 5 => 0 }
|
10
|
+
)
|
11
|
+
|
12
|
+
def type
|
13
|
+
:datetime
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize(value)
|
17
|
+
super(cast(value))
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cast_value(value)
|
23
|
+
return apply_seconds_precision(value) unless value.is_a?(::String)
|
24
|
+
return if value.empty?
|
25
|
+
|
26
|
+
fast_string_to_time(value) || fallback_string_to_time(value)
|
27
|
+
end
|
28
|
+
|
29
|
+
# '0.123456' -> 123456
|
30
|
+
# '1.123456' -> 123456
|
31
|
+
def microseconds(time)
|
32
|
+
time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def fallback_string_to_time(string)
|
36
|
+
time_hash = ::Date._parse(string)
|
37
|
+
time_hash[:sec_fraction] = microseconds(time_hash)
|
38
|
+
|
39
|
+
new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
|
40
|
+
end
|
41
|
+
|
42
|
+
def value_from_multiparameter_assignment(values_hash)
|
43
|
+
missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
|
44
|
+
if missing_parameter
|
45
|
+
raise ArgumentError, missing_parameter
|
46
|
+
end
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal/util"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
class Decimal < Value # :nodoc:
|
8
|
+
include Helpers::Numeric
|
9
|
+
BIGDECIMAL_PRECISION = 18
|
10
|
+
|
11
|
+
def type
|
12
|
+
:decimal
|
13
|
+
end
|
14
|
+
|
15
|
+
def type_cast_for_schema(value)
|
16
|
+
value.to_s.inspect
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def cast_value(value)
|
22
|
+
casted_value = \
|
23
|
+
case value
|
24
|
+
when ::Float
|
25
|
+
convert_float_to_big_decimal(value)
|
26
|
+
when ::Numeric
|
27
|
+
BigDecimal(value, precision || BIGDECIMAL_PRECISION)
|
28
|
+
when ::String
|
29
|
+
begin
|
30
|
+
value.to_d
|
31
|
+
rescue ArgumentError
|
32
|
+
BigDecimal(0)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
if value.respond_to?(:to_d)
|
36
|
+
value.to_d
|
37
|
+
else
|
38
|
+
cast_value(value.to_s)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
apply_scale(casted_value)
|
43
|
+
end
|
44
|
+
|
45
|
+
def convert_float_to_big_decimal(value)
|
46
|
+
if precision
|
47
|
+
BigDecimal(apply_scale(value), float_precision)
|
48
|
+
else
|
49
|
+
value.to_d
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def float_precision
|
54
|
+
if precision.to_i > ::Float::DIG + 1
|
55
|
+
::Float::DIG + 1
|
56
|
+
else
|
57
|
+
precision.to_i
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def apply_scale(value)
|
62
|
+
if scale
|
63
|
+
value.round(scale)
|
64
|
+
else
|
65
|
+
value
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
class Float < Value # :nodoc:
|
6
|
+
include Helpers::Numeric
|
7
|
+
|
8
|
+
def type
|
9
|
+
:float
|
10
|
+
end
|
11
|
+
|
12
|
+
def type_cast_for_schema(value)
|
13
|
+
return "::Float::NAN" if value.try(:nan?)
|
14
|
+
case value
|
15
|
+
when ::Float::INFINITY then "::Float::INFINITY"
|
16
|
+
when -::Float::INFINITY then "-::Float::INFINITY"
|
17
|
+
else super
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
alias serialize cast
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def cast_value(value)
|
26
|
+
case value
|
27
|
+
when ::Float then value
|
28
|
+
when "Infinity" then ::Float::INFINITY
|
29
|
+
when "-Infinity" then -::Float::INFINITY
|
30
|
+
when "NaN" then ::Float::NAN
|
31
|
+
else value.to_f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|