activemodel 6.0.5.1 → 6.1.7.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -160
  3. data/MIT-LICENSE +1 -2
  4. data/README.rdoc +1 -1
  5. data/lib/active_model/attribute.rb +18 -17
  6. data/lib/active_model/attribute_assignment.rb +3 -4
  7. data/lib/active_model/attribute_methods.rb +74 -38
  8. data/lib/active_model/attribute_mutation_tracker.rb +8 -5
  9. data/lib/active_model/attribute_set/builder.rb +80 -13
  10. data/lib/active_model/attribute_set.rb +18 -16
  11. data/lib/active_model/attributes.rb +20 -24
  12. data/lib/active_model/callbacks.rb +1 -1
  13. data/lib/active_model/dirty.rb +17 -4
  14. data/lib/active_model/error.rb +207 -0
  15. data/lib/active_model/errors.rb +316 -208
  16. data/lib/active_model/gem_version.rb +3 -3
  17. data/lib/active_model/lint.rb +1 -1
  18. data/lib/active_model/naming.rb +2 -2
  19. data/lib/active_model/nested_error.rb +22 -0
  20. data/lib/active_model/railtie.rb +1 -1
  21. data/lib/active_model/secure_password.rb +15 -14
  22. data/lib/active_model/serialization.rb +9 -6
  23. data/lib/active_model/serializers/json.rb +7 -0
  24. data/lib/active_model/type/date_time.rb +2 -2
  25. data/lib/active_model/type/float.rb +2 -0
  26. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +11 -7
  27. data/lib/active_model/type/helpers/numeric.rb +8 -3
  28. data/lib/active_model/type/helpers/time_value.rb +27 -17
  29. data/lib/active_model/type/helpers/timezone.rb +1 -1
  30. data/lib/active_model/type/immutable_string.rb +14 -10
  31. data/lib/active_model/type/integer.rb +11 -2
  32. data/lib/active_model/type/registry.rb +12 -9
  33. data/lib/active_model/type/string.rb +12 -2
  34. data/lib/active_model/type/value.rb +9 -1
  35. data/lib/active_model/type.rb +3 -2
  36. data/lib/active_model/validations/absence.rb +1 -1
  37. data/lib/active_model/validations/acceptance.rb +1 -1
  38. data/lib/active_model/validations/callbacks.rb +15 -15
  39. data/lib/active_model/validations/clusivity.rb +5 -1
  40. data/lib/active_model/validations/confirmation.rb +2 -2
  41. data/lib/active_model/validations/exclusion.rb +1 -1
  42. data/lib/active_model/validations/format.rb +2 -2
  43. data/lib/active_model/validations/inclusion.rb +1 -1
  44. data/lib/active_model/validations/length.rb +2 -2
  45. data/lib/active_model/validations/numericality.rb +54 -41
  46. data/lib/active_model/validations/presence.rb +1 -1
  47. data/lib/active_model/validations/validates.rb +6 -4
  48. data/lib/active_model/validations.rb +6 -6
  49. data/lib/active_model/validator.rb +7 -1
  50. data/lib/active_model.rb +2 -1
  51. metadata +9 -7
@@ -8,9 +8,9 @@ module ActiveModel
8
8
 
9
9
  module VERSION
10
10
  MAJOR = 6
11
- MINOR = 0
12
- TINY = 5
13
- PRE = "1"
11
+ MINOR = 1
12
+ TINY = 7
13
+ PRE = "4"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -101,7 +101,7 @@ module ActiveModel
101
101
  # locale. If no error is present, the method should return an empty array.
102
102
  def test_errors_aref
103
103
  assert_respond_to model, :errors
104
- assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
104
+ assert_equal [], model.errors[:hello], "errors#[] should return an empty Array"
105
105
  end
106
106
 
107
107
  private
@@ -8,7 +8,7 @@ module ActiveModel
8
8
  class Name
9
9
  include Comparable
10
10
 
11
- attr_reader :singular, :plural, :element, :collection,
11
+ attr_accessor :singular, :plural, :element, :collection,
12
12
  :singular_route_key, :route_key, :param_key, :i18n_key,
