activemodel 5.2.7.1 → 6.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -158
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_model/attribute/user_provided_default.rb +1 -2
  6. data/lib/active_model/attribute.rb +3 -4
  7. data/lib/active_model/attribute_assignment.rb +1 -1
  8. data/lib/active_model/attribute_methods.rb +39 -1
  9. data/lib/active_model/attribute_mutation_tracker.rb +1 -6
  10. data/lib/active_model/attribute_set/builder.rb +1 -3
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
  12. data/lib/active_model/attribute_set.rb +2 -10
  13. data/lib/active_model/attributes.rb +10 -22
  14. data/lib/active_model/callbacks.rb +10 -7
  15. data/lib/active_model/conversion.rb +1 -1
  16. data/lib/active_model/dirty.rb +2 -2
  17. data/lib/active_model/errors.rb +90 -11
  18. data/lib/active_model/gem_version.rb +4 -4
  19. data/lib/active_model/naming.rb +19 -3
  20. data/lib/active_model/railtie.rb +6 -0
  21. data/lib/active_model/secure_password.rb +48 -55
  22. data/lib/active_model/serializers/json.rb +10 -9
  23. data/lib/active_model/type/binary.rb +1 -1
  24. data/lib/active_model/type/boolean.rb +1 -10
  25. data/lib/active_model/type/date.rb +1 -2
  26. data/lib/active_model/type/date_time.rb +3 -4
  27. data/lib/active_model/type/decimal.rb +4 -0
  28. data/lib/active_model/type/helpers/time_value.rb +19 -1
  29. data/lib/active_model/type/helpers.rb +0 -1
  30. data/lib/active_model/type/integer.rb +1 -6
  31. data/lib/active_model/type/registry.rb +2 -10
  32. data/lib/active_model/type/string.rb +2 -2
  33. data/lib/active_model/type/time.rb +0 -5
  34. data/lib/active_model/validations/acceptance.rb +4 -8
  35. data/lib/active_model/validations/clusivity.rb +1 -1
  36. data/lib/active_model/validations/confirmation.rb +2 -2
  37. data/lib/active_model/validations/inclusion.rb +1 -1
  38. data/lib/active_model/validations/numericality.rb +9 -6
  39. data/lib/active_model/validations/validates.rb +2 -2
  40. data/lib/active_model/validations.rb +0 -2
  41. data/lib/active_model/validator.rb +1 -1
  42. data/lib/active_model.rb +1 -1
  43. metadata +13 -14
  44. data/lib/active_model/type/helpers/timezone.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00ff74e146a582069a06258fe8b5d5e3457340d958590ef6f9f7a5d8bbd3c75b
4
- data.tar.gz: 1f00d19269dd49aec1a2db891d62595e7e45d3c4bf2c42cdfc6e583fccbb8894
3
+ metadata.gz: 69b55e1af8331655f54dbced151a75b1ccbd7e48757fc4a3fd1ee2e5f2236d5a
4
+ data.tar.gz: e756ba5fc97ccd6ee212f32568e58e372b6e94d79082c81711b8e39ac6d86a3a
5
5
  SHA512:
6
- metadata.gz: 3ceb7660baf4f321a0b67027113594906977d13b9bf4e3c09b353c6274a744f86c6d5acf28190c528445805ba8e89860c691348067e0b1f29b86b5c9c13ac883
7
- data.tar.gz: 9d8ef0e06a870222ea59e12cb15e4950515bd7f2265d814fa6b6bfaa4b976a565e92060b60d424f88cfe172955a0b20ec1d17c1c9671360f400e171096164880
6
+ metadata.gz: 394b1945a1c68549337a884d68985f0e15c2cbef0e0f968ee50ab55076327e6d55558d9d884d580b7359ea87f460c9a31211090f621f919b3967aa2e9b93188d
7
+ data.tar.gz: 0e1e72d58afc1f7b3f572276c9efa4b21586fa4741d8e31ab2c3bf7bb19e98888f571731d4b5dc04c0ae5f76e5e6c8ee4cf481458eef8e1669c03445277317ec
data/CHANGELOG.md CHANGED
@@ -1,108 +1,17 @@
1
- ## Rails 5.2.7.1 (April 26, 2022) ##
1
+ ## Rails 6.0.0.beta1 (January 18, 2019) ##
2
2
 
