activemodel 5.2.3

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 +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