activemodel 7.0.8.7 → 7.1.5.1
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 +4 -4
- data/CHANGELOG.md +174 -167
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -9
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +5 -5
- data/lib/active_model/attribute/user_provided_default.rb +4 -0
- data/lib/active_model/attribute.rb +26 -1
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +134 -73
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +10 -1
- data/lib/active_model/attributes.rb +65 -48
- data/lib/active_model/callbacks.rb +5 -5
- data/lib/active_model/conversion.rb +14 -4
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +134 -13
- data/lib/active_model/error.rb +4 -3
- data/lib/active_model/errors.rb +37 -6
- data/lib/active_model/forbidden_attributes_protection.rb +2 -0
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/lint.rb +1 -1
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +34 -2
- data/lib/active_model/naming.rb +29 -10
- data/lib/active_model/railtie.rb +4 -0
- data/lib/active_model/secure_password.rb +61 -23
- data/lib/active_model/serialization.rb +3 -3
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/translation.rb +18 -16
- data/lib/active_model/type/big_integer.rb +23 -1
- data/lib/active_model/type/binary.rb +7 -1
- data/lib/active_model/type/boolean.rb +11 -9
- data/lib/active_model/type/date.rb +28 -2
- data/lib/active_model/type/date_time.rb +45 -3
- data/lib/active_model/type/decimal.rb +39 -1
- data/lib/active_model/type/float.rb +30 -1
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
- data/lib/active_model/type/helpers/numeric.rb +6 -1
- data/lib/active_model/type/helpers/time_value.rb +28 -12
- data/lib/active_model/type/immutable_string.rb +37 -1
- data/lib/active_model/type/integer.rb +44 -1
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +9 -1
- data/lib/active_model/type/time.rb +48 -7
- data/lib/active_model/type/value.rb +17 -1
- data/lib/active_model/type.rb +1 -0
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +1 -1
- data/lib/active_model/validations/callbacks.rb +4 -4
- data/lib/active_model/validations/clusivity.rb +5 -8
- data/lib/active_model/validations/comparability.rb +0 -11
- data/lib/active_model/validations/comparison.rb +15 -7
- data/lib/active_model/validations/format.rb +6 -7
- data/lib/active_model/validations/length.rb +10 -8
- data/lib/active_model/validations/numericality.rb +35 -23
- data/lib/active_model/validations/presence.rb +1 -1
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +4 -4
- data/lib/active_model/validations/with.rb +9 -2
- data/lib/active_model/validations.rb +44 -9
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +11 -6
@@ -10,7 +10,7 @@ module ActiveModel
|
|
10
10
|
# keys matching the attribute names.
|
11
11
|
#
|
12
12
|
# If the passed hash responds to <tt>permitted?</tt> method and the return value
|
13
|
-
# of this method is +false+ an
|
13
|
+
# of this method is +false+ an ActiveModel::ForbiddenAttributesError
|
14
14
|
# exception is raised.
|
15
15
|
#
|
16
16
|
# class Cat
|
@@ -11,17 +11,17 @@ module ActiveModel
|
|
11
11
|
#
|
12
12
|
# user = User.first
|
13
13
|
# user.pets.select(:id).first.user_id
|
14
|
-
# # => ActiveModel::MissingAttributeError: missing attribute
|
14
|
+
# # => ActiveModel::MissingAttributeError: missing attribute 'user_id' for Pet
|
15
15
|
class MissingAttributeError < NoMethodError
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
18
|
+
# = Active \Model \Attribute \Methods
|
19
19
|
#
|
20
20
|
# Provides a way to add prefixes and suffixes to your methods as
|
21
|
-
# well as handling the creation of
|
21
|
+
# well as handling the creation of ActiveRecord::Base - like
|
22
22
|
# class methods such as +table_name+.
|
23
23
|
#
|
24
|
-
# The requirements to implement
|
24
|
+
# The requirements to implement +ActiveModel::AttributeMethods+ are to:
|
25
25
|
#
|
26
26
|
# * <tt>include ActiveModel::AttributeMethods</tt> in your class.
|
27
27
|
# * Call each of its methods you want to add, such as +attribute_method_suffix+
|
@@ -70,7 +70,7 @@ module ActiveModel
|
|
70
70
|
|
71
71
|
included do
|
72
72
|
class_attribute :attribute_aliases, instance_writer: false, default: {}
|
73
|
-
class_attribute :
|
73
|
+
class_attribute :attribute_method_patterns, instance_writer: false, default: [ ClassMethods::AttributeMethodPattern.new ]
|
74
74
|
end
|
75
75
|
|
76
76
|
module ClassMethods
|
@@ -105,7 +105,7 @@ module ActiveModel
|
|
105
105
|
# person.clear_name
|
106
106
|
# person.name # => nil
|
107
107
|
def attribute_method_prefix(*prefixes, parameters: nil)
|
108
|
-
self.
|
108
|
+
self.attribute_method_patterns += prefixes.map! { |prefix| AttributeMethodPattern.new(prefix: prefix, parameters: parameters) }
|
109
109
|
undefine_attribute_methods
|
110
110
|
end
|
111
111
|
|
@@ -139,7 +139,7 @@ module ActiveModel
|
|
139
139
|
# person.name # => "Bob"
|
140
140
|
# person.name_short? # => true
|
141
141
|
def attribute_method_suffix(*suffixes, parameters: nil)
|
142
|
-
self.
|
142
|
+
self.attribute_method_patterns += suffixes.map! { |suffix| AttributeMethodPattern.new(suffix: suffix, parameters: parameters) }
|
143
143
|
undefine_attribute_methods
|
144
144
|
end
|
145
145
|
|
@@ -174,7 +174,7 @@ module ActiveModel
|
|
174
174
|
# person.reset_name_to_default!
|
175
175
|
# person.name # => 'Default Name'
|
176
176
|
def attribute_method_affix(*affixes)
|
177
|
-
self.
|
177
|
+
self.attribute_method_patterns += affixes.map! { |affix| AttributeMethodPattern.new(**affix) }
|
178
178
|
undefine_attribute_methods
|
179
179
|
end
|
180
180
|
|
@@ -202,35 +202,53 @@ module ActiveModel
|
|
202
202
|
# person.name_short? # => true
|
203
203
|
# person.nickname_short? # => true
|
204
204
|
def alias_attribute(new_name, old_name)
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
mangled_name = target_name
|
213
|
-
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
214
|
-
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
215
|
-
end
|
205
|
+
old_name = old_name.to_s
|
206
|
+
new_name = new_name.to_s
|
207
|
+
self.attribute_aliases = attribute_aliases.merge(new_name => old_name)
|
208
|
+
aliases_by_attribute_name[old_name] << new_name
|
209
|
+
eagerly_generate_alias_attribute_methods(new_name, old_name)
|
210
|
+
end
|
216
211
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
batch <<
|
229
|
-
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
230
|
-
body <<
|
231
|
-
"end"
|
232
|
-
end
|
212
|
+
def eagerly_generate_alias_attribute_methods(new_name, old_name) # :nodoc:
|
213
|
+
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |code_generator|
|
214
|
+
generate_alias_attribute_methods(code_generator, new_name, old_name)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name) # :nodoc:
|
219
|
+
ActiveSupport::CodeGenerator.batch(code_generator, __FILE__, __LINE__) do |owner|
|
220
|
+
attribute_method_patterns.each do |pattern|
|
221
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
233
222
|
end
|
223
|
+
attribute_method_patterns_cache.clear
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
228
|
+
method_name = pattern.method_name(new_name).to_s
|
229
|
+
target_name = pattern.method_name(old_name).to_s
|
230
|
+
parameters = pattern.parameters
|
231
|
+
mangled_name = target_name
|
232
|
+
|
233
|
+
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
234
|
+
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
235
|
+
end
|
236
|
+
|
237
|
+
code_generator.define_cached_method(mangled_name, as: method_name, namespace: :alias_attribute) do |batch|
|
238
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
|
239
|
+
"self.#{target_name}(#{parameters || ''})"
|
240
|
+
else
|
241
|
+
call_args = [":'#{target_name}'"]
|
242
|
+
call_args << parameters if parameters
|
243
|
+
"send(#{call_args.join(", ")})"
|
244
|
+
end
|
245
|
+
|
246
|
+
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
247
|
+
|
248
|
+
batch <<
|
249
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
250
|
+
body <<
|
251
|
+
"end"
|
234
252
|
end
|
235
253
|
end
|
236
254
|
|
@@ -245,7 +263,7 @@ module ActiveModel
|
|
245
263
|
end
|
246
264
|
|
247
265
|
# Declares the attributes that should be prefixed and suffixed by
|
248
|
-
#
|
266
|
+
# +ActiveModel::AttributeMethods+.
|
249
267
|
#
|
250
268
|
# To use, pass attribute names (as strings or symbols). Be sure to declare
|
251
269
|
# +define_attribute_methods+ after you define any prefix, suffix, or affix
|
@@ -269,12 +287,17 @@ module ActiveModel
|
|
269
287
|
# end
|
270
288
|
def define_attribute_methods(*attr_names)
|
271
289
|
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
272
|
-
attr_names.flatten.each
|
290
|
+
attr_names.flatten.each do |attr_name|
|
291
|
+
define_attribute_method(attr_name, _owner: owner)
|
292
|
+
aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
|
293
|
+
generate_alias_attribute_methods owner, aliased_name, attr_name
|
294
|
+
end
|
295
|
+
end
|
273
296
|
end
|
274
297
|
end
|
275
298
|
|
276
299
|
# Declares an attribute that should be prefixed and suffixed by
|
277
|
-
#
|
300
|
+
# +ActiveModel::AttributeMethods+.
|
278
301
|
#
|
279
302
|
# To use, pass an attribute name (as string or symbol). Be sure to declare
|
280
303
|
# +define_attribute_method+ after you define any prefix, suffix or affix
|
@@ -301,26 +324,45 @@ module ActiveModel
|
|
301
324
|
# person.name = 'Bob'
|
302
325
|
# person.name # => "Bob"
|
303
326
|
# person.name_short? # => true
|
304
|
-
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
327
|
+
def define_attribute_method(attr_name, _owner: generated_attribute_methods, as: attr_name)
|
305
328
|
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
306
|
-
|
307
|
-
|
329
|
+
attribute_method_patterns.each do |pattern|
|
330
|
+
define_attribute_method_pattern(pattern, attr_name, owner: owner, as: as)
|
331
|
+
end
|
332
|
+
attribute_method_patterns_cache.clear
|
333
|
+
end
|
334
|
+
end
|
308
335
|
|
309
|
-
|
310
|
-
|
336
|
+
def define_attribute_method_pattern(pattern, attr_name, owner:, as:, override: false) # :nodoc:
|
337
|
+
canonical_method_name = pattern.method_name(attr_name)
|
338
|
+
public_method_name = pattern.method_name(as)
|
339
|
+
|
340
|
+
# If defining a regular attribute method, we don't override methods that are explictly
|
341
|
+
# defined in parrent classes.
|
342
|
+
if instance_method_already_implemented?(public_method_name)
|
343
|
+
# However, for `alias_attribute`, we always define the method.
|
344
|
+
# We check for override second because `instance_method_already_implemented?`
|
345
|
+
# also check for dangerous methods.
|
346
|
+
return unless override
|
347
|
+
end
|
311
348
|
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
349
|
+
generate_method = "define_method_#{pattern.proxy_target}"
|
350
|
+
if respond_to?(generate_method, true)
|
351
|
+
send(generate_method, attr_name.to_s, owner: owner, as: as)
|
352
|
+
else
|
353
|
+
define_proxy_call(
|
354
|
+
owner,
|
355
|
+
canonical_method_name,
|
356
|
+
pattern.proxy_target,
|
357
|
+
pattern.parameters,
|
358
|
+
attr_name.to_s,
|
359
|
+
namespace: :active_model_proxy,
|
360
|
+
as: public_method_name,
|
361
|
+
)
|
320
362
|
end
|
321
363
|
end
|
322
364
|
|
323
|
-
# Removes all the previously dynamically defined methods from the class.
|
365
|
+
# Removes all the previously dynamically defined methods from the class, including alias attribute methods.
|
324
366
|
#
|
325
367
|
# class Person
|
326
368
|
# include ActiveModel::AttributeMethods
|
@@ -328,6 +370,7 @@ module ActiveModel
|
|
328
370
|
# attr_accessor :name
|
329
371
|
# attribute_method_suffix '_short?'
|
330
372
|
# define_attribute_method :name
|
373
|
+
# alias_attribute :first_name, :name
|
331
374
|
#
|
332
375
|
# private
|
333
376
|
# def attribute_short?(attr)
|
@@ -337,19 +380,36 @@ module ActiveModel
|
|
337
380
|
#
|
338
381
|
# person = Person.new
|
339
382
|
# person.name = 'Bob'
|
383
|
+
# person.first_name # => "Bob"
|
340
384
|
# person.name_short? # => true
|
341
385
|
#
|
342
386
|
# Person.undefine_attribute_methods
|
343
387
|
#
|
344
388
|
# person.name_short? # => NoMethodError
|
389
|
+
# person.first_name # => NoMethodError
|
345
390
|
def undefine_attribute_methods
|
346
391
|
generated_attribute_methods.module_eval do
|
347
392
|
undef_method(*instance_methods)
|
348
393
|
end
|
349
|
-
|
394
|
+
attribute_method_patterns_cache.clear
|
395
|
+
end
|
396
|
+
|
397
|
+
def aliases_by_attribute_name # :nodoc:
|
398
|
+
@aliases_by_attribute_name ||= Hash.new { |h, k| h[k] = [] }
|
350
399
|
end
|
351
400
|
|
352
401
|
private
|
402
|
+
def inherited(base) # :nodoc:
|
403
|
+
super
|
404
|
+
base.class_eval do
|
405
|
+
@attribute_method_patterns_cache = nil
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def resolve_attribute_name(name)
|
410
|
+
attribute_aliases.fetch(super, &:itself)
|
411
|
+
end
|
412
|
+
|
353
413
|
def generated_attribute_methods
|
354
414
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
355
415
|
end
|
@@ -367,33 +427,34 @@ module ActiveModel
|
|
367
427
|
# used to alleviate the GC, which ultimately also speeds up the app
|
368
428
|
# significantly (in our case our test suite finishes 10% faster with
|
369
429
|
# this cache).
|
370
|
-
def
|
371
|
-
@
|
430
|
+
def attribute_method_patterns_cache
|
431
|
+
@attribute_method_patterns_cache ||= Concurrent::Map.new(initial_capacity: 4)
|
372
432
|
end
|
373
433
|
|
374
|
-
def
|
375
|
-
|
376
|
-
|
434
|
+
def attribute_method_patterns_matching(method_name)
|
435
|
+
attribute_method_patterns_cache.compute_if_absent(method_name) do
|
436
|
+
attribute_method_patterns.filter_map { |pattern| pattern.match(method_name) }
|
377
437
|
end
|
378
438
|
end
|
379
439
|
|
380
440
|
# Define a method `name` in `mod` that dispatches to `send`
|
381
441
|
# using the given `extra` args. This falls back on `send`
|
382
442
|
# if the called name cannot be compiled.
|
383
|
-
def define_proxy_call(code_generator, name,
|
443
|
+
def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:, as: name)
|
384
444
|
mangled_name = name
|
385
445
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
386
446
|
mangled_name = "__temp__#{name.unpack1("h*")}"
|
387
447
|
end
|
388
448
|
|
389
|
-
|
390
|
-
|
391
|
-
|
449
|
+
call_args.map!(&:inspect)
|
450
|
+
call_args << parameters if parameters
|
451
|
+
namespace = :"#{namespace}_#{proxy_target}"
|
392
452
|
|
393
|
-
|
394
|
-
|
453
|
+
code_generator.define_cached_method(mangled_name, as: as, namespace: namespace) do |batch|
|
454
|
+
body = if CALL_COMPILABLE_REGEXP.match?(proxy_target)
|
455
|
+
"self.#{proxy_target}(#{call_args.join(", ")})"
|
395
456
|
else
|
396
|
-
call_args.unshift(":'#{
|
457
|
+
call_args.unshift(":'#{proxy_target}'")
|
397
458
|
"send(#{call_args.join(", ")})"
|
398
459
|
end
|
399
460
|
|
@@ -406,23 +467,23 @@ module ActiveModel
|
|
406
467
|
end
|
407
468
|
end
|
408
469
|
|
409
|
-
class
|
410
|
-
attr_reader :prefix, :suffix, :
|
470
|
+
class AttributeMethodPattern # :nodoc:
|
471
|
+
attr_reader :prefix, :suffix, :proxy_target, :parameters
|
411
472
|
|
412
|
-
|
473
|
+
AttributeMethod = Struct.new(:proxy_target, :attr_name)
|
413
474
|
|
414
475
|
def initialize(prefix: "", suffix: "", parameters: nil)
|
415
476
|
@prefix = prefix
|
416
477
|
@suffix = suffix
|
417
478
|
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
418
479
|
@regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
|
419
|
-
@
|
480
|
+
@proxy_target = "#{@prefix}attribute#{@suffix}"
|
420
481
|
@method_name = "#{prefix}%s#{suffix}"
|
421
482
|
end
|
422
483
|
|
423
484
|
def match(method_name)
|
424
485
|
if @regex =~ method_name
|
425
|
-
|
486
|
+
AttributeMethod.new(proxy_target, $1)
|
426
487
|
end
|
427
488
|
end
|
428
489
|
|
@@ -457,7 +518,7 @@ module ActiveModel
|
|
457
518
|
# attribute method. If so, we tell +attribute_missing+ to dispatch the
|
458
519
|
# attribute. This method can be overloaded to customize the behavior.
|
459
520
|
def attribute_missing(match, *args, &block)
|
460
|
-
__send__(match.
|
521
|
+
__send__(match.proxy_target, match.attr_name, *args, &block)
|
461
522
|
end
|
462
523
|
ruby2_keywords(:attribute_missing)
|
463
524
|
|
@@ -485,12 +546,12 @@ module ActiveModel
|
|
485
546
|
# Returns a struct representing the matching attribute method.
|
486
547
|
# The struct's attributes are prefix, base and suffix.
|
487
548
|
def matched_attribute_method(method_name)
|
488
|
-
matches = self.class.send(:
|
549
|
+
matches = self.class.send(:attribute_method_patterns_matching, method_name)
|
489
550
|
matches.detect { |match| attribute_method?(match.attr_name) }
|
490
551
|
end
|
491
552
|
|
492
553
|
def missing_attribute(attr_name, stack)
|
493
|
-
raise ActiveModel::MissingAttributeError, "missing attribute
|
554
|
+
raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack
|
494
555
|
end
|
495
556
|
|
496
557
|
def _read_attribute(attr)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/class/subclasses"
|
4
|
+
require "active_model/attribute_set"
|
5
|
+
require "active_model/attribute/user_provided_default"
|
6
|
+
|
7
|
+
module ActiveModel
|
8
|
+
module AttributeRegistration # :nodoc:
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
module ClassMethods # :nodoc:
|
12
|
+
def attribute(name, type = nil, default: (no_default = true), **options)
|
13
|
+
type = resolve_type_name(type, **options) if type.is_a?(Symbol)
|
14
|
+
|
15
|
+
pending = pending_attribute(name)
|
16
|
+
pending.type = type if type
|
17
|
+
pending.default = default unless no_default
|
18
|
+
|
19
|
+
reset_default_attributes
|
20
|
+
end
|
21
|
+
|
22
|
+
def _default_attributes # :nodoc:
|
23
|
+
@default_attributes ||= build_default_attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
def attribute_types # :nodoc:
|
27
|
+
@attribute_types ||= _default_attributes.cast_types.tap do |hash|
|
28
|
+
hash.default = Type.default_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
class PendingAttribute # :nodoc:
|
34
|
+
attr_accessor :type, :default
|
35
|
+
|
36
|
+
def apply_to(attribute)
|
37
|
+
attribute = attribute.with_type(type || attribute.type)
|
38
|
+
attribute = attribute.with_user_default(default) if defined?(@default)
|
39
|
+
attribute
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def pending_attribute(name)
|
44
|
+
@pending_attributes ||= {}
|
45
|
+
@pending_attributes[resolve_attribute_name(name)] ||= PendingAttribute.new
|
46
|
+
end
|
47
|
+
|
48
|
+
def apply_pending_attributes(attribute_set)
|
49
|
+
superclass.send(__method__, attribute_set) if superclass.respond_to?(__method__, true)
|
50
|
+
|
51
|
+
defined?(@pending_attributes) && @pending_attributes.each do |name, pending|
|
52
|
+
attribute_set[name] = pending.apply_to(attribute_set[name])
|
53
|
+
end
|
54
|
+
|
55
|
+
attribute_set
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_default_attributes
|
59
|
+
apply_pending_attributes(AttributeSet.new({}))
|
60
|
+
end
|
61
|
+
|
62
|
+
def reset_default_attributes
|
63
|
+
@default_attributes = nil
|
64
|
+
@attribute_types = nil
|
65
|
+
subclasses.each { |subclass| subclass.send(__method__) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def resolve_attribute_name(name)
|
69
|
+
name.to_s
|
70
|
+
end
|
71
|
+
|
72
|
+
def resolve_type_name(name, **options)
|
73
|
+
Type.lookup(name, **options)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -21,6 +21,10 @@ module ActiveModel
|
|
21
21
|
@attributes[name] = value
|
22
22
|
end
|
23
23
|
|
24
|
+
def cast_types
|
25
|
+
attributes.transform_values(&:type)
|
26
|
+
end
|
27
|
+
|
24
28
|
def values_before_type_cast
|
25
29
|
attributes.transform_values(&:value_before_type_cast)
|
26
30
|
end
|
@@ -37,6 +41,7 @@ module ActiveModel
|
|
37
41
|
def key?(name)
|
38
42
|
attributes.key?(name) && self[name].initialized?
|
39
43
|
end
|
44
|
+
alias :include? :key?
|
40
45
|
|
41
46
|
def keys
|
42
47
|
attributes.each_key.select { |name| self[name].initialized? }
|
@@ -94,8 +99,12 @@ module ActiveModel
|
|
94
99
|
AttributeSet.new(new_attributes)
|
95
100
|
end
|
96
101
|
|
102
|
+
def reverse_merge!(target_attributes)
|
103
|
+
attributes.reverse_merge!(target_attributes.attributes) && self
|
104
|
+
end
|
105
|
+
|
97
106
|
def ==(other)
|
98
|
-
attributes == other.attributes
|
107
|
+
other.is_a?(AttributeSet) && attributes == other.send(:attributes)
|
99
108
|
end
|
100
109
|
|
101
110
|
protected
|
@@ -1,33 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_model/attribute_set"
|
4
|
-
require "active_model/attribute/user_provided_default"
|
5
|
-
|
6
3
|
module ActiveModel
|
7
|
-
|
4
|
+
# = Active \Model \Attributes
|
5
|
+
#
|
6
|
+
# The Attributes module allows models to define attributes beyond simple Ruby
|
7
|
+
# readers and writers. Similar to Active Record attributes, which are
|
8
|
+
# typically inferred from the database schema, Active Model Attributes are
|
9
|
+
# aware of data types, can have default values, and can handle casting and
|
10
|
+
# serialization.
|
11
|
+
#
|
12
|
+
# To use Attributes, include the module in your model class and define your
|
13
|
+
# attributes using the +attribute+ macro. It accepts a name, a type, a default
|
14
|
+
# value, and any other options supported by the attribute type.
|
15
|
+
#
|
16
|
+
# ==== Examples
|
17
|
+
#
|
18
|
+
# class Person
|
19
|
+
# include ActiveModel::Attributes
|
20
|
+
#
|
21
|
+
# attribute :name, :string
|
22
|
+
# attribute :active, :boolean, default: true
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# person = Person.new
|
26
|
+
# person.name = "Volmer"
|
27
|
+
#
|
28
|
+
# person.name # => "Volmer"
|
29
|
+
# person.active # => true
|
30
|
+
module Attributes
|
8
31
|
extend ActiveSupport::Concern
|
32
|
+
include ActiveModel::AttributeRegistration
|
9
33
|
include ActiveModel::AttributeMethods
|
10
34
|
|
11
35
|
included do
|
12
36
|
attribute_method_suffix "=", parameters: "value"
|
13
|
-
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
14
|
-
self.attribute_types = Hash.new(Type.default_value)
|
15
|
-
self._default_attributes = AttributeSet.new({})
|
16
37
|
end
|
17
38
|
|
18
39
|
module ClassMethods
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
40
|
+
##
|
41
|
+
# :call-seq: attribute(name, cast_type = nil, default: nil, **options)
|
42
|
+
#
|
43
|
+
# Defines a model attribute. In addition to the attribute name, a cast
|
44
|
+
# type and default value may be specified, as well as any options
|
45
|
+
# supported by the given cast type.
|
46
|
+
#
|
47
|
+
# class Person
|
48
|
+
# include ActiveModel::Attributes
|
49
|
+
#
|
50
|
+
# attribute :name, :string
|
51
|
+
# attribute :active, :boolean, default: true
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# person = Person.new
|
55
|
+
# person.name = "Volmer"
|
56
|
+
#
|
57
|
+
# person.name # => "Volmer"
|
58
|
+
# person.active # => true
|
59
|
+
def attribute(name, ...)
|
60
|
+
super
|
27
61
|
define_attribute_method(name)
|
28
62
|
end
|
29
63
|
|
30
|
-
# Returns an array of attribute names as strings
|
64
|
+
# Returns an array of attribute names as strings.
|
31
65
|
#
|
32
66
|
# class Person
|
33
67
|
# include ActiveModel::Attributes
|
@@ -36,18 +70,17 @@ module ActiveModel
|
|
36
70
|
# attribute :age, :integer
|
37
71
|
# end
|
38
72
|
#
|
39
|
-
# Person.attribute_names
|
40
|
-
# # => ["name", "age"]
|
73
|
+
# Person.attribute_names # => ["name", "age"]
|
41
74
|
def attribute_names
|
42
75
|
attribute_types.keys
|
43
76
|
end
|
44
77
|
|
45
78
|
private
|
46
|
-
def define_method_attribute=(
|
79
|
+
def define_method_attribute=(canonical_name, owner:, as: canonical_name)
|
47
80
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
48
|
-
owner,
|
81
|
+
owner, canonical_name, writer: true,
|
49
82
|
) do |temp_method_name, attr_name_expr|
|
50
|
-
owner.define_cached_method("#{
|
83
|
+
owner.define_cached_method(temp_method_name, as: "#{as}=", namespace: :active_model) do |batch|
|
51
84
|
batch <<
|
52
85
|
"def #{temp_method_name}(value)" <<
|
53
86
|
" _write_attribute(#{attr_name_expr}, value)" <<
|
@@ -55,27 +88,9 @@ module ActiveModel
|
|
55
88
|
end
|
56
89
|
end
|
57
90
|
end
|
58
|
-
|
59
|
-
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
60
|
-
private_constant :NO_DEFAULT_PROVIDED
|
61
|
-
|
62
|
-
def define_default_attribute(name, value, type)
|
63
|
-
self._default_attributes = _default_attributes.deep_dup
|
64
|
-
if value == NO_DEFAULT_PROVIDED
|
65
|
-
default_attribute = _default_attributes[name].with_type(type)
|
66
|
-
else
|
67
|
-
default_attribute = Attribute::UserProvidedDefault.new(
|
68
|
-
name,
|
69
|
-
value,
|
70
|
-
type,
|
71
|
-
_default_attributes.fetch(name.to_s) { nil },
|
72
|
-
)
|
73
|
-
end
|
74
|
-
_default_attributes[name] = default_attribute
|
75
|
-
end
|
76
91
|
end
|
77
92
|
|
78
|
-
def initialize(*)
|
93
|
+
def initialize(*) # :nodoc:
|
79
94
|
@attributes = self.class._default_attributes.deep_dup
|
80
95
|
super
|
81
96
|
end
|
@@ -85,7 +100,8 @@ module ActiveModel
|
|
85
100
|
super
|
86
101
|
end
|
87
102
|
|
88
|
-
# Returns a hash of all the attributes with their names as keys and the
|
103
|
+
# Returns a hash of all the attributes with their names as keys and the
|
104
|
+
# values of the attributes as values.
|
89
105
|
#
|
90
106
|
# class Person
|
91
107
|
# include ActiveModel::Attributes
|
@@ -94,14 +110,16 @@ module ActiveModel
|
|
94
110
|
# attribute :age, :integer
|
95
111
|
# end
|
96
112
|
#
|
97
|
-
# person = Person.new
|
98
|
-
# person.
|
99
|
-
#
|
113
|
+
# person = Person.new
|
114
|
+
# person.name = "Francesco"
|
115
|
+
# person.age = 22
|
116
|
+
#
|
117
|
+
# person.attributes # => { "name" => "Francesco", "age" => 22}
|
100
118
|
def attributes
|
101
119
|
@attributes.to_hash
|
102
120
|
end
|
103
121
|
|
104
|
-
# Returns an array of attribute names as strings
|
122
|
+
# Returns an array of attribute names as strings.
|
105
123
|
#
|
106
124
|
# class Person
|
107
125
|
# include ActiveModel::Attributes
|
@@ -111,13 +129,12 @@ module ActiveModel
|
|
111
129
|
# end
|
112
130
|
#
|
113
131
|
# person = Person.new
|
114
|
-
# person.attribute_names
|
115
|
-
# # => ["name", "age"]
|
132
|
+
# person.attribute_names # => ["name", "age"]
|
116
133
|
def attribute_names
|
117
134
|
@attributes.keys
|
118
135
|
end
|
119
136
|
|
120
|
-
def freeze
|
137
|
+
def freeze # :nodoc:
|
121
138
|
@attributes = @attributes.clone.freeze unless frozen?
|
122
139
|
super
|
123
140
|
end
|