3
- * No changes.
3
+ * Add `ActiveModel::Errors#of_kind?`.
4
4
 
5
-
6
- ## Rails 5.2.7 (March 10, 2022) ##
7
-
8
- * No changes.
9
-
10
-
11
- ## Rails 5.2.6.3 (March 08, 2022) ##
12
-
13
- * No changes.
14
-
15
-
16
- ## Rails 5.2.6.2 (February 11, 2022) ##
17
-
18
- * No changes.
19
-
20
-
21
- ## Rails 5.2.6.1 (February 11, 2022) ##
22
-
23
- * No changes.
24
-
25
-
26
- ## Rails 5.2.6 (May 05, 2021) ##
27
-
28
- * No changes.
29
-
30
-
31
- ## Rails 5.2.5 (March 26, 2021) ##
32
-
33
- * No changes.
34
-
35
-
36
- ## Rails 5.2.4.6 (May 05, 2021) ##
37
-
38
- * No changes.
39
-
40
-
41
- ## Rails 5.2.4.5 (February 10, 2021) ##
42
-
43
- * No changes.
44
-
45
-
46
- ## Rails 5.2.4.4 (September 09, 2020) ##
47
-
48
- * No changes.
49
-
50
-
51
- ## Rails 5.2.4.3 (May 18, 2020) ##
52
-
53
- * No changes.
54
-
55
-
56
- ## Rails 5.2.4.2 (March 19, 2020) ##
57
-
58
- * No changes.
59
-
60
-
61
- ## Rails 5.2.4.1 (December 18, 2019) ##
62
-
63
- * No changes.
64
-
65
-
66
- ## Rails 5.2.4 (November 27, 2019) ##
67
-
68
- * Type cast falsy boolean symbols on boolean attribute as false.
69
-
70
- Fixes #35676.
71
-
72
- *Ryuta Kamizono*
73
-
74
-
75
- ## Rails 5.2.3 (March 27, 2019) ##
76
-
77
- * Fix date value when casting a multiparameter date hash to not convert
78
- from Gregorian date to Julian date.
79
-
80
- Before:
81
-
82
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
83
- => #<Day id: nil, day: "0001-01-03", created_at: nil, updated_at: nil>
84
-
85
- After:
86
-
87
- Day.new({"day(1i)"=>"1", "day(2i)"=>"1", "day(3i)"=>"1"})
88
- => #<Day id: nil, day: "0001-01-01", created_at: nil, updated_at: nil>
89
-
90
- Fixes #28521.
91
-
92
- *Sayan Chakraborty*
5
+ *bogdanvlviv*, *Rafael Mendonça França*
93
6
 
94
7
  * Fix numericality equality validation of `BigDecimal` and `Float`
95
8
  by casting to `BigDecimal` on both ends of the validation.
96
9
 
97
10
  *Gannon McGibbon*
98
11
 
12
+ * Add `#slice!` method to `ActiveModel::Errors`.
99
13
 
100
- ## Rails 5.2.2.1 (March 11, 2019) ##
101
-
102
- * No changes.
103
-
104
-
105
- ## Rails 5.2.2 (December 04, 2018) ##
14
+ *Daniel López Prat*
106
15
 
107
16
  * Fix numericality validator to still use value before type cast except Active Record.
108
17
 
@@ -110,79 +19,50 @@
110
19
 
111
20
  *Ryuta Kamizono*
112
21
 
22
+ * Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps.
113
23
 
