activemodel 6.1.6.1 → 7.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -164
  3. data/MIT-LICENSE +2 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_model/api.rb +99 -0
  6. data/lib/active_model/attribute.rb +4 -0
  7. data/lib/active_model/attribute_methods.rb +99 -52
  8. data/lib/active_model/attribute_set.rb +4 -1
  9. data/lib/active_model/attributes.rb +15 -12
  10. data/lib/active_model/callbacks.rb +1 -1
  11. data/lib/active_model/conversion.rb +2 -2
  12. data/lib/active_model/dirty.rb +5 -4
  13. data/lib/active_model/errors.rb +17 -3
  14. data/lib/active_model/gem_version.rb +4 -4
  15. data/lib/active_model/locale/en.yml +1 -0
  16. data/lib/active_model/model.rb +6 -59
  17. data/lib/active_model/naming.rb +15 -8
  18. data/lib/active_model/secure_password.rb +0 -1
  19. data/lib/active_model/serialization.rb +7 -2
  20. data/lib/active_model/translation.rb +1 -1
  21. data/lib/active_model/type/helpers/numeric.rb +9 -1
  22. data/lib/active_model/type/helpers/time_value.rb +2 -2
  23. data/lib/active_model/type/integer.rb +4 -1
  24. data/lib/active_model/type/registry.rb +8 -38
  25. data/lib/active_model/type/time.rb +1 -1
  26. data/lib/active_model/type.rb +6 -6
  27. data/lib/active_model/validations/absence.rb +1 -1
  28. data/lib/active_model/validations/clusivity.rb +1 -1
  29. data/lib/active_model/validations/comparability.rb +29 -0
  30. data/lib/active_model/validations/comparison.rb +82 -0
  31. data/lib/active_model/validations/confirmation.rb +4 -4
  32. data/lib/active_model/validations/numericality.rb +27 -20
  33. data/lib/active_model/validations.rb +4 -4
  34. data/lib/active_model/validator.rb +2 -2
  35. data/lib/active_model.rb +2 -1
  36. metadata +17 -15
@@ -4,25 +4,26 @@ require "active_model/attribute_set"
4
4
  require "active_model/attribute/user_provided_default"
5
5
 
6
6
  module ActiveModel
7
- module Attributes #:nodoc:
7
+ module Attributes # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
  include ActiveModel::AttributeMethods
10
10
 
11
11
  included do
12
- attribute_method_suffix "="
12
+ attribute_method_suffix "=", parameters: "value"
13
13
  class_attribute :attribute_types, :_default_attributes, instance_accessor: false
14
14
  self.attribute_types = Hash.new(Type.default_value)
15
15
  self._default_attributes = AttributeSet.new({})
16
16
  end
17
17
 
18
18
  module ClassMethods
19
- def attribute(name, type = Type::Value.new, **options)
19
+ def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
20
20
  name = name.to_s
21
- if type.is_a?(Symbol)
22
- type = ActiveModel::Type.lookup(type, **options.except(:default))
23
- end
24
- self.attribute_types = attribute_types.merge(name => type)
25
- define_default_attribute(name, options.fetch(:default, NO_DEFAULT_PROVIDED), type)
21
+
22
+ cast_type = Type.lookup(cast_type, **options) if Symbol === cast_type
23
+ cast_type ||= attribute_types[name]
24
+
25
+ self.attribute_types = attribute_types.merge(name => cast_type)
26
+ define_default_attribute(name, default, cast_type)
26
27
  define_attribute_method(name)
27
28
  end
28
29
 
@@ -46,10 +47,12 @@ module ActiveModel
46
47
  ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
47
48
  owner, name, writer: true,
48
49
  ) do |temp_method_name, attr_name_expr|
