activemodel 4.2.0 → 6.1.0

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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +49 -37
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -22
  5. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute_assignment.rb +55 -0
  8. data/lib/active_model/attribute_methods.rb +150 -73
  9. data/lib/active_model/attribute_mutation_tracker.rb +181 -0
  10. data/lib/active_model/attribute_set/builder.rb +191 -0
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  12. data/lib/active_model/attribute_set.rb +106 -0
  13. data/lib/active_model/attributes.rb +132 -0
  14. data/lib/active_model/callbacks.rb +31 -25
  15. data/lib/active_model/conversion.rb +20 -9
  16. data/lib/active_model/dirty.rb +142 -116
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +436 -202
  19. data/lib/active_model/forbidden_attributes_protection.rb +6 -3
  20. data/lib/active_model/gem_version.rb +5 -3
  21. data/lib/active_model/lint.rb +47 -42
  22. data/lib/active_model/locale/en.yml +2 -1
  23. data/lib/active_model/model.rb +7 -7
  24. data/lib/active_model/naming.rb +36 -18
  25. data/lib/active_model/nested_error.rb +22 -0
  26. data/lib/active_model/railtie.rb +8 -0
  27. data/lib/active_model/secure_password.rb +61 -67
  28. data/lib/active_model/serialization.rb +48 -17
  29. data/lib/active_model/serializers/json.rb +22 -13
  30. data/lib/active_model/translation.rb +5 -4
  31. data/lib/active_model/type/big_integer.rb +14 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +46 -0
  34. data/lib/active_model/type/date.rb +52 -0
  35. data/lib/active_model/type/date_time.rb +46 -0
  36. data/lib/active_model/type/decimal.rb +69 -0
  37. data/lib/active_model/type/float.rb +35 -0
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
  39. data/lib/active_model/type/helpers/mutable.rb +20 -0
  40. data/lib/active_model/type/helpers/numeric.rb +48 -0
  41. data/lib/active_model/type/helpers/time_value.rb +90 -0
  42. data/lib/active_model/type/helpers/timezone.rb +19 -0
  43. data/lib/active_model/type/helpers.rb +7 -0
  44. data/lib/active_model/type/immutable_string.rb +35 -0
  45. data/lib/active_model/type/integer.rb +67 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +35 -0
  48. data/lib/active_model/type/time.rb +46 -0
  49. data/lib/active_model/type/value.rb +133 -0
  50. data/lib/active_model/type.rb +53 -0
  51. data/lib/active_model/validations/absence.rb +6 -4
  52. data/lib/active_model/validations/acceptance.rb +72 -14
  53. data/lib/active_model/validations/callbacks.rb +23 -19
  54. data/lib/active_model/validations/clusivity.rb +18 -12
  55. data/lib/active_model/validations/confirmation.rb +27 -14
  56. data/lib/active_model/validations/exclusion.rb +7 -4
  57. data/lib/active_model/validations/format.rb +27 -27
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +8 -7
  60. data/lib/active_model/validations/length.rb +35 -32
  61. data/lib/active_model/validations/numericality.rb +72 -34
  62. data/lib/active_model/validations/presence.rb +3 -3
  63. data/lib/active_model/validations/validates.rb +17 -15
  64. data/lib/active_model/validations/with.rb +6 -12
  65. data/lib/active_model/validations.rb +58 -23
  66. data/lib/active_model/validator.rb +23 -17
  67. data/lib/active_model/version.rb +4 -2
  68. data/lib/active_model.rb +18 -11
  69. metadata +44 -25
  70. data/lib/active_model/serializers/xml.rb +0 -238
  71. data/lib/active_model/test_case.rb +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0d16f8919f81db8eadf49f60bc8a4ae705bfa7d2
4
- data.tar.gz: dae2d1dcc05714b21558f04cb03de8ee964e3490
2
+ SHA256:
3
+ metadata.gz: 1caa114ce8c604cb9ac388ef4ebbfd796f8ff253799322db9fa714193bca6821
4
+ data.tar.gz: 49f8fb7f47a3154022161dee9871d6aba46f7750b3381e690e37d7ff2663ef48
5
5
  SHA512:
