activemodel 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +172 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +247 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +517 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +178 -0
  11. data/lib/active_model/attribute_set.rb +106 -0
  12. data/lib/active_model/attribute_set/builder.rb +124 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  14. data/lib/active_model/attributes.rb +138 -0
  15. data/lib/active_model/callbacks.rb +156 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +280 -0
  18. data/lib/active_model/errors.rb +601 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +334 -0
  25. data/lib/active_model/railtie.rb +20 -0
  26. data/lib/active_model/secure_password.rb +128 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +147 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +47 -0
  34. data/lib/active_model/type/date.rb +53 -0
  35. data/lib/active_model/type/date_time.rb +47 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +34 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +45 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +44 -0
  42. data/lib/active_model/type/helpers/time_value.rb +81 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +58 -0
  46. data/lib/active_model/type/registry.rb +62 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +47 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +437 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +102 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. 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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/integer"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class BigInteger < Integer # :nodoc:
8
+ private
9
+
10
+ def max_value
11
+ ::Float::INFINITY
12
+ end
13
+ end
14
+ end
15
+ 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.unpack1("H*")
44
+ end
45
+
46
+ def ==(other)
47
+ other == to_s || super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,47 @@
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 = [
18
+ false, 0,
19
+ "0", :"0",
20
+ "f", :f,
21
+ "F", :F,
22
+ "false", :false,
23
+ "FALSE", :FALSE,
24
+ "off", :off,
25
+ "OFF", :OFF,
26
+ ].to_set.freeze
27
+
28
+ def type # :nodoc:
29
+ :boolean
30
+ end
31
+
32
+ def serialize(value) # :nodoc:
33
+ cast(value)
34
+ end
35
+
36
+ private
37
+
38
+ def cast_value(value)
39
+ if value == ""
40
+ nil
41
+ else
42
+ !FALSE_VALUES.include?(value)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,53 @@
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 type_cast_for_schema(value)
14
+ value.to_s(:db).inspect
15
+ end
16
+
17
+ private
18
+
19
+ def cast_value(value)
20
+ if value.is_a?(::String)
21
+ return if value.empty?
22
+ fast_string_to_date(value) || fallback_string_to_date(value)
23
+ elsif value.respond_to?(:to_date)
24
+ value.to_date
25
+ else
26
+ value
27
+ end
28
+ end
29
+
30
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
31
+ def fast_string_to_date(string)
32
+ if string =~ ISO_DATE
33
+ new_date $1.to_i, $2.to_i, $3.to_i
34
+ end
35
+ end
36
+
37
+ def fallback_string_to_date(string)
38
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
39
+ end
40
+
41
+ def new_date(year, mon, mday)
42
+ unless year.nil? || (year == 0 && mon == 0 && mday == 0)
43
+ ::Date.new(year, mon, mday) rescue nil
44
+ end
45
+ end
46
+
47
+ def value_from_multiparameter_assignment(*)
48
+ time = super
49
+ time && new_date(time.year, time.mon, time.mday)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,47 @@
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
+ private
17
+
18
+ def cast_value(value)
19
+ return apply_seconds_precision(value) unless value.is_a?(::String)
20
+ return if value.empty?
21
+
22
+ fast_string_to_time(value) || fallback_string_to_time(value)
23
+ end
24
+
25
+ # '0.123456' -> 123456
26
+ # '1.123456' -> 123456
27
+ def microseconds(time)
28
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
29
+ end
30
+
31
+ def fallback_string_to_time(string)
32
+ time_hash = ::Date._parse(string)
33
+ time_hash[:sec_fraction] = microseconds(time_hash)
34
+
35
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
36
+ end
37
+
38
+ def value_from_multiparameter_assignment(values_hash)
39
+ missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
40
+ if missing_parameters.any?
41
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
42
+ end
43
+ super
44
+ end
45
+ end
46
+ end
47
+ 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,34 @@
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
+ private
22
+
23
+ def cast_value(value)
24
+ case value
25
+ when ::Float then value
26
+ when "Infinity" then ::Float::INFINITY
27
+ when "-Infinity" then -::Float::INFINITY
28
+ when "NaN" then ::Float::NAN
29
+ else value.to_f
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end