activemodel 5.2.7.1 → 6.0.0.beta1

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