49
- owner <<
50
- "def #{temp_method_name}(value)" <<
51
- " _write_attribute(#{attr_name_expr}, value)" <<
52
- "end"
50
+ owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_model) do |batch|
51
+ batch <<
52
+ "def #{temp_method_name}(value)" <<
53
+ " _write_attribute(#{attr_name_expr}, value)" <<
54
+ "end"
55
+ end
53
56
  end
54
57
  end
55
58
 
@@ -63,7 +63,7 @@ module ActiveModel
63
63
  # NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
64
64
  #
65
65
  module Callbacks
66
- def self.extended(base) #:nodoc:
66
+ def self.extended(base) # :nodoc:
67
67
  base.class_eval do
68
68
  include ActiveSupport::Callbacks
69
69
  end
@@ -96,10 +96,10 @@ module ActiveModel
96
96
  self.class._to_partial_path
97
97
  end
98
98
 
99
- module ClassMethods #:nodoc:
99
+ module ClassMethods # :nodoc:
100
100
  # Provide a class level cache for #to_partial_path. This is an
101
101
  # internal method and should not be accessed directly.
102
- def _to_partial_path #:nodoc:
102
+ def _to_partial_path # :nodoc:
103
103
  @_to_partial_path ||= begin
104
104
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
105
  collection = ActiveSupport::Inflector.tableize(name)
@@ -123,10 +123,11 @@ module ActiveModel
123
123
  include ActiveModel::AttributeMethods
124
124
 
125
125
  included do
126
- attribute_method_suffix "_changed?", "_change", "_will_change!", "_was"
127
- attribute_method_suffix "_previously_changed?", "_previous_change", "_previously_was"
128
- attribute_method_affix prefix: "restore_", suffix: "!"
129
- attribute_method_affix prefix: "clear_", suffix: "_change"
126
+ attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
127
+ attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
128
+ attribute_method_suffix "_previous_change", "_previously_was", parameters: false
129
+ attribute_method_affix prefix: "restore_", suffix: "!", parameters: false
130
+ attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false
130
131
  end
131
132
 
132
133
  def initialize_dup(other) # :nodoc:
@@ -378,6 +378,14 @@ module ActiveModel
378
378
  # If +type+ is a symbol, it will be translated using the appropriate
379
379
  # scope (see +generate_message+).
380
380
  #
381
+ # person.errors.add(:name, :blank)
382
+ # person.errors.messages
383
+ # # => {:name=>["can't be blank"]}
384
+ #
385
+ # person.errors.add(:name, :too_long, { count: 25 })
386
+ # person.errors.messages
387
+ # # => ["is too long (maximum is 25 characters)"]
388
+ #
381
389
  # If +type+ is a proc, it will be called, allowing for things like
382
390
  # <tt>Time.now</tt> to be used within an error.
383
391
  #
@@ -563,6 +571,12 @@ module ActiveModel
563
571
  add_from_legacy_details_hash(data["details"]) if data.key?("details")
564
572
  end
565
573
 
574
+ def inspect # :nodoc:
575
+ inspection = @errors.inspect
576
+
577
+ "#<#{self.class.name} #{inspection}>"
578
+ end
579
+
566
580
  private
567
581
  def normalize_arguments(attribute, type, **options)
568
582
  # Evaluate proc first
@@ -596,7 +610,7 @@ module ActiveModel
596
610
  end
597
611
  end
598
612
 
599
- class DeprecationHandlingMessageHash < SimpleDelegator
613
+ class DeprecationHandlingMessageHash < SimpleDelegator # :nodoc:
600
614
  def initialize(errors)
601
615
  @errors = errors
602
616
  super(prepare_content)
@@ -635,7 +649,7 @@ module ActiveModel
635
649
  end
636
650
  end
637
651
 
638
- class DeprecationHandlingMessageArray < SimpleDelegator
652
+ class DeprecationHandlingMessageArray < SimpleDelegator # :nodoc:
639
653
  def initialize(content, errors, attribute)
640
654
  @errors = errors
641
655
  @attribute = attribute
@@ -657,7 +671,7 @@ module ActiveModel
657
671
  end
