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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +49 -37
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -22
- data/lib/active_model/attribute/user_provided_default.rb +51 -0
- data/lib/active_model/attribute.rb +248 -0
- data/lib/active_model/attribute_assignment.rb +55 -0
- data/lib/active_model/attribute_methods.rb +150 -73
- data/lib/active_model/attribute_mutation_tracker.rb +181 -0
- data/lib/active_model/attribute_set/builder.rb +191 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +106 -0
- data/lib/active_model/attributes.rb +132 -0
- data/lib/active_model/callbacks.rb +31 -25
- data/lib/active_model/conversion.rb +20 -9
- data/lib/active_model/dirty.rb +142 -116
- data/lib/active_model/error.rb +207 -0
- data/lib/active_model/errors.rb +436 -202
- data/lib/active_model/forbidden_attributes_protection.rb +6 -3
- data/lib/active_model/gem_version.rb +5 -3
- data/lib/active_model/lint.rb +47 -42
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +7 -7
- data/lib/active_model/naming.rb +36 -18
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +8 -0
- data/lib/active_model/secure_password.rb +61 -67
- data/lib/active_model/serialization.rb +48 -17
- data/lib/active_model/serializers/json.rb +22 -13
- data/lib/active_model/translation.rb +5 -4
- data/lib/active_model/type/big_integer.rb +14 -0
- data/lib/active_model/type/binary.rb +52 -0
- data/lib/active_model/type/boolean.rb +46 -0
- data/lib/active_model/type/date.rb +52 -0
- data/lib/active_model/type/date_time.rb +46 -0
- data/lib/active_model/type/decimal.rb +69 -0
- data/lib/active_model/type/float.rb +35 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
- data/lib/active_model/type/helpers/mutable.rb +20 -0
- data/lib/active_model/type/helpers/numeric.rb +48 -0
- data/lib/active_model/type/helpers/time_value.rb +90 -0
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +35 -0
- data/lib/active_model/type/integer.rb +67 -0
- data/lib/active_model/type/registry.rb +70 -0
- data/lib/active_model/type/string.rb +35 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/value.rb +133 -0
- data/lib/active_model/type.rb +53 -0
- data/lib/active_model/validations/absence.rb +6 -4
- data/lib/active_model/validations/acceptance.rb +72 -14
- data/lib/active_model/validations/callbacks.rb +23 -19
- data/lib/active_model/validations/clusivity.rb +18 -12
- data/lib/active_model/validations/confirmation.rb +27 -14
- data/lib/active_model/validations/exclusion.rb +7 -4
- data/lib/active_model/validations/format.rb +27 -27
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +8 -7
- data/lib/active_model/validations/length.rb +35 -32
- data/lib/active_model/validations/numericality.rb +72 -34
- data/lib/active_model/validations/presence.rb +3 -3
- data/lib/active_model/validations/validates.rb +17 -15
- data/lib/active_model/validations/with.rb +6 -12
- data/lib/active_model/validations.rb +58 -23
- data/lib/active_model/validator.rb +23 -17
- data/lib/active_model/version.rb +4 -2
- data/lib/active_model.rb +18 -11
- metadata +44 -25
- data/lib/active_model/serializers/xml.rb +0 -238
- data/lib/active_model/test_case.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1caa114ce8c604cb9ac388ef4ebbfd796f8ff253799322db9fa714193bca6821
|
4
|
+
data.tar.gz: 49f8fb7f47a3154022161dee9871d6aba46f7750b3381e690e37d7ff2663ef48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 26271099e8c36f89e1ee19fcb3469be914240ea887f15aba4d0de79b3db57f17d8467f523010330ba4f70e4df01d14398a6db7687df0a9be674f17ffd284be5d
|
7
|
+
data.tar.gz: e32d32dac693b8cae0445b2b9634345688b4010ecd4cbda054288934b4d0aeb353486415b55934e2c298fc8ab09655712ede1214e175ef4b67fe8af80926bb86
|
data/CHANGELOG.md
CHANGED
@@ -1,61 +1,73 @@
|
|
1
|
-
|
1
|
+
## Rails 6.1.0 (December 09, 2020) ##
|
2
2
|
|
3
|
-
|
3
|
+
* Pass in `base` instead of `base_class` to Error.human_attribute_name
|
4
4
|
|
5
|
-
|
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
|
-
*
|
9
|
+
*Filipe Sabella*
|
8
10
|
|
9
|
-
|
11
|
+
* Deprecate marshalling load from legacy attributes format.
|
10
12
|
|
11
|
-
*
|
13
|
+
*Ryuta Kamizono*
|
12
14
|
|
13
|
-
*
|
15
|
+
* `*_previously_changed?` accepts `:from` and `:to` keyword arguments like `*_changed?`.
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
+
topic.update!(status: :archived)
|
18
|
+
topic.status_previously_changed?(from: "active", to: "archived")
|
19
|
+
# => true
|
17
20
|
|
18
|
-
*
|
21
|
+
*George Claghorn*
|
19
22
|
|
20
|
-
*
|
23
|
+
* Raise FrozenError when trying to write attributes that aren't backed by the database on an object that is frozen:
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
+
class Animal
|
26
|
+
include ActiveModel::Attributes
|
27
|
+
attribute :age
|
28
|
+
end
|
25
29
|
|
26
|
-
|
30
|
+
animal = Animal.new
|
31
|
+
animal.freeze
|
32
|
+
animal.age = 25 # => FrozenError, "can't modify a frozen Animal"
|
27
33
|
|
28
|
-
*
|
29
|
-
the value of changed attributes to previous value.
|
34
|
+
*Josh Brody*
|
30
35
|
|
31
|
-
|
36
|
+
* Add `*_previously_was` attribute methods when dirty tracking. Example:
|
32
37
|
|
33
|
-
|
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
|
-
*
|
42
|
+
*DHH*
|
36
43
|
|
37
|
-
*
|
38
|
-
characters if validations are enabled.
|
44
|
+
* Encapsulate each validation error as an Error object.
|
39
45
|
|
40
|
-
|
46
|
+
The `ActiveModel`’s `errors` collection is now an array of these Error
|
47
|
+
objects, instead of messages/details hash.
|
41
48
|
|
42
|
-
|
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
|
-
|
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
|
-
|
58
|
+
The list of deprecated methods and their planned future behavioral changes at the next major release are:
|
47
59
|
|
48
|
-
*
|
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
|
-
*
|
70
|
+
*lulalala*
|
51
71
|
|
52
|
-
*Abd ar-Rahman Hamid*
|
53
72
|
|
54
|
-
|
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
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+
|
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,
|
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
|
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
|
-
|
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/
|
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
|
-
*
|
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
|
-
*
|
258
|
+
* https://api.rubyonrails.org
|
264
259
|
|
265
|
-
Bug reports
|
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://
|
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
|