6
- metadata.gz: 038b122e38dd5b82f015060bfeae6252f8d0edfa2e0cf3f545a5f8219b312bcc3740b4836d74d0afaf761a60a6cadd2e81ee47d3fb2066a386fb395f1461bacf
7
- data.tar.gz: f4e3cfa3a0575f8da3cb1ab965140edd06243c8943d0a988be55c0b8115218087a555673c0df4d09dd1fd9c9a92a9ff4c29051179d4a4812b77156edb757ab63
6
+ metadata.gz: 26271099e8c36f89e1ee19fcb3469be914240ea887f15aba4d0de79b3db57f17d8467f523010330ba4f70e4df01d14398a6db7687df0a9be674f17ffd284be5d
7
+ data.tar.gz: e32d32dac693b8cae0445b2b9634345688b4010ecd4cbda054288934b4d0aeb353486415b55934e2c298fc8ab09655712ede1214e175ef4b67fe8af80926bb86
data/CHANGELOG.md CHANGED
@@ -1,61 +1,73 @@
1
- * Passwords with spaces only allowed in `ActiveModel::SecurePassword`.
1
+ ## Rails 6.1.0 (December 09, 2020) ##
2
2
 
3
- Presence validation can be used to restore old behavior.
3
+ * Pass in `base` instead of `base_class` to Error.human_attribute_name
4
4
 
5
- *Yevhene Shemet*
5
+ This is useful in cases where the `human_attribute_name` method depends
6
+ on other attributes' values of the class under validation to derive what the
7
+ attribute name should be.
6
8
 
7
- * Validate options passed to `ActiveModel::Validations.validate`.
9
+ *Filipe Sabella*
8
10
 
9
- Preventing, in many cases, the simple mistake of using `validate` instead of `validates`.
11
+ * Deprecate marshalling load from legacy attributes format.
10
12
 
11
- *Sonny Michaud*
13
+ *Ryuta Kamizono*
12
14
 
13
- * Deprecate `reset_#{attribute}` in favor of `restore_#{attribute}`.
15
+ * `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
14
16
 
15
- These methods may cause confusion with the `reset_changes`, which has
16
- different behaviour.
17
+ topic.update!(status: :archived)
18
+ topic.status_previously_changed?(from: "active", to: "archived")
19
+ # => true
17
20
 
18
- *Rafael Mendonça França*
21
+ *George Claghorn*
19
22
 
20
- * Deprecate `ActiveModel::Dirty#reset_changes` in favor of `#clear_changes_information`.
23
+ * Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
21
24
 
22
- Method's name is causing confusion with the `reset_#{attribute}` methods.
23
- While `reset_name` sets the value of the name attribute to previous value
24
- `reset_changes` only discards the changes.
25
+ class Animal
26
+ include ActiveModel::Attributes
27
+ attribute :age
28
+ end
25
29
 
26
- *Rafael Mendonça França*
30
+ animal = Animal.new
31
+ animal.freeze
32
+ animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
27
33
 
28
- * Added `restore_attributes` method to `ActiveModel::Dirty` API which restores
29
- the value of changed attributes to previous value.
34
+ *Josh Brody*
30
35
 
31
- *Igor G.*
36
+ * Add `*_previously_was` attribute methods when dirty tracking. Example:
32
37
 
33
- * Allow proc and symbol as values for `only_integer` of `NumericalityValidator`
38
+ pirate.update(catchphrase: "Ahoy!")
39
+ pirate.previous_changes["catchphrase"] # => ["Thar She Blows!", "Ahoy!"]
40
+ pirate.catchphrase_previously_was # => "Thar She Blows!"
34
41
 
35
- *Robin Mehner*
42
+ *DHH*
36
43
 
37
- * `has_secure_password` now verifies that the given password is less than 72
38
- characters if validations are enabled.
44
+ * Encapsulate each validation error as an Error object.
39
45
 
40
- Fixes #14591.
46
+ The `ActiveModel`’s `errors` collection is now an array of these Error
47
+ objects, instead of messages/details hash.
41
48
 