658
672
  end
659
673
 
660
- class DeprecationHandlingDetailsHash < SimpleDelegator
674
+ class DeprecationHandlingDetailsHash < SimpleDelegator # :nodoc:
661
675
  def initialize(details)
662
676
  details.default = []
663
677
  details.freeze
@@ -7,10 +7,10 @@ module ActiveModel
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 6
13
- PRE = "1"
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "alpha1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -32,5 +32,6 @@ en:
32
32
  less_than: "must be less than %{count}"
33
33
  less_than_or_equal_to: "must be less than or equal to %{count}"
34
34
  other_than: "must be other than %{count}"
35
+ in: "must be in %{count}"
35
36
  odd: "must be odd"
36
37
  even: "must be even"
@@ -3,11 +3,10 @@
3
3
  module ActiveModel
4
4
  # == Active \Model \Basic \Model
5
5
  #
6
- # Includes the required interface for an object to interact with
7
- # Action Pack and Action View, using different Active Model modules.
8
- # It includes model name introspections, conversions, translations and
9
- # validations. Besides that, it allows you to initialize the object with a
10
- # hash of attributes, pretty much like Active Record does.
6
+ # Allows implementing models similar to <tt>ActiveRecord::Base</tt>.
7
+ # Includes <tt>ActiveModel::API</tt> for the required interface for an
8
+ # object to interact with Action Pack and Action View, but can be
9
+ # extended with other functionalities.
11
10
  #
12
11
  # A minimal implementation could be:
13
12
  #
@@ -20,23 +19,7 @@ module ActiveModel
20
19
  # person.name # => "bob"
21
20
  # person.age # => "18"
22
21
  #
23
- # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
24
- # to return +false+, which is the most common case. You may want to override
25
- # it in your class to simulate a different scenario:
26
- #
27
- # class Person
28
- # include ActiveModel::Model
29
- # attr_accessor :id, :name
30
- #
31
- # def persisted?
32
- # self.id == 1
33
- # end
34
- # end
35
- #
36
- # person = Person.new(id: 1, name: 'bob')
37
- # person.persisted? # => true
38
- #
39
- # Also, if for some reason you need to run code on <tt>initialize</tt>, make
22
+ # If for some reason you need to run code on <tt>initialize</tt>, make
40
23
  # sure you call +super+ if you want the attributes hash initialization to
41
24
  # happen.
42
25
  #
@@ -58,42 +41,6 @@ module ActiveModel
58
41
  # (see below).
59
42
  module Model
60
43
  extend ActiveSupport::Concern
61
- include ActiveModel::AttributeAssignment
62
- include ActiveModel::Validations
63
- include ActiveModel::Conversion
64
-
65
- included do
66
- extend ActiveModel::Naming
67
- extend ActiveModel::Translation
68
- end
69
-
70
- # Initializes a new model with the given +params+.
71
- #
72
- # class Person
73
- # include ActiveModel::Model
74
- # attr_accessor :name, :age
75
- # end
76
- #
77
- # person = Person.new(name: 'bob', age: '18')
78
- # person.name # => "bob"
79
- # person.age # => "18"
80
- def initialize(attributes = {})
81
- assign_attributes(attributes) if attributes
82
-
83
- super()
84
- end
85
-
86
- # Indicates if the model is persisted. Default is +false+.
87
- #
88
- # class Person
89
- # include ActiveModel::Model
90
- # attr_accessor :id, :name
91
- # end
92
- #
93
- # person = Person.new(id: 1, name: 'bob')
94
- # person.persisted? # => false
95
- def persisted?
96
- false
97
- end
44
+ include ActiveModel::API
98
45
  end
99
46
  end
@@ -3,6 +3,7 @@
3
3
  require "active_support/core_ext/hash/except"
4
4
  require "active_support/core_ext/module/introspection"
5
5
  require "active_support/core_ext/module/redefine_method"