114
- ## Rails 5.2.1.1 (November 27, 2018) ##
115
-
116
- * No changes.
117
-
118
-
119
- ## Rails 5.2.1 (August 07, 2018) ##
120
-
121
- * No changes.
122
-
123
-
124
- ## Rails 5.2.0 (April 09, 2018) ##
125
-
126
- * Do not lose all multiple `:includes` with options in serialization.
127
-
128
- *Mike Mangino*
129
-
130
- * Models using the attributes API with a proc default can now be marshalled.
131
-
132
- Fixes #31216.
133
-
134
- *Sean Griffin*
135
-
136
- * Fix to working before/after validation callbacks on multiple contexts.
137
-
138
- *Yoshiyuki Hirano*
24
+ Before:
25
+ ```
26
+ contact = Contact.new(created_at: Time.utc(2006, 8, 1))
27
+ contact.as_json["created_at"] # => 2006-08-01 00:00:00 UTC
28
+ ```
139
29
 
140
- * Execute `ConfirmationValidator` validation when `_confirmation`'s value is `false`.
30
+ After:
31
+ ```
32
+ contact = Contact.new(created_at: Time.utc(2006, 8, 1))
33
+ contact.as_json["created_at"] # => "2006-08-01T00:00:00.000Z"
34
+ ```
141
35
 
142
- *bogdanvlviv*
36
+ *Bogdan Gusiev*
143
37
 
144
- * Allow passing a Proc or Symbol to length validator options.
38
+ * Allows configurable attribute name for `#has_secure_password`. This
39
+ still defaults to an attribute named 'password', causing no breaking
40
+ change. There is a new method `#authenticate_XXX` where XXX is the
41
+ configured attribute name, making the existing `#authenticate` now an
42
+ alias for this when the attribute is the default 'password'.
145
43
 
146
- *Matt Rohrer*
44
+ Example:
147
45
 
148
- * Add method `#merge!` for `ActiveModel::Errors`.
46
+ class User < ActiveRecord::Base
47
+ has_secure_password :recovery_password, validations: false
48
+ end
149
49
 
150
- *Jahfer Husain*
50
+ user = User.new()
51
+ user.recovery_password = "42password"
52
+ user.recovery_password_digest # => "$2a$04$iOfhwahFymCs5weB3BNH/uX..."
53
+ user.authenticate_recovery_password('42password') # => user
151
54
 
152
- * Fix regression in numericality validator when comparing Decimal and Float input
153
- values with more scale than the schema.
55
+ *Unathi Chonco*
154
56
 
155
- *Bradley Priest*
57
+ * Add `config.active_model.i18n_full_message` in order to control whether
58
+ the `full_message` error format can be overridden at the attribute or model
59
+ level in the locale files. This is `false` by default.
156
60
 
157
- * Fix methods `#keys`, `#values` in `ActiveModel::Errors`.
61
+ *Martin Larochelle*
158
62
 
159
- Change `#keys` to only return the keys that don't have empty messages.
63
+ * Rails 6 requires Ruby 2.5.0 or newer.
160
64
 
161
- Change `#values` to only return the not empty values.
65
+ *Jeremy Daer*, *Kasper Timm Hansen*
162
66
 
163
- Example:
164
67
 
165
- # Before
166
- person = Person.new
167
- person.errors.keys # => []
168
- person.errors.values # => []
169
- person.errors.messages # => {}
170
- person.errors[:name] # => []
171
- person.errors.messages # => {:name => []}
172
- person.errors.keys # => [:name]
173
- person.errors.values # => [[]]
174
-
175
- # After
176
- person = Person.new
177
- person.errors.keys # => []
178
- person.errors.values # => []
179
- person.errors.messages # => {}
180
- person.errors[:name] # => []
181
- person.errors.messages # => {:name => []}
182
- person.errors.keys # => []
183
- person.errors.values # => []
184
-
185
- *bogdanvlviv*
186
-
187
-
188
- Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activemodel/CHANGELOG.md) for previous changes.
68
+ Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2018 David Heinemeier Hansson
1
+ Copyright (c) 2004-2019 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -239,7 +239,7 @@ The latest version of Active Model can be installed with RubyGems:
239
239
 
240
240
  Source code can be downloaded as part of the Rails project on GitHub