42
- *Akshay Vishnoi*
49
+ For each of these `Error` object, its `message` and `full_message` methods
50
+ are for generating error messages. Its `details` method would return error’s
51
+ extra parameters, found in the original `details` hash.
43
52
 
44
- * Remove deprecated `Validator#setup` without replacement.
53
+ The change tries its best at maintaining backward compatibility, however
54
+ some edge cases won’t be covered, like `errors#first` will return `ActiveModel::Error` and manipulating
55
+ `errors.messages` and `errors.details` hashes directly will have no effect. Moving forward,
56
+ please convert those direct manipulations to use provided API methods instead.
45
57
 
46
- See #10716.
58
+ The list of deprecated methods and their planned future behavioral changes at the next major release are:
47
59
 
48
- *Kuldeep Aggarwal*
60
+ * `errors#slice!` will be removed.
61
+ * `errors#each` with the `key, value` two-arguments block will stop working, while the `error` single-argument block would return `Error` object.
62
+ * `errors#values` will be removed.
63
+ * `errors#keys` will be removed.
64
+ * `errors#to_xml` will be removed.
65
+ * `errors#to_h` will be removed, and can be replaced with `errors#to_hash`.
66
+ * Manipulating `errors` itself as a hash will have no effect (e.g. `errors[:foo] = 'bar'`).
67
+ * Manipulating the hash returned by `errors#messages` (e.g. `errors.messages[:foo] = 'bar'`) will have no effect.
68
+ * Manipulating the hash returned by `errors#details` (e.g. `errors.details[:foo].clear`) will have no effect.
49
69
 
50
- * Add plural and singular form for length validator's default messages.
70
+ *lulalala*
51
71
 
52
- *Abd ar-Rahman Hamid*
53
72
 
54
- * Introduce `validate` as an alias for `valid?`.
55
-
56
- This is more intuitive when you want to run validations but don't care about
57
- the return value.
58
-
59
- *Henrik Nyh*
60
-
61
- Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activemodel/CHANGELOG.md) for previous changes.
73
+ Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activemodel/CHANGELOG.md) for previous changes.
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2014 David Heinemeier Hansson
1
+ Copyright (c) 2004-2020 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
@@ -5,6 +5,8 @@ They allow for Action Pack helpers to interact with non-Active Record models,
5
5
  for example. Active Model also helps with building custom ORMs for use outside of
6
6
  the Rails framework.
7
7
 