6
+ require "active_support/core_ext/module/delegation"
6
7
 
7
8
  module ActiveModel
8
9
  class Name
@@ -153,6 +154,7 @@ module ActiveModel
153
154
  # Returns a new ActiveModel::Name instance. By default, the +namespace+
154
155
  # and +name+ option will take the namespace and name of the given class
155
156
  # respectively.
157
+ # Use +locale+ argument for singularize and pluralize model name.
156
158
  #
157
159
  # module Foo
158
160
  # class Bar
@@ -161,7 +163,7 @@ module ActiveModel
161
163
  #
162
164
  # ActiveModel::Name.new(Foo::Bar).to_s
163
165
  # # => "Foo::Bar"
164
- def initialize(klass, namespace = nil, name = nil)
166
+ def initialize(klass, namespace = nil, name = nil, locale = :en)
165
167
  @name = name || klass.name
166
168
 
167
169
  raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
@@ -169,16 +171,17 @@ module ActiveModel
169
171
  @unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
170
172
  @klass = klass
171
173
  @singular = _singularize(@name)
172
- @plural = ActiveSupport::Inflector.pluralize(@singular)
174
+ @plural = ActiveSupport::Inflector.pluralize(@singular, locale)
175
+ @uncountable = @plural == @singular
173
176
  @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
174
177
  @human = ActiveSupport::Inflector.humanize(@element)
175
178
  @collection = ActiveSupport::Inflector.tableize(@name)
176
179
  @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
177
180
  @i18n_key = @name.underscore.to_sym
178
181
 
179
- @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
180
- @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
181
- @route_key << "_index" if @plural == @singular
182
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup)
183
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale)
184
+ @route_key << "_index" if @uncountable
182
185
  end
183
186
 
184
187
  # Transform the model name into a more human format, using I18n. By default,
@@ -206,6 +209,10 @@ module ActiveModel
206
209
  I18n.translate(defaults.shift, **options)
207
210
  end
208
211
 
212
+ def uncountable?
213
+ @uncountable
214
+ end
215
+
209
216
  private
210
217
  def _singularize(string)
211
218
  ActiveSupport::Inflector.underscore(string).tr("/", "_")
@@ -232,7 +239,7 @@ module ActiveModel
232
239
  # is required to pass the \Active \Model Lint test. So either extending the
233
240
  # provided method below, or rolling your own is required.
234
241
  module Naming
235
- def self.extended(base) #:nodoc:
242
+ def self.extended(base) # :nodoc:
236
243
  base.silence_redefinition_of_method :model_name
237
244
  base.delegate :model_name, to: :class
238
245
  end
@@ -279,7 +286,7 @@ module ActiveModel
279
286
  # ActiveModel::Naming.uncountable?(Sheep) # => true
280
287
  # ActiveModel::Naming.uncountable?(Post) # => false
281
288
  def self.uncountable?(record_or_class)
282
- plural(record_or_class) == singular(record_or_class)
289
+ model_name_from_record_or_class(record_or_class).uncountable?
283
290
  end
284
291
 
285
292
  # Returns string to use while generating route names. It differs for
@@ -321,7 +328,7 @@ module ActiveModel
321
328
  model_name_from_record_or_class(record_or_class).param_key
322
329
  end
323
330
 
324
- def self.model_name_from_record_or_class(record_or_class) #:nodoc:
331
+ def self.model_name_from_record_or_class(record_or_class) # :nodoc:
325
332
  if record_or_class.respond_to?(:to_model)
326
333
  record_or_class.to_model.model_name
327
334
  else
@@ -94,7 +94,6 @@ module ActiveModel
94
94
 
95
95
  define_method("#{attribute}=") do |unencrypted_password|
96
96
  if unencrypted_password.nil?
97
- instance_variable_set("@#{attribute}", nil)
98
97
  self.public_send("#{attribute}_digest=", nil)
99
98
  elsif !unencrypted_password.empty?
