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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -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 +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -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 +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -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 +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -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,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ # == \Active \Model Absence Validator
6
+ class AbsenceValidator < EachValidator #:nodoc:
7
+ def validate_each(record, attr_name, value)
8
+ record.errors.add(attr_name, :present, options) if value.present?
9
+ end
10
+ end
11
+
12
+ module HelperMethods
13
+ # Validates that the specified attributes are blank (as defined by
14
+ # Object#blank?). Happens by default on save.
15
+ #
16
+ # class Person < ActiveRecord::Base
17
+ # validates_absence_of :first_name
18
+ # end
19
+ #
20
+ # The first_name attribute must be in the object and it must be blank.
21
+ #
22
+ # Configuration options:
23
+ # * <tt>:message</tt> - A custom error message (default is: "must be blank").
24
+ #
25
+ # There is also a list of default options supported by every validator:
26
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
27
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
28
+ def validates_absence_of(*attr_names)
29
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ class AcceptanceValidator < EachValidator # :nodoc:
6
+ def initialize(options)
7
+ super({ allow_nil: true, accept: ["1", true] }.merge!(options))
8
+ setup!(options[:class])
9
+ end
10
+
11
+ def validate_each(record, attribute, value)
12
+ unless acceptable_option?(value)
13
+ record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def setup!(klass)
20
+ klass.include(LazilyDefineAttributes.new(AttributeDefinition.new(attributes)))
21
+ end
22
+
23
+ def acceptable_option?(value)
24
+ Array(options[:accept]).include?(value)
25
+ end
26
+
27
+ class LazilyDefineAttributes < Module
28
+ def initialize(attribute_definition)
29
+ define_method(:respond_to_missing?) do |method_name, include_private = false|
30
+ super(method_name, include_private) || attribute_definition.matches?(method_name)
31
+ end
32
+
33
+ define_method(:method_missing) do |method_name, *args, &block|
34
+ if attribute_definition.matches?(method_name)
35
+ attribute_definition.define_on(self.class)
36
+ send(method_name, *args, &block)
37
+ else
38
+ super(method_name, *args, &block)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class AttributeDefinition
45
+ def initialize(attributes)
46
+ @attributes = attributes.map(&:to_s)
47
+ end
48
+
49
+ def matches?(method_name)
50
+ attr_name = convert_to_reader_name(method_name)
51
+ attributes.include?(attr_name)
52
+ end
53
+
54
+ def define_on(klass)
55
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
56
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
57
+ klass.send(:attr_reader, *attr_readers)
58
+ klass.send(:attr_writer, *attr_writers)
59
+ end
60
+
61
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
62
+ # Workaround for Ruby 2.2 "private attribute?" warning.
63
+ protected
64
+
65
+ attr_reader :attributes
66
+
67
+ private
68
+
69
+ def convert_to_reader_name(method_name)
70
+ method_name.to_s.chomp("=")
71
+ end
72
+ end
73
+ end
74
+
75
+ module HelperMethods
76
+ # Encapsulates the pattern of wanting to validate the acceptance of a
77
+ # terms of service check box (or similar agreement).
78
+ #
79
+ # class Person < ActiveRecord::Base
80
+ # validates_acceptance_of :terms_of_service
81
+ # validates_acceptance_of :eula, message: 'must be abided'
82
+ # end
83
+ #
84
+ # If the database column does not exist, the +terms_of_service+ attribute
85
+ # is entirely virtual. This check is performed only if +terms_of_service+
86
+ # is not +nil+ and by default on save.
87
+ #
88
+ # Configuration options:
89
+ # * <tt>:message</tt> - A custom error message (default is: "must be
90
+ # accepted").
91
+ # * <tt>:accept</tt> - Specifies a value that is considered accepted.
92
+ # Also accepts an array of possible values. The default value is
93
+ # an array ["1", true], which makes it easy to relate to an HTML
94
+ # checkbox. This should be set to, or include, +true+ if you are validating
95
+ # a database column, since the attribute is typecast from "1" to +true+
96
+ # before validation.
97
+ #
98
+ # There is also a list of default options supported by every validator:
99
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
100
+ # See <tt>ActiveModel::Validations#validates</tt> for more information.
101
+ def validates_acceptance_of(*attr_names)
102
+ validates_with AcceptanceValidator, _merge_attributes(attr_names)
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ # == Active \Model \Validation \Callbacks
6
+ #
7
+ # Provides an interface for any class to have +before_validation+ and
8
+ # +after_validation+ callbacks.
9
+ #
10
+ # First, include ActiveModel::Validations::Callbacks from the class you are
11
+ # creating:
12
+ #
13
+ # class MyModel
14
+ # include ActiveModel::Validations::Callbacks
15
+ #
16
+ # before_validation :do_stuff_before_validation
17
+ # after_validation :do_stuff_after_validation
18
+ # end
19
+ #
20
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
21
+ # +:abort+ then <tt>valid?</tt> will not be called.
22
+ module Callbacks
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ include ActiveSupport::Callbacks
27
+ define_callbacks :validation,
28
+ skip_after_callbacks_if_terminated: true,
29
+ scope: [:kind, :name]
30
+ end
31
+
32
+ module ClassMethods
33
+ # Defines a callback that will get called right before validation.
34
+ #
35
+ # class Person
36
+ # include ActiveModel::Validations
37
+ # include ActiveModel::Validations::Callbacks
38
+ #
39
+ # attr_accessor :name
40
+ #
41
+ # validates_length_of :name, maximum: 6
42
+ #
43
+ # before_validation :remove_whitespaces
44
+ #
45
+ # private
46
+ #
47
+ # def remove_whitespaces
48
+ # name.strip!
49
+ # end
50
+ # end
51
+ #
52
+ # person = Person.new
53
+ # person.name = ' bob '
54
+ # person.valid? # => true
55
+ # person.name # => "bob"
56
+ def before_validation(*args, &block)
57
+ options = args.extract_options!
58
+
59
+ if options.key?(:on)
60
+ options = options.dup
61
+ options[:on] = Array(options[:on])
62
+ options[:if] = Array(options[:if])
63
+ options[:if].unshift ->(o) {
64
+ !(options[:on] & Array(o.validation_context)).empty?
65
+ }
66
+ end
67
+
68
+ set_callback(:validation, :before, *args, options, &block)
69
+ end
70
+
71
+ # Defines a callback that will get called right after validation.
72
+ #
73
+ # class Person
74
+ # include ActiveModel::Validations
75
+ # include ActiveModel::Validations::Callbacks
76
+ #
77
+ # attr_accessor :name, :status
78
+ #
79
+ # validates_presence_of :name
80
+ #
81
+ # after_validation :set_status
82
+ #
83
+ # private
84
+ #
85
+ # def set_status
86
+ # self.status = errors.empty?
87
+ # end
88
+ # end
89
+ #
90
+ # person = Person.new
91
+ # person.name = ''
92
+ # person.valid? # => false
93
+ # person.status # => false
94
+ # person.name = 'bob'
95
+ # person.valid? # => true
96
+ # person.status # => true
97
+ def after_validation(*args, &block)
98
+ options = args.extract_options!
99
+ options = options.dup
100
+ options[:prepend] = true
101
+
102
+ if options.key?(:on)
103
+ options[:on] = Array(options[:on])
104
+ options[:if] = Array(options[:if])
105
+ options[:if].unshift ->(o) {
106
+ !(options[:on] & Array(o.validation_context)).empty?
107
+ }
108
+ end
109
+
110
+ set_callback(:validation, :after, *args, options, &block)
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ # Overwrite run validations to include callbacks.
117
+ def run_validations!
118
+ _run_validation_callbacks { super }
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/range"
4
+
5
+ module ActiveModel
6
+ module Validations
7
+ module Clusivity #:nodoc:
8
+ ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
9
+ "and must be supplied as the :in (or :within) option of the configuration hash"
10
+
11
+ def check_validity!
12
+ unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
13
+ raise ArgumentError, ERROR_MESSAGE
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def include?(record, value)
20
+ members = if delimiter.respond_to?(:call)
21
+ delimiter.call(record)
22
+ elsif delimiter.respond_to?(:to_sym)
23
+ record.send(delimiter)
24
+ else
25
+ delimiter
26
+ end
27
+
28
+ members.send(inclusion_method(members), value)
29
+ end
30
+
31
+ def delimiter
32
+ @delimiter ||= options[:in] || options[:within]
33
+ end
34
+
35
+ # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
36
+ # possible values in the range for equality, which is slower but more accurate.
37
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
38
+ # endpoints, which is fast but is only accurate on Numeric, Time, Date,
39
+ # or DateTime ranges.
40
+ def inclusion_method(enumerable)
41
+ if enumerable.is_a? Range
42
+ case enumerable.first
43
+ when Numeric, Time, DateTime, Date
44
+ :cover?
45
+ else
46
+ :include?
47
+ end
48
+ else
49
+ :include?
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ class ConfirmationValidator < EachValidator # :nodoc:
6
+ def initialize(options)
7
+ super({ case_sensitive: true }.merge!(options))
8
+ setup!(options[:class])
9
+ end
10
+
11
+ def validate_each(record, attribute, value)
12
+ unless (confirmed = record.send("#{attribute}_confirmation")).nil?
13
+ unless confirmation_value_equal?(record, attribute, value, confirmed)
14
+ human_attribute_name = record.class.human_attribute_name(attribute)
15
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.except(:case_sensitive).merge!(attribute: human_attribute_name))
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+ def setup!(klass)
22
+ klass.send(:attr_reader, *attributes.map do |attribute|
23
+ :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
24
+ end.compact)
25
+
26
+ klass.send(:attr_writer, *attributes.map do |attribute|
27
+ :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
28
+ end.compact)
29
+ end
30
+
31
+ def confirmation_value_equal?(record, attribute, value, confirmed)
32
+ if !options[:case_sensitive] && value.is_a?(String)
33
+ value.casecmp(confirmed) == 0
34
+ else
35
+ value == confirmed
36
+ end
37
+ end
38
+ end
39
+
40
+ module HelperMethods
41
+ # Encapsulates the pattern of wanting to validate a password or email
42
+ # address field with a confirmation.
43
+ #
44
+ # Model:
45
+ # class Person < ActiveRecord::Base
46
+ # validates_confirmation_of :user_name, :password
47
+ # validates_confirmation_of :email_address,
48
+ # message: 'should match confirmation'
49
+ # end
50
+ #
51
+ # View:
52
+ # <%= password_field "person", "password" %>
53
+ # <%= password_field "person", "password_confirmation" %>
54
+ #
55
+ # The added +password_confirmation+ attribute is virtual; it exists only
56
+ # as an in-memory attribute for validating the password. To achieve this,
57
+ # the validation adds accessors to the model for the confirmation
58
+ # attribute.
59
+ #
60
+ # NOTE: This check is performed only if +password_confirmation+ is not
61
+ # +nil+. To require confirmation, make sure to add a presence check for
62
+ # the confirmation attribute:
63
+ #
64
+ # validates_presence_of :password_confirmation, if: :password_changed?
65
+ #
66
+ # Configuration options:
67
+ # * <tt>:message</tt> - A custom error message (default is: "doesn't match
68
+ # <tt>%{translated_attribute_name}</tt>").
69
+ # * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
70
+ # non-text columns (+true+ by default).
71
+ #
72
+ # There is also a list of default options supported by every validator:
73
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
74
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
75
+ def validates_confirmation_of(*attr_names)
76
+ validates_with ConfirmationValidator, _merge_attributes(attr_names)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/validations/clusivity"
4
+
5
+ module ActiveModel
6
+ module Validations
7
+ class ExclusionValidator < EachValidator # :nodoc:
8
+ include Clusivity
9
+
10
+ def validate_each(record, attribute, value)
11
+ if include?(record, value)
12
+ record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
13
+ end
14
+ end
15
+ end
16
+
17
+ module HelperMethods
18
+ # Validates that the value of the specified attribute is not in a
19
+ # particular enumerable object.
20
+ #
21
+ # class Person < ActiveRecord::Base
22
+ # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
23
+ # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
24
+ # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
25
+ # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
26
+ # message: 'should not be the same as your username or first name'
27
+ # validates_exclusion_of :karma, in: :reserved_karmas
28
+ # end
29
+ #
30
+ # Configuration options:
31
+ # * <tt>:in</tt> - An enumerable object of items that the value shouldn't
32
+ # be part of. This can be supplied as a proc, lambda or symbol which returns an
33
+ # enumerable. If the enumerable is a numerical, time or datetime range the test
34
+ # is performed with <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. When
35
+ # using a proc or lambda the instance under validation is passed as an argument.
36
+ # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
37
+ # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
38
+ # * <tt>:message</tt> - Specifies a custom error message (default is: "is
39
+ # reserved").
40
+ #
41
+ # There is also a list of default options supported by every validator:
42
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
43
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
44
+ def validates_exclusion_of(*attr_names)
45
+ validates_with ExclusionValidator, _merge_attributes(attr_names)
46
+ end
47
+ end
48
+ end
49
+ end