13
13
  :name
14
14
 
@@ -166,7 +166,7 @@ module ActiveModel
166
166
 
167
167
  raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
168
168
 
169
- @unnamespaced = @name.sub(/^#{namespace.name}::/, "") if namespace
169
+ @unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
170
170
  @klass = klass
171
171
  @singular = _singularize(@name)
172
172
  @plural = ActiveSupport::Inflector.pluralize(@singular)
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/error"
4
+ require "forwardable"
5
+
6
+ module ActiveModel
7
+ class NestedError < Error
8
+ def initialize(base, inner_error, override_options = {})
9
+ @base = base
10
+ @inner_error = inner_error
11
+ @attribute = override_options.fetch(:attribute) { inner_error.attribute }
12
+ @type = override_options.fetch(:type) { inner_error.type }
13
+ @raw_type = inner_error.raw_type
14
+ @options = inner_error.options
15
+ end
16
+
17
+ attr_reader :inner_error
18
+
19
+ extend Forwardable
20
+ def_delegators :@inner_error, :message
21
+ end
22
+ end
@@ -14,7 +14,7 @@ module ActiveModel
14
14
  end
15
15
 
16
16
  initializer "active_model.i18n_customize_full_message" do
17
- ActiveModel::Errors.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false
17
+ ActiveModel::Error.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false
18
18
  end
19
19
  end
20
20
  end
@@ -45,19 +45,19 @@ module ActiveModel
45
45
  # end
46
46
  #
47
47
  # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
48
- # user.save # => false, password required
48
+ # user.save # => false, password required
49
49
  # user.password = 'mUc3m00RsqyRe'
50
- # user.save # => false, confirmation doesn't match
50
+ # user.save # => false, confirmation doesn't match
51
51
  # user.password_confirmation = 'mUc3m00RsqyRe'
52
- # user.save # => true
52
+ # user.save # => true
53
53
  # user.recovery_password = "42password"
54
- # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
55
- # user.save # => true
56
- # user.authenticate('notright') # => false
57
- # user.authenticate('mUc3m00RsqyRe') # => user
58
- # user.authenticate_recovery_password('42password') # => user
59
- # User.find_by(name: 'david').try(:authenticate, 'notright') # => false
60
- # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
54
+ # user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uXkTG65HR.qpW.bNhEjFP3ftli3o5DQC"
55
+ # user.save # => true
56
+ # user.authenticate('notright') # => false
57
+ # user.authenticate('mUc3m00RsqyRe') # => user
58
+ # user.authenticate_recovery_password('42password') # => user
59
+ # User.find_by(name: 'david')&.authenticate('notright') # => false
60
+ # User.find_by(name: 'david')&.authenticate('mUc3m00RsqyRe') # => user
61
61
  def has_secure_password(attribute = :password, validations: true)
62
62
  # Load bcrypt gem only when has_secure_password is used.
63
63
  # This is to avoid ActiveModel (and by extension the entire framework)
@@ -79,7 +79,7 @@ module ActiveModel
79
79
  # when there is an error, the message is added to the password attribute instead
80
80
  # so that the error message will make sense to the end-user.
81
81
  validate do |record|
82
- record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
82
+ record.errors.add(attribute, :blank) unless record.public_send("#{attribute}_digest").present?
83
83
  end
84
84
 
85
85
  validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
@@ -94,11 +94,12 @@ module ActiveModel
94
94
 
95
95
  define_method("#{attribute}=") do |unencrypted_password|
96
96
  if unencrypted_password.nil?
97
- self.send("#{attribute}_digest=", nil)
97
+ instance_variable_set("@#{attribute}", nil)
98
+ self.public_send("#{attribute}_digest=", nil)
98
99
  elsif !unencrypted_password.empty?
99
100
  instance_variable_set("@#{attribute}", unencrypted_password)
100
101
  cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
101
- self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
102
+ self.public_send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
102
103
  end
103
104
  end
104
105
 
@@ -117,7 +118,7 @@ module ActiveModel
117
118
  # user.authenticate_password('notright') # => false
118
119
  # user.authenticate_password('mUc3m00RsqyRe') # => user
119
120
  define_method("authenticate_#{attribute}") do |unencrypted_password|
120
- attribute_digest = send("#{attribute}_digest")
121
+ attribute_digest = public_send("#{attribute}_digest")
121
122
  BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
122
123
  end
123
124
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/hash/except"
4
- require "active_support/core_ext/hash/slice"
3
+ require "active_support/core_ext/enumerable"
5
4
 
6
5
  module ActiveModel
7
6
  # == Active \Model \Serialization
@@ -124,17 +123,17 @@ module ActiveModel
124
123
  # user.serializable_hash(include: { notes: { only: 'title' }})
125
124
  # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
126
125
  def serializable_hash(options = nil)
127
- options ||= {}
128
-
129
126
  attribute_names = attributes.keys
127
+
128
+ return serializable_attributes(attribute_names) if options.blank?
129
+
130
130
  if only = options[:only]
131
131
  attribute_names &= Array(only).map(&:to_s)
132
132
  elsif except = options[:except]
133
133
  attribute_names -= Array(except).map(&:to_s)
134
134
  end
135
135
 
136
- hash = {}
137
- attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
136
+ hash = serializable_attributes(attribute_names)
138
137
 
139
138
  Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
140
139
 
@@ -168,6 +167,10 @@ module ActiveModel
168
167
  # end
169
168
  alias :read_attribute_for_serialization :send
170
169
 
170
+ def serializable_attributes(attribute_names)
171
+ attribute_names.index_with { |n| read_attribute_for_serialization(n) }
172
+ end
173
+
171
174
  # Add associations specified via the <tt>:include</tt> option.
172
175
  #
173
176
  # Expects a block that takes as arguments:
@@ -42,6 +42,13 @@ module ActiveModel
42
42
  # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
43
43
  # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
44
44
  #
45
+ # If you prefer, <tt>:root</tt> may also be set to a custom string key instead as in:
46
+ #
47
+ # user = User.find(1)
48
+ # user.as_json(root: "author")
49
+ # # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
50
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
51
+ #
45
52
  # Without any +options+, the returned Hash will include all the model's
46
53
  # attributes.
47
54
  #
@@ -35,8 +35,8 @@ module ActiveModel
35
35
  end
36
36
 
37
37
  def value_from_multiparameter_assignment(values_hash)
38
- missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
39
- if missing_parameters.any?
38
+ missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
39
+ unless missing_parameters.empty?
40
40
  raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
41
41
  end
42
42
  super
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+
3
5
  module ActiveModel
4
6
  module Type
5
7
  class Float < Value # :nodoc:
@@ -4,12 +4,12 @@ module ActiveModel
4
4
  module Type
5
5
  module Helpers # :nodoc: all
6
6
  class AcceptsMultiparameterTime < Module
7
- def initialize(defaults: {})
8
- define_method(:serialize) do |value|
7
+ module InstanceMethods
8
+ def serialize(value)
9
9
  super(cast(value))
10
10
  end
11
11
 
12
- define_method(:cast) do |value|
12
+ def cast(value)
13
13
  if value.is_a?(Hash)
14
14
  value_from_multiparameter_assignment(value)
15
15
  else
@@ -17,7 +17,7 @@ module ActiveModel
17
17
  end
18
18
  end
19
19
 
20
- define_method(:assert_valid_value) do |value|
20
+ def assert_valid_value(value)
21
21
  if value.is_a?(Hash)
22
22
  value_from_multiparameter_assignment(value)
23
23
  else
@@ -25,17 +25,21 @@ module ActiveModel
25
25
  end
26
26
  end
27
27
 
28
- define_method(:value_constructed_by_mass_assignment?) do |value|
28
+ def value_constructed_by_mass_assignment?(value)
29
29
  value.is_a?(Hash)
30
30
  end
31
+ end
32
+
33
+ def initialize(defaults: {})
34
+ include InstanceMethods
31
35
 
32
36
  define_method(:value_from_multiparameter_assignment) do |values_hash|
33
37
  defaults.each do |k, v|
34
38
  values_hash[k] ||= v
35
39
  end
36
40
  return unless values_hash[1] && values_hash[2] && values_hash[3]
37
- values = values_hash.sort.map(&:last)
38
- ::Time.send(default_timezone, *values)
41
+ values = values_hash.sort.map!(&:last)
42
+ ::Time.public_send(default_timezone, *values)
39
43
  end
40
44
  private :value_from_multiparameter_assignment
41
45
  end
@@ -9,13 +9,18 @@ module ActiveModel
9
9
  end
10
10
 
11
11
  def cast(value)
12
- value = \
12
+ # Checks whether the value is numeric. Spaceship operator
13
+ # will return nil if value is not numeric.
14
+ value = if value <=> 0
15
+ value
16
+ else
13
17
  case value
14
18
  when true then 1
15
19
  when false then 0
16
- when ::String then value.presence
17
- else value
20
+ else value.presence
18
21
  end
22
+ end
23
+
19
24
  super(value)
20
25
  end
21
26
 
@@ -11,10 +11,10 @@ module ActiveModel
11
11
  value = apply_seconds_precision(value)
12
12
 
13
13
  if value.acts_like?(:time)
14
- zone_conversion_method = is_utc? ? :getutc : :getlocal
15
-
16
- if value.respond_to?(zone_conversion_method)
17
- value = value.send(zone_conversion_method)
14
+ if is_utc?
15
+ value = value.getutc if value.respond_to?(:getutc) && !value.utc?
16
+ else
17
+ value = value.getlocal if value.respond_to?(:getlocal)
18
18
  end
19
19
  end
20
20
 
@@ -52,27 +52,37 @@ module ActiveModel
52
52
  time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
53
53
  return unless time
54
54
 
55
- time -= offset
55
+ time -= offset unless offset == 0
56
56
  is_utc? ? time : time.getlocal
57
+ elsif is_utc?
58
+ ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
57
59
  else
58
- ::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
60
+ ::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil
59
61
  end
60
62
  end
61
63
 
62
- ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
64
+ ISO_DATETIME = /
65
+ \A
66
+ (\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T
67
+ (\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456
68
+ (?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00
69
+ \z
70
+ /x
63
71
 
64
- # Doesn't handle time zones.
65
72
  def fast_string_to_time(string)
66
- if string =~ ISO_DATETIME
67
- microsec_part = $7
68
- if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
69
- microsec_part[0] = ""
70
- microsec = microsec_part.to_i
71
- else
72
- microsec = (microsec_part.to_r * 1_000_000).to_i
73
- end
74
- new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
73
+ return unless ISO_DATETIME =~ string
74
+
75
+ usec = $7.to_i
76
+ usec_len = $7&.length
77
+ if usec_len&.< 6
78
+ usec *= 10**(6 - usec_len)
75
79
  end
80
+
81
+ if $8
82
+ offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
83
+ end
84
+
85
+ new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
76
86
  end
77
87
  end
78
88
  end
@@ -7,7 +7,7 @@ module ActiveModel
7
7
  module Helpers # :nodoc: all
8
8
  module Timezone
9
9
  def is_utc?
10
- ::Time.zone_default.nil? || ::Time.zone_default =~ "UTC"
10
+ ::Time.zone_default.nil? || ::Time.zone_default.match?("UTC")
11
11
  end
12
12
 
13
13
  def default_timezone
@@ -3,28 +3,32 @@
3
3
  module ActiveModel
4
4
  module Type
5
5
  class ImmutableString < Value # :nodoc:
6
+ def initialize(**args)
7
+ @true = -(args.delete(:true)&.to_s || "t")
8
+ @false = -(args.delete(:false)&.to_s || "f")
9
+ super
10
+ end
11
+
6
12
  def type
7
13
  :string
8
14
  end
9
15
 
10
16
  def serialize(value)
11
17
  case value
12
- when ::Numeric, ActiveSupport::Duration then value.to_s
13
- when true then "t"
14
- when false then "f"
18
+ when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s
19
+ when true then @true
20
+ when false then @false
15
21
  else super
16
22
  end
17
23
  end
18
24
 
19
25
  private
20
26
  def cast_value(value)
21
- result = \
22
- case value
23
- when true then "t"
24
- when false then "f"
25
- else value.to_s
26
- end
27
- result.freeze
27
+ case value
28
+ when true then @true
29
+ when false then @false
30
+ else value.to_s.freeze
31
+ end
28
32
  end
29
33
  end
30
34
  end
@@ -9,7 +9,7 @@ module ActiveModel
9
9
  # 4 bytes means an integer as opposed to smallint etc.
10
10
  DEFAULT_LIMIT = 4
11
11
 
12
- def initialize(*, **)
12
+ def initialize(**)
13
13
  super
14
14
  @range = min_value...max_value
15
15
  end
@@ -28,15 +28,24 @@ module ActiveModel
28
28
  ensure_in_range(super)
29
29
  end
30
30
 
31
+ def serializable?(value)
32
+ cast_value = cast(value)
33
+ in_range?(cast_value) && super
34
+ end
35
+
31
36
  private
32
37
  attr_reader :range
33
38
 
39
+ def in_range?(value)
40
+ !value || range.member?(value)
41
+ end
42
+
34
43
  def cast_value(value)
35
44
  value.to_i rescue nil
36
45
  end
37
46
 
38
47
  def ensure_in_range(value)
39
- if value && !range.cover?(value)
48
+ unless in_range?(value)
40
49
  raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
41
50
  end
42
51
  value
@@ -8,6 +8,11 @@ module ActiveModel
8
8
  @registrations = []
9
9
  end
10
10
 
11
+ def initialize_dup(other)
12
+ @registrations = @registrations.dup
13
+ super
14
+ end
15
+
11
16
  def register(type_name, klass = nil, **options, &block)
12
17
  unless block_given?
13
18
  block = proc { |_, *args| klass.new(*args) }
@@ -16,15 +21,16 @@ module ActiveModel
16
21
  registrations << registration_klass.new(type_name, block, **options)
17
22
  end
18
23
 
19
- def lookup(symbol, *args, **kwargs)
20
- registration = find_registration(symbol, *args, **kwargs)
24
+ def lookup(symbol, *args)
25
+ registration = find_registration(symbol, *args)
21
26
 
22
27
  if registration
23
- registration.call(self, symbol, *args, **kwargs)
28
+ registration.call(self, symbol, *args)
24
29
  else
25
30
  raise ArgumentError, "Unknown type #{symbol.inspect}"
26
31
  end
27
32
  end
33
+ ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
28
34
 
29
35
  private
30
36
  attr_reader :registrations
@@ -45,13 +51,10 @@ module ActiveModel
45
51
  @block = block
46
52
  end
47
53
 
48
- def call(_registry, *args, **kwargs)
49
- if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
50
- block.call(*args, **kwargs)
51
- else
52
- block.call(*args)
53
- end
54
+ def call(_registry, *args)
55
+ block.call(*args)
54
56
  end
57
+ ruby2_keywords(:call) if respond_to?(:ruby2_keywords, true)
55
58
 
56
59
  def matches?(type_name, *args, **kwargs)
57
60
  type_name == name
@@ -11,12 +11,22 @@ module ActiveModel
11
11
  end
12
12
  end
13
13
 
14
+ def to_immutable_string
15
+ ImmutableString.new(
16
+ true: @true,
17
+ false: @false,
18
+ limit: limit,
19
+ precision: precision,
20
+ scale: scale,
21
+ )
22
+ end
23
+
14
24
  private
15
25
  def cast_value(value)
16
26
  case value
17
27
  when ::String then ::String.new(value)
18
- when true then "t"
19
- when false then "f"
28
+ when true then @true
29
+ when false then @false
20
30
  else value.to_s
21
31
  end
22
32
  end
@@ -11,6 +11,14 @@ module ActiveModel
11
11
  @limit = limit
12
12
  end
13
13
 
14
+ # Returns true if this type can convert +value+ to a type that is usable
15
+ # by the database. For example a boolean type can return +true+ if the
16
+ # value parameter is a Ruby boolean, but may return +false+ if the value
17
+ # parameter is some other object.
18
+ def serializable?(value)
19
+ true
20
+ end
21
+
14
22
  def type # :nodoc:
15
23
  end
16
24
 
@@ -110,7 +118,7 @@ module ActiveModel
110
118
  [self.class, precision, scale, limit].hash
111
119
  end
112
120
 
113
- def assert_valid_value(*)
121
+ def assert_valid_value(_)
114
122
  end
115
123
 
116
124
  private
@@ -29,9 +29,10 @@ module ActiveModel
29
29
  registry.register(type_name, klass, **options, &block)
30
30
  end
31
31
 
32
- def lookup(*args, **kwargs) # :nodoc:
33
- registry.lookup(*args, **kwargs)
32
+ def lookup(*args) # :nodoc:
33
+ registry.lookup(*args)
34
34
  end
35
+ ruby2_keywords(:lookup) if respond_to?(:ruby2_keywords, true)
35
36
 
36
37
  def default_value # :nodoc:
37
38
  @default_value ||= Value.new
@@ -5,7 +5,7 @@ module ActiveModel
5
5
  # == \Active \Model Absence Validator
6
6
  class AbsenceValidator < EachValidator #:nodoc:
7
7
  def validate_each(record, attr_name, value)
8
- record.errors.add(attr_name, :present, options) if value.present?
8
+ record.errors.add(attr_name, :present, **options) if value.present?
9
9
  end
10
10
  end
11
11
 
@@ -10,7 +10,7 @@ module ActiveModel
10
10
 
11
11
  def validate_each(record, attribute, value)
12
12
  unless acceptable_option?(value)
13
- record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
13
+ record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
14
14
  end
15
15
  end
16
16
 
@@ -56,14 +56,7 @@ module ActiveModel
56
56
  def before_validation(*args, &block)
57
57
  options = args.extract_options!
58
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
59
+ set_options_for_callback(options)
67
60
 
68
61
  set_callback(:validation, :before, *args, options, &block)
69
62
  end
@@ -99,16 +92,23 @@ module ActiveModel
99
92
  options = options.dup
100
93
  options[:prepend] = true
101
94
 
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
95
+ set_options_for_callback(options)
109
96
 
110
97
  set_callback(:validation, :after, *args, options, &block)
111
98
  end
99
+
100
+ private
101
+ def set_options_for_callback(options)
102
+ if options.key?(:on)
103
+ options[:on] = Array(options[:on])
104
+ options[:if] = [
105
+ ->(o) {
106
+ !(options[:on] & Array(o.validation_context)).empty?
107
+ },
108
+ *options[:if]
109
+ ]
110
+ end
111
+ end
112
112
  end
113
113
 
114
114
  private
@@ -24,7 +24,11 @@ module ActiveModel
24
24
  delimiter
25
25
  end
26
26
 
27
- members.send(inclusion_method(members), value)
27
+ if value.is_a?(Array)
28
+ value.all? { |v| members.public_send(inclusion_method(members), v) }
29
+ else
30
+ members.public_send(inclusion_method(members), value)
31
+ end
28
32
  end
29
33
 
30
34
  def delimiter
@@ -9,10 +9,10 @@ module ActiveModel
9
9
  end
10
10
 
11
11
  def validate_each(record, attribute, value)
12
- unless (confirmed = record.send("#{attribute}_confirmation")).nil?
12
+ unless (confirmed = record.public_send("#{attribute}_confirmation")).nil?
13
13
  unless confirmation_value_equal?(record, attribute, value, confirmed)
14
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))
15
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, **options.except(:case_sensitive).merge!(attribute: human_attribute_name))
16
16
  end
17
17
  end
18
18
  end