100
99
  instance_variable_set("@#{attribute}", unencrypted_password)
@@ -123,7 +123,7 @@ module ActiveModel
123
123
  # user.serializable_hash(include: { notes: { only: 'title' }})
124
124
  # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
125
125
  def serializable_hash(options = nil)
126
- attribute_names = attributes.keys
126
+ attribute_names = self.attribute_names
127
127
 
128
128
  return serializable_attributes(attribute_names) if options.blank?
129
129
 
@@ -148,6 +148,11 @@ module ActiveModel
148
148
  hash
149
149
  end
150
150
 
151
+ # Returns an array of attribute names as strings
152
+ def attribute_names # :nodoc:
153
+ attributes.keys
154
+ end
155
+
151
156
  private
152
157
  # Hook method defining how an attribute value should be retrieved for
153
158
  # serialization. By default this is assumed to be an instance named after
@@ -177,7 +182,7 @@ module ActiveModel
177
182
  # +association+ - name of the association
178
183
  # +records+ - the association record(s) to be serialized
179
184
  # +opts+ - options for the association records
180
- def serializable_add_includes(options = {}) #:nodoc:
185
+ def serializable_add_includes(options = {}) # :nodoc:
181
186
  return unless includes = options[:include]
182
187
 
183
188
  unless includes.is_a?(Hash)
@@ -17,7 +17,7 @@ module ActiveModel
17
17
  #
18
18
  # This also provides the required class methods for hooking into the
19
19
  # Rails internationalization API, including being able to define a
20
- # class based +i18n_scope+ and +lookup_ancestors+ to find translations in
20
+ # class-based +i18n_scope+ and +lookup_ancestors+ to find translations in
21
21
  # parent classes.
22
22
  module Translation
23
23
  include ActiveModel::Naming
@@ -25,10 +25,18 @@ module ActiveModel
25
25
  end
26
26
 
27
27
  def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
28
- super || number_to_non_number?(old_value, new_value_before_type_cast)
28
+ (super || number_to_non_number?(old_value, new_value_before_type_cast)) &&
29
+ !equal_nan?(old_value, new_value_before_type_cast)
29
30
  end
30
31
 
31
32
  private
33
+ def equal_nan?(old_value, new_value)
34
+ (old_value.is_a?(::Float) || old_value.is_a?(BigDecimal)) &&
35
+ old_value.nan? &&
36
+ old_value.instance_of?(new_value.class) &&
37
+ new_value.nan?
38
+ end
39
+
32
40
  def number_to_non_number?(old_value, new_value_before_type_cast)
33
41
  old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
34
42
  end
@@ -12,9 +12,9 @@ module ActiveModel
12
12
 
13
13
  if value.acts_like?(:time)
14
14
  if is_utc?
15
- value = value.getutc if value.respond_to?(:getutc) && !value.utc?
15
+ value = value.getutc if !value.utc?
16
16
  else
17
- value = value.getlocal if value.respond_to?(:getlocal)
17
+ value = value.getlocal
18
18
  end
19
19
  end
20
20
 
@@ -30,7 +30,10 @@ module ActiveModel
30
30
 
31
31
  def serializable?(value)
32
32
  cast_value = cast(value)
33
- in_range?(cast_value) && super
33
+ in_range?(cast_value) || begin
34
+ yield cast_value if block_given?
35
+ false
36
+ end
34
37
  end
35
38
 
36
39
  private
@@ -1,68 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveModel
4
- # :stopdoc:
5
4
  module Type
6
- class Registry
5
+ class Registry # :nodoc:
7
6
  def initialize
8
- @registrations = []
7
+ @registrations = {}
9
8
  end
10
9
 
11
- def initialize_dup(other)
10
+ def initialize_copy(other)
12
11
  @registrations = @registrations.dup
13
12
  super
14
13
  end
15
14
 
16
- def register(type_name, klass = nil, **options, &block)
15
+ def register(type_name, klass = nil, &block)
17
16
  unless block_given?