241
241
 
242
- * https://github.com/rails/rails/tree/5-2-stable/activemodel
242
+ * https://github.com/rails/rails/tree/master/activemodel
243
243
 
244
244
 
245
245
  == License
@@ -44,8 +44,7 @@ module ActiveModel
44
44
  end
45
45
  end
46
46
 
47
- protected
48
-
47
+ private
49
48
  attr_reader :user_provided_value
50
49
  end
51
50
  end
@@ -133,10 +133,6 @@ module ActiveModel
133
133
  end
134
134
 
135
135
  protected
136
-
137
- attr_reader :original_attribute
138
- alias_method :assigned?, :original_attribute
139
-
140
136
  def original_value_for_database
141
137
  if assigned?
142
138
  original_attribute.original_value_for_database
@@ -146,6 +142,9 @@ module ActiveModel
146
142
  end
147
143
 
148
144
  private
145
+ attr_reader :original_attribute
146
+ alias :assigned? :original_attribute
147
+
149
148
  def initialize_dup(other)
150
149
  if defined?(@value) && @value.duplicable?
151
150
  @value = @value.dup
@@ -27,7 +27,7 @@ module ActiveModel
27
27
  # cat.status # => 'sleeping'
28
28
  def assign_attributes(new_attributes)
29
29
  if !new_attributes.respond_to?(:stringify_keys)
30
- raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
30
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
31
31
  end
32
32
  return if new_attributes.empty?
33
33
 
@@ -369,7 +369,7 @@ module ActiveModel
369
369
  "define_method(:'#{name}') do |*args|"
370
370
  end
371
371
 
372
- extra = (extra.map!(&:inspect) << "*args").join(", ".freeze)
372
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
373
373
 
374
374
  target = if CALL_COMPILABLE_REGEXP.match?(send)
375
375
  "#{"self." unless include_private}#{send}(#{extra})"
@@ -474,5 +474,43 @@ module ActiveModel
474
474
  def _read_attribute(attr)
475
475
  __send__(attr)
476
476
  end
477
+
478
+ module AttrNames # :nodoc:
479
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
480
+
481
+ # We want to generate the methods via module_eval rather than
482
+ # define_method, because define_method is slower on dispatch.
483
+ # Evaluating many similar methods may use more memory as the instruction
484
+ # sequences are duplicated and cached (in MRI). define_method may
485
+ # be slower on dispatch, but if you're careful about the closure
486
+ # created, then define_method will consume much less memory.
487
+ #
488
+ # But sometimes the database might return columns with
489
+ # characters that are not allowed in normal method names (like
490
+ # 'my_column(omg)'. So to work around this we first define with
491
+ # the __temp__ identifier, and then use alias method to rename
492
+ # it to what we want.
493
+ #
494
+ # We are also defining a constant to hold the frozen string of
495
+ # the attribute name. Using a constant means that we do not have
496
+ # to allocate an object on each call to the attribute method.
497
+ # Making it frozen means that it doesn't get duped when used to
498
+ # key the @attributes in read_attribute.
499
+ def self.define_attribute_accessor_method(mod, attr_name, writer: false)
500
+ method_name = "#{attr_name}#{'=' if writer}"
501
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
502
+ yield method_name, "'#{attr_name}'.freeze"
503
+ else
504
+ safe_name = attr_name.unpack1("h*")
505
+ const_name = "ATTR_#{safe_name}"
506
+ const_set(const_name, attr_name) unless const_defined?(const_name)
507
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
508
+ attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}"
509
+ yield temp_method_name, attr_name_expr
510
+ mod.alias_method method_name, temp_method_name
511
+ mod.undef_method temp_method_name
512
+ end
513
+ end
514
+ end
477
515
  end
478
516
  end
@@ -69,13 +69,8 @@ module ActiveModel
69
69
  forced_changes << attr_name.to_s
70
70
  end
71
71
 
