activemodel 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|