18
17
  block = proc { |_, *args| klass.new(*args) }
19
18
  block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
20
19
  end
21
- registrations << registration_klass.new(type_name, block, **options)
20
+ registrations[type_name] = block
22
21
  end
23
22
 
24
23
  def lookup(symbol, *args)
25
- registration = find_registration(symbol, *args)
24
+ registration = registrations[symbol]
26
25
 
27
26
  if registration
28
- registration.call(self, symbol, *args)
27
+ registration.call(symbol, *args)
29
28
  else
30
29
  raise ArgumentError, "Unknown type #{symbol.inspect}"
31
30
  end
32
31
  end
33
- ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
32
+ ruby2_keywords(:lookup)
34
33
 
35
34
  private
36
35
  attr_reader :registrations
37
-
38
- def registration_klass
39
- Registration
40
- end
41
-
42
- def find_registration(symbol, *args, **kwargs)
43
- registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
44
- end
45
- end
46
-
47
- class Registration
48
- # Options must be taken because of https://bugs.ruby-lang.org/issues/10856
49
- def initialize(name, block, **)
50
- @name = name
51
- @block = block
52
- end
53
-
54
- def call(_registry, *args)
55
- block.call(*args)
56
- end
57
- ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true)
58
-
59
- def matches?(type_name, *args, **kwargs)
60
- type_name == name
61
- end
62
-
63
- private
64
- attr_reader :name, :block
65
36
  end
66
37
  end
67
- # :startdoc:
68
38
  end
@@ -33,7 +33,7 @@ module ActiveModel
33
33
  return apply_seconds_precision(value) unless value.is_a?(::String)
34
34
  return if value.empty?
35
35
 
36
- dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
36
+ dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
37
37
 
38
38
  fast_string_to_time(dummy_time_value) || begin
39
39
  time_hash = ::Date._parse(dummy_time_value)
@@ -24,15 +24,15 @@ module ActiveModel
24
24
  class << self
25
25
  attr_accessor :registry # :nodoc:
26
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)
27
+ # Add a new type to the registry, allowing it to be referenced as a
28
+ # symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
29
+ def register(type_name, klass = nil, &block)
30
+ registry.register(type_name, klass, &block)
30
31
  end
31
32
 
32
- def lookup(*args) # :nodoc:
33
- registry.lookup(*args)
33
+ def lookup(...) # :nodoc:
34
+ registry.lookup(...)
34
35
  end
35
- ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
36
36
 
37
37
  def default_value # :nodoc:
38
38
  @default_value ||= Value.new
@@ -3,7 +3,7 @@
3
3
  module ActiveModel
4
4
  module Validations
5
5
  # == \Active \Model Absence Validator
6
- class AbsenceValidator < EachValidator #:nodoc:
6
+ class AbsenceValidator < EachValidator # :nodoc:
7
7
  def validate_each(record, attr_name, value)
8
8
  record.errors.add(attr_name, :present, **options) if value.present?
9
9
  end
@@ -4,7 +4,7 @@ require "active_support/core_ext/range"
4
4
 
5
5
  module ActiveModel
6
6
  module Validations
7
- module Clusivity #:nodoc:
7
+ module Clusivity # :nodoc:
8
8
  ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
9
9
  "and must be supplied as the :in (or :within) option of the configuration hash"
10
10
 
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ module Comparability # :nodoc:
6
+ COMPARE_CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
7
+ equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
8
+ other_than: :!= }.freeze
9
+
10
+ def option_value(record, option_value)
11
+ case option_value
12
+ when Proc
13
+ option_value.call(record)
14
+ when Symbol
15
+ record.send(option_value)
16
+ else
17
+ option_value
18
+ end
19
+ end
20
+
21
+ def error_options(value, option_value)
22
+ options.except(*COMPARE_CHECKS.keys).merge!(
23
+ count: option_value,
24
+ value: value
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end