activemodel 4.2.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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