72
- # TODO Change this to private once we've dropped Ruby 2.2 support.
73
- # Workaround for Ruby 2.2 "private attribute?" warning.
74
- protected
75
-
76
- attr_reader :attributes, :forced_changes
77
-
78
72
  private
73
+ attr_reader :attributes, :forced_changes
79
74
 
80
75
  def attr_names
81
76
  attributes.keys
@@ -90,9 +90,6 @@ module ActiveModel
90
90
  end
91
91
 
92
92
  protected
93
-
94
- attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
95
-
96
93
  def materialize
97
94
  unless @materialized
98
95
  values.each_key { |key| self[key] }
@@ -105,6 +102,7 @@ module ActiveModel
105
102
  end
106
103
 
107
104
  private
105
+ attr_reader :types, :values, :additional_types, :delegate_hash, :default_attributes
108
106
 
109
107
  def assign_default_value(name)
110
108
  type = additional_types.fetch(name, types[name])
@@ -33,8 +33,7 @@ module ActiveModel
33
33
  end
34
34
  end
35
35
 
36
- protected
37
-
36
+ private
38
37
  attr_reader :default_types
39
38
  end
40
39
  end
@@ -37,16 +37,8 @@ module ActiveModel
37
37
  attributes.each_key.select { |name| self[name].initialized? }
38
38
  end
39
39
 
40
- if defined?(JRUBY_VERSION)
41
- # This form is significantly faster on JRuby, and this is one of our biggest hotspots.
42
- # https://github.com/jruby/jruby/pull/2562
43
- def fetch_value(name, &block)
44
- self[name].value(&block)
45
- end
46
- else
47
- def fetch_value(name)
48
- self[name].value { |n| yield n if block_given? }
49
- end
40
+ def fetch_value(name, &block)
41
+ self[name].value(&block)
50
42
  end
51
43
 
52
44
  def write_from_database(name, value)
@@ -29,17 +29,16 @@ module ActiveModel
29
29
  private
30
30
 
31
31
  def define_method_attribute=(name)
32
- safe_name = name.unpack("h*".freeze).first
33
- ActiveModel::AttributeMethods::AttrNames.set_name_cache safe_name, name
34
-
35
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
36
- def __temp__#{safe_name}=(value)
37
- name = ::ActiveModel::AttributeMethods::AttrNames::ATTR_#{safe_name}
38
- write_attribute(name, value)
39
- end
40
- alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
41
- undef_method :__temp__#{safe_name}=
42
- STR
32
+ ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
33
+ generated_attribute_methods, name, writer: true,
34
+ ) do |temp_method_name, attr_name_expr|
35
+ generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
36
+ def #{temp_method_name}(value)
37
+ name = #{attr_name_expr}
38
+ write_attribute(name, value)
39
+ end
40
+ RUBY
41
+ end
43
42
  end
44
43
 
45
44
  NO_DEFAULT_PROVIDED = Object.new # :nodoc:
@@ -97,15 +96,4 @@ module ActiveModel
97
96
  write_attribute(attribute_name, value)
98
97
  end
99
98
  end
100
-
101
- module AttributeMethods #:nodoc:
102
- AttrNames = Module.new {
103
- def self.set_name_cache(name, value)
104
- const_name = "ATTR_#{name}"
105
- unless const_defined? const_name
106
- const_set const_name, value.dup.freeze
107
- end
108
- end
109
- }
110
- end
111
99
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/array/extract_options"
4
+ require "active_support/core_ext/hash/keys"
4
5
 
5
6
  module ActiveModel
6
7
  # == Active \Model \Callbacks
@@ -127,26 +128,28 @@ module ActiveModel
127
128
  private
128
129
 
129
130
  def _define_before_model_callback(klass, callback)
130
- klass.define_singleton_method("before_#{callback}") do |*args, &block|
131
- set_callback(:"#{callback}", :before, *args, &block)
131
+ klass.define_singleton_method("before_#{callback}") do |*args, **options, &block|
132
+ options.assert_valid_keys(:if, :unless, :prepend)
133
+ set_callback(:"#{callback}", :before, *args, options, &block)
132
134
  end