8
+ You can read more about Active Model in the {Active Model Basics}[https://edgeguides.rubyonrails.org/active_model_basics.html] guide.
9
+
8
10
  Prior to Rails 3.0, if a plugin or gem developer wanted to have an object
9
11
  interact with Action Pack helpers, it was required to either copy chunks of
10
12
  code from Rails, or monkey patch entire helpers to make them handle objects
@@ -49,7 +51,7 @@ behavior out of the box:
49
51
  send("#{attr}=", nil)
50
52
  end
51
53
  end
52
-
54
+
53
55
  person = Person.new
54
56
  person.clear_name
55
57
  person.clear_age
@@ -132,7 +134,7 @@ behavior out of the box:
132
134
  "Name"
133
135
  end
134
136
  end
135
-
137
+
136
138
  person = Person.new
137
139
  person.name = nil
138
140
  person.validate!
@@ -154,8 +156,8 @@ behavior out of the box:
154
156
 
155
157
  * Making objects serializable
156
158
 
157
- ActiveModel::Serialization provides a standard interface for your object
158
- to provide +to_json+ or +to_xml+ serialization.
159
+ <tt>ActiveModel::Serialization</tt> provides a standard interface for your object
160
+ to provide +to_json+ serialization.
159
161
 
160
162
  class SerialPerson
161
163
  include ActiveModel::Serialization
@@ -177,13 +179,6 @@ behavior out of the box:
177
179
  s = SerialPerson.new
178
180
  s.to_json # => "{\"name\":null}"
179
181
 
180
- class SerialPerson
181
- include ActiveModel::Serializers::Xml
182
- end
183
-
184
- s = SerialPerson.new
185
- s.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
186
-
187
182
  {Learn more}[link:classes/ActiveModel/Serialization.html]
188
183
 
189
184
  * Internationalization (i18n) support
@@ -205,7 +200,7 @@ behavior out of the box:
205
200
  attr_accessor :first_name, :last_name
206
201
 
207
202
  validates_each :first_name, :last_name do |record, attr, value|
208
- record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
203
+ record.errors.add attr, "starts with z." if value.start_with?("z")
209
204
  end
210
205
  end
211
206
 
@@ -216,10 +211,10 @@ behavior out of the box:
216
211
  {Learn more}[link:classes/ActiveModel/Validations.html]
217
212
 
218
213
  * Custom validators
219
-
214
+
220
215
  class HasNameValidator < ActiveModel::Validator
221
216
  def validate(record)
222
- record.errors[:name] = "must exist" if record.name.blank?
217
+ record.errors.add(:name, "must exist") if record.name.blank?
223
218
  end
224
219
  end
225
220
 
@@ -242,31 +237,30 @@ behavior out of the box:
242
237
 
243
238
  The latest version of Active Model can be installed with RubyGems:
244
239
 
245
- % [sudo] gem install activemodel
240
+ $ gem install activemodel
246
241
 
247
242
  Source code can be downloaded as part of the Rails project on GitHub
248
243
 
249
- * https://github.com/rails/rails/tree/4-2-stable/activemodel
244
+ * https://github.com/rails/rails/tree/master/activemodel
250
245
 
251
246
 
252
247
  == License
253
248
 
254
249
  Active Model is released under the MIT license:
255
250
 
256
- * http://www.opensource.org/licenses/MIT
251
+ * https://opensource.org/licenses/MIT
257
252
 
258
253
 
259
254
  == Support
260
255
 
261
- API documentation is at
256
+ API documentation is at:
262
257
 
263
- * http://api.rubyonrails.org
258
+ * https://api.rubyonrails.org
264
259
 
265
- Bug reports can be filed for the Ruby on Rails project here:
260
+ Bug reports for the Ruby on Rails project can be filed here:
266
261
 
267
262
  * https://github.com/rails/rails/issues
268
263
 
269
264
  Feature requests should be discussed on the rails-core mailing list here:
270
265
 
271
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
272
-
266
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/attribute"
4
+
5
+ module ActiveModel
6
+ class Attribute # :nodoc:
7
+ class UserProvidedDefault < FromUser # :nodoc:
8
+ def initialize(name, value, type, database_default)
9
+ @user_provided_value = value
10
+ super(name, value, type, database_default)
11
+ end
12
+
13
+ def value_before_type_cast
14
+ if user_provided_value.is_a?(Proc)
15
+ @memoized_value_before_type_cast ||= user_provided_value.call
16
+ else
17
+ @user_provided_value
18
+ end
19
+ end
20
+
21
+ def with_type(type)
22
+ self.class.new(name, user_provided_value, type, original_attribute)
23
+ end
24
+
25
+ def marshal_dump
26
+ result = [
27
+ name,
28
+ value_before_type_cast,
29
+ type,
30
+ original_attribute,
31
+ ]
32
+ result << value if defined?(@value)
33
+ result
34
+ end
35
+
36
+ def marshal_load(values)
37
+ name, user_provided_value, type, original_attribute, value = values
38
+ @name = name
39
+ @user_provided_value = user_provided_value
40
+ @type = type
41
+ @original_attribute = original_attribute
42
+ if values.length == 5
43
+ @value = value
44
+ end
45
+ end
46
+
47
+ private
48
+ attr_reader :user_provided_value
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+
5
+ module ActiveModel
6
+ class Attribute # :nodoc:
7
+ class << self
8
+ def from_database(name, value_before_type_cast, type, value = nil)
9
+ FromDatabase.new(name, value_before_type_cast, type, nil, value)
10
+ end
11
+
12
+ def from_user(name, value_before_type_cast, type, original_attribute = nil)
13
+ FromUser.new(name, value_before_type_cast, type, original_attribute)
14
+ end
15
+
16
+ def with_cast_value(name, value_before_type_cast, type)
17
+ WithCastValue.new(name, value_before_type_cast, type)
18
+ end
19
+
20
+ def null(name)
21
+ Null.new(name)
22
+ end
23
+
24
+ def uninitialized(name, type)
25
+ Uninitialized.new(name, type)
26
+ end
27
+ end
28
+
29
+ attr_reader :name, :value_before_type_cast, :type
30
+
31
+ # This method should not be called directly.
32
+ # Use #from_database or #from_user
33
+ def initialize(name, value_before_type_cast, type, original_attribute = nil, value = nil)
34
+ @name = name
35
+ @value_before_type_cast = value_before_type_cast
36
+ @type = type
37
+ @original_attribute = original_attribute
38
+ @value = value unless value.nil?
39
+ end
40
+
41
+ def value
42
+ # `defined?` is cheaper than `||=` when we get back falsy values
43
+ @value = type_cast(value_before_type_cast) unless defined?(@value)
44
+ @value
45
+ end
46
+
47
+ def original_value
48
+ if assigned?
49
+ original_attribute.original_value
50
+ else
51
+ type_cast(value_before_type_cast)
52
+ end
53
+ end
54
+
55
+ def value_for_database
56
+ type.serialize(value)
57
+ end
58
+
59
+ def changed?
60
+ changed_from_assignment? || changed_in_place?
61
+ end
62
+
63
+ def changed_in_place?
64
+ has_been_read? && type.changed_in_place?(original_value_for_database, value)
65
+ end
66
+
67
+ def forgetting_assignment
68
+ with_value_from_database(value_for_database)
69
+ end
70
+
71
+ def with_value_from_user(value)
72
+ type.assert_valid_value(value)
73
+ self.class.from_user(name, value, type, original_attribute || self)
74
+ end
75
+
76
+ def with_value_from_database(value)
77
+ self.class.from_database(name, value, type)
78
+ end
79
+
80
+ def with_cast_value(value)
81
+ self.class.with_cast_value(name, value, type)
82
+ end
83
+
84
+ def with_type(type)
85
+ if changed_in_place?
86
+ with_value_from_user(value).with_type(type)
87
+ else
88
+ self.class.new(name, value_before_type_cast, type, original_attribute)
89
+ end
90
+ end
91
+
92
+ def type_cast(*)
93
+ raise NotImplementedError
94
+ end
95
+
96
+ def initialized?
97
+ true
98
+ end
99
+
100
+ def came_from_user?
101
+ false
102
+ end
103
+
104
+ def has_been_read?
105
+ defined?(@value)
106
+ end
107
+
108
+ def ==(other)
109
+ self.class == other.class &&
110
+ name == other.name &&
111
+ value_before_type_cast == other.value_before_type_cast &&
112
+ type == other.type
113
+ end
114
+ alias eql? ==
115
+
116
+ def hash
117
+ [self.class, name, value_before_type_cast, type].hash
118
+ end
119
+
120
+ def init_with(coder)
121
+ @name = coder["name"]
122
+ @value_before_type_cast = coder["value_before_type_cast"]
123
+ @type = coder["type"]
124
+ @original_attribute = coder["original_attribute"]
125
+ @value = coder["value"] if coder.map.key?("value")
126
+ end
127
+
128
+ def encode_with(coder)
129
+ coder["name"] = name
130
+ coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil?
131
+ coder["type"] = type if type
132
+ coder["original_attribute"] = original_attribute if original_attribute
133
+ coder["value"] = value if defined?(@value)
134
+ end
135
+
136
+ def original_value_for_database
137
+ if assigned?
138
+ original_attribute.original_value_for_database
139
+ else
140
+ _original_value_for_database
141
+ end
142
+ end
143
+
144
+ private
145
+ attr_reader :original_attribute
146
+ alias :assigned? :original_attribute
147
+
148
+ def initialize_dup(other)
149
+ if defined?(@value) && @value.duplicable?
150
+ @value = @value.dup
151
+ end
152
+ end
153
+
154
+ def changed_from_assignment?
155
+ assigned? && type.changed?(original_value, value, value_before_type_cast)
156
+ end
157
+
158
+ def _original_value_for_database
159
+ type.serialize(original_value)
160
+ end
161
+
162
+ class FromDatabase < Attribute # :nodoc:
163
+ def type_cast(value)
164
+ type.deserialize(value)
165
+ end
166
+
167
+ def _original_value_for_database
168
+ value_before_type_cast
169
+ end
170
+ private :_original_value_for_database
171
+ end
172
+
173
+ class FromUser < Attribute # :nodoc:
174
+ def type_cast(value)
175
+ type.cast(value)
176
+ end
177
+
178
+ def came_from_user?
179
+ !type.value_constructed_by_mass_assignment?(value_before_type_cast)
180
+ end
181
+ end
182
+
183
+ class WithCastValue < Attribute # :nodoc:
184
+ def type_cast(value)
185
+ value
186
+ end
187
+
188
+ def changed_in_place?
189
+ false
190
+ end
191
+ end
192
+
193
+ class Null < Attribute # :nodoc:
194
+ def initialize(name)
195
+ super(name, nil, Type.default_value)
196
+ end
197
+
198
+ def type_cast(*)
199
+ nil
200
+ end
201
+
202
+ def with_type(type)
203
+ self.class.with_cast_value(name, nil, type)
204
+ end
205
+
206
+ def with_value_from_database(value)
207
+ raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
208
+ end
209
+ alias_method :with_value_from_user, :with_value_from_database
210
+ alias_method :with_cast_value, :with_value_from_database
211
+ end
212
+
213
+ class Uninitialized < Attribute # :nodoc:
214
+ UNINITIALIZED_ORIGINAL_VALUE = Object.new
215
+
216
+ def initialize(name, type)
217
+ super(name, nil, type)
218
+ end
219
+
220
+ def value
221
+ if block_given?
222
+ yield name
223
+ end
224
+ end
225
+
226
+ def original_value
227
+ UNINITIALIZED_ORIGINAL_VALUE
228
+ end
229
+
230
+ def value_for_database
231
+ end
232
+
233
+ def initialized?
234
+ false
235
+ end
236
+
237
+ def forgetting_assignment
238
+ dup
239
+ end
240
+
241
+ def with_type(type)
242
+ self.class.new(name, type)
243
+ end
244
+ end
245
+
246
+ private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
247
+ end
248
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActiveModel
6
+ module AttributeAssignment
7
+ include ActiveModel::ForbiddenAttributesProtection
8
+
9
+ # Allows you to set all the attributes by passing in a hash of attributes with
10
+ # keys matching the attribute names.
11
+ #
12
+ # If the passed hash responds to <tt>permitted?</tt> method and the return value
13
+ # of this method is +false+ an <tt>ActiveModel::ForbiddenAttributesError</tt>
14
+ # exception is raised.
15
+ #
16
+ # class Cat
17
+ # include ActiveModel::AttributeAssignment
18
+ # attr_accessor :name, :status
19
+ # end
20
+ #
21
+ # cat = Cat.new
22
+ # cat.assign_attributes(name: "Gorby", status: "yawning")
23
+ # cat.name # => 'Gorby'
24
+ # cat.status # => 'yawning'
25
+ # cat.assign_attributes(status: "sleeping")
26
+ # cat.name # => 'Gorby'
27
+ # cat.status # => 'sleeping'
28
+ def assign_attributes(new_attributes)
29
+ unless new_attributes.respond_to?(:each_pair)
30
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
31
+ end
32
+ return if new_attributes.empty?
33
+
34
+ _assign_attributes(sanitize_for_mass_assignment(new_attributes))
35
+ end
36
+
37
+ alias attributes= assign_attributes
38
+
39
+ private
40
+ def _assign_attributes(attributes)
41
+ attributes.each do |k, v|
42
+ _assign_attribute(k, v)
43
+ end
44
+ end
45
+
46
+ def _assign_attribute(k, v)
47
+ setter = :"#{k}="
48
+ if respond_to?(setter)
49
+ public_send(setter, v)
50
+ else
51
+ raise UnknownAttributeError.new(self, k.to_s)
52
+ end
53
+ end
54
+ end
55
+ end