133
135
  end
134
136
 
135
137
  def _define_around_model_callback(klass, callback)
136
- klass.define_singleton_method("around_#{callback}") do |*args, &block|
137
- set_callback(:"#{callback}", :around, *args, &block)
138
+ klass.define_singleton_method("around_#{callback}") do |*args, **options, &block|
139
+ options.assert_valid_keys(:if, :unless, :prepend)
140
+ set_callback(:"#{callback}", :around, *args, options, &block)
138
141
  end
139
142
  end
140
143
 
141
144
  def _define_after_model_callback(klass, callback)
142
- klass.define_singleton_method("after_#{callback}") do |*args, &block|
143
- options = args.extract_options!
145
+ klass.define_singleton_method("after_#{callback}") do |*args, **options, &block|
146
+ options.assert_valid_keys(:if, :unless, :prepend)
144
147
  options[:prepend] = true
145
148
  conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
146
149
  v != false
147
150
  }
148
151
  options[:if] = Array(options[:if]) << conditional
149
- set_callback(:"#{callback}", :after, *(args << options), &block)
152
+ set_callback(:"#{callback}", :after, *args, options, &block)
150
153
  end
151
154
  end
152
155
  end
@@ -103,7 +103,7 @@ module ActiveModel
103
103
  @_to_partial_path ||= begin
104
104
  element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
105
105
  collection = ActiveSupport::Inflector.tableize(name)
106
- "#{collection}/#{element}".freeze
106
+ "#{collection}/#{element}"
107
107
  end
108
108
  end
109
109
  end
@@ -153,7 +153,7 @@ module ActiveModel
153
153
  @mutations_from_database = nil
154
154
  end
155
155
 
156
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
156
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
157
157
  #
158
158
  # person.changed? # => false
159
159
  # person.name = 'bob'
@@ -306,7 +306,7 @@ module ActiveModel
306
306
 
307
307
  # Handles <tt>*_previous_change</tt> for +method_missing+.
308
308
  def attribute_previous_change(attr)
309
- previous_changes[attr] if attribute_previously_changed?(attr)
309
+ previous_changes[attr]
310
310
  end
311
311
 
312
312
  # Handles <tt>*_will_change!</tt> for +method_missing+.
@@ -62,6 +62,11 @@ module ActiveModel
62
62
  CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
63
63
  MESSAGE_OPTIONS = [:message]
64
64
 
65
+ class << self
66
+ attr_accessor :i18n_full_message # :nodoc:
67
+ end
68
+ self.i18n_full_message = false
69
+
65
70
  attr_reader :messages, :details
66
71
 
67
72
  # Pass in the instance of the object that is using the errors object.
@@ -107,6 +112,17 @@ module ActiveModel
107
112
  @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }
108
113
  end
109
114
 
115
+ # Removes all errors except the given keys. Returns a hash containing the removed errors.
116
+ #
117
+ # person.errors.keys # => [:name, :age, :gender, :city]
118
+ # person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
119
+ # person.errors.keys # => [:age, :gender]
120
+ def slice!(*keys)
121
+ keys = keys.map(&:to_sym)
122
+ @details.slice!(*keys)
123
+ @messages.slice!(*keys)
124
+ end
125
+
110
126
  # Clear the error messages.
111
127
  #
112
128
  # person.errors.full_messages # => ["name cannot be nil"]
@@ -312,15 +328,15 @@ module ActiveModel
312
328
  # person.errors.added? :name, :blank # => true
313
329
  # person.errors.added? :name, "can't be blank" # => true
314
330
  #
315
- # If the error message requires an option, then it returns +true+ with
316
- # the correct option, or +false+ with an incorrect or missing option.
331
+ # If the error message requires options, then it returns +true+ with
332
+ # the correct options, or +false+ with incorrect or missing options.
317
333
  #
318
- # person.errors.add :name, :too_long, { count: 25 }
319
- # person.errors.added? :name, :too_long, count: 25 # => true
320
- # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
321
- # person.errors.added? :name, :too_long, count: 24 # => false
322
- # person.errors.added? :name, :too_long # => false
323
- # person.errors.added? :name, "is too long" # => false
334
+ # person.errors.add :name, :too_long, { count: 25 }
335
+ # person.errors.added? :name, :too_long, count: 25 # => true
336
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
337
+ # person.errors.added? :name, :too_long, count: 24 # => false
338
+ # person.errors.added? :name, :too_long # => false
339
+ # person.errors.added? :name, "is too long" # => false
324
340
  def added?(attribute, message = :invalid, options = {})
325
341
  message = message.call if message.respond_to?(:call)
326
342
 
@@ -331,6 +347,27 @@ module ActiveModel
331
347
  end
332
348
  end
333
349
 
350
+ # Returns +true+ if an error on the attribute with the given message is
351
+ # present, or +false+ otherwise. +message+ is treated the same as for +add+.
352
+ #
353
+ # person.errors.add :age
354
+ # person.errors.add :name, :too_long, { count: 25 }
355
+ # person.errors.of_kind? :age # => true
356
+ # person.errors.of_kind? :name # => false
357
+ # person.errors.of_kind? :name, :too_long # => true
358
+ # person.errors.of_kind? :name, "is too long (maximum is 25 characters)" # => true
359
+ # person.errors.of_kind? :name, :not_too_long # => false
360
+ # person.errors.of_kind? :name, "is too long" # => false
361
+ def of_kind?(attribute, message = :invalid)
362
+ message = message.call if message.respond_to?(:call)
363
+
364
+ if message.is_a? Symbol
365
+ details[attribute.to_sym].map { |e| e[:error] }.include? message
366
+ else
367
+ self[attribute].include? message
368
+ end
369
+ end
370
+
334
371
  # Returns all the full error messages in an array.
335
372
  #
336
373
  # class Person
@@ -364,12 +401,54 @@ module ActiveModel
364
401
  # Returns a full message for a given attribute.
365
402
  #
366
403
  # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
404
+ #
405
+ # The `"%{attribute} %{message}"` error format can be overridden with either
406
+ #
407
+ # * <tt>activemodel.errors.models.person/contacts/addresses.attributes.street.format</tt>
408
+ # * <tt>activemodel.errors.models.person/contacts/addresses.format</tt>
409
+ # * <tt>activemodel.errors.models.person.attributes.name.format</tt>
410
+ # * <tt>activemodel.errors.models.person.format</tt>
411
+ # * <tt>errors.format</tt>
367
412
  def full_message(attribute, message)
368
413
  return message if attribute == :base
369
- attr_name = attribute.to_s.tr(".", "_").humanize
414
+ attribute = attribute.to_s
415
+
416
+ if self.class.i18n_full_message && @base.class.respond_to?(:i18n_scope)
417
+ attribute = attribute.remove(/\[\d\]/)
418
+ parts = attribute.split(".")
419
+ attribute_name = parts.pop
420
+ namespace = parts.join("/") unless parts.empty?
421
+ attributes_scope = "#{@base.class.i18n_scope}.errors.models"
422
+
423
+ if namespace
424
+ defaults = @base.class.lookup_ancestors.map do |klass|
425
+ [
426
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
427
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
428
+ ]
429
+ end
430
+ else
431
+ defaults = @base.class.lookup_ancestors.map do |klass|
432
+ [
433
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
434
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
435
+ ]
436
+ end
437
+ end
438
+
439
+ defaults.flatten!
440
+ else
441
+ defaults = []
442
+ end
443
+
444
+ defaults << :"errors.format"
445
+ defaults << "%{attribute} %{message}"
446
+
447
+ attr_name = attribute.tr(".", "_").humanize
370
448
  attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
371
- I18n.t(:"errors.format",
372
- default: "%{attribute} %{message}",
449
+
450
+ I18n.t(defaults.shift,
451
+ default: defaults,
373
452
  attribute: attr_name,
374
453
  message: message)
375
454
  end