activemodel 7.0.8.1 → 7.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -196
- 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 +102 -63
- data/lib/active_model/attribute_registration.rb +77 -0
- data/lib/active_model/attribute_set.rb +9 -0
- data/lib/active_model/attributes.rb +62 -45
- 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 +17 -12
- 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 +4 -3
- data/lib/active_model/model.rb +26 -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 +4 -0
- 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/confirmation.rb +1 -1
- 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 +2 -2
- 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 +45 -10
- data/lib/active_model/validator.rb +7 -5
- data/lib/active_model/version.rb +1 -1
- data/lib/active_model.rb +5 -1
- metadata +15 -10
@@ -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,50 @@ 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
|
-
call_args << parameters if parameters
|
223
|
-
"send(#{call_args.join(", ")})"
|
224
|
-
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
|
225
217
|
|
226
|
-
|
218
|
+
def generate_alias_attribute_methods(code_generator, new_name, old_name)
|
219
|
+
attribute_method_patterns.each do |pattern|
|
220
|
+
alias_attribute_method_definition(code_generator, pattern, new_name, old_name)
|
221
|
+
end
|
222
|
+
end
|
227
223
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
224
|
+
def alias_attribute_method_definition(code_generator, pattern, new_name, old_name) # :nodoc:
|
225
|
+
method_name = pattern.method_name(new_name).to_s
|
226
|
+
target_name = pattern.method_name(old_name).to_s
|
227
|
+
parameters = pattern.parameters
|
228
|
+
mangled_name = target_name
|
229
|
+
|
230
|
+
unless NAME_COMPILABLE_REGEXP.match?(target_name)
|
231
|
+
mangled_name = "__temp__#{target_name.unpack1("h*")}"
|
232
|
+
end
|
233
|
+
|
234
|
+
code_generator.define_cached_method(method_name, as: mangled_name, namespace: :alias_attribute) do |batch|
|
235
|
+
body = if CALL_COMPILABLE_REGEXP.match?(target_name)
|
236
|
+
"self.#{target_name}(#{parameters || ''})"
|
237
|
+
else
|
238
|
+
call_args = [":'#{target_name}'"]
|
239
|
+
call_args << parameters if parameters
|
240
|
+
"send(#{call_args.join(", ")})"
|
233
241
|
end
|
242
|
+
|
243
|
+
modifier = parameters == FORWARD_PARAMETERS ? "ruby2_keywords " : ""
|
244
|
+
|
245
|
+
batch <<
|
246
|
+
"#{modifier}def #{mangled_name}(#{parameters || ''})" <<
|
247
|
+
body <<
|
248
|
+
"end"
|
234
249
|
end
|
235
250
|
end
|
236
251
|
|
@@ -245,7 +260,7 @@ module ActiveModel
|
|
245
260
|
end
|
246
261
|
|
247
262
|
# Declares the attributes that should be prefixed and suffixed by
|
248
|
-
#
|
263
|
+
# +ActiveModel::AttributeMethods+.
|
249
264
|
#
|
250
265
|
# To use, pass attribute names (as strings or symbols). Be sure to declare
|
251
266
|
# +define_attribute_methods+ after you define any prefix, suffix, or affix
|
@@ -269,12 +284,17 @@ module ActiveModel
|
|
269
284
|
# end
|
270
285
|
def define_attribute_methods(*attr_names)
|
271
286
|
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
|
272
|
-
attr_names.flatten.each
|
287
|
+
attr_names.flatten.each do |attr_name|
|
288
|
+
define_attribute_method(attr_name, _owner: owner)
|
289
|
+
aliases_by_attribute_name[attr_name.to_s].each do |aliased_name|
|
290
|
+
generate_alias_attribute_methods owner, aliased_name, attr_name
|
291
|
+
end
|
292
|
+
end
|
273
293
|
end
|
274
294
|
end
|
275
295
|
|
276
296
|
# Declares an attribute that should be prefixed and suffixed by
|
277
|
-
#
|
297
|
+
# +ActiveModel::AttributeMethods+.
|
278
298
|
#
|
279
299
|
# To use, pass an attribute name (as string or symbol). Be sure to declare
|
280
300
|
# +define_attribute_method+ after you define any prefix, suffix or affix
|
@@ -303,24 +323,24 @@ module ActiveModel
|
|
303
323
|
# person.name_short? # => true
|
304
324
|
def define_attribute_method(attr_name, _owner: generated_attribute_methods)
|
305
325
|
ActiveSupport::CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
|
306
|
-
|
307
|
-
method_name =
|
326
|
+
attribute_method_patterns.each do |pattern|
|
327
|
+
method_name = pattern.method_name(attr_name)
|
308
328
|
|
309
329
|
unless instance_method_already_implemented?(method_name)
|
310
|
-
generate_method = "define_method_#{
|
330
|
+
generate_method = "define_method_#{pattern.proxy_target}"
|
311
331
|
|
312
332
|
if respond_to?(generate_method, true)
|
313
333
|
send(generate_method, attr_name.to_s, owner: owner)
|
314
334
|
else
|
315
|
-
define_proxy_call(owner, method_name,
|
335
|
+
define_proxy_call(owner, method_name, pattern.proxy_target, pattern.parameters, attr_name.to_s, namespace: :active_model_proxy)
|
316
336
|
end
|
317
337
|
end
|
318
338
|
end
|
319
|
-
|
339
|
+
attribute_method_patterns_cache.clear
|
320
340
|
end
|
321
341
|
end
|
322
342
|
|
323
|
-
# Removes all the previously dynamically defined methods from the class.
|
343
|
+
# Removes all the previously dynamically defined methods from the class, including alias attribute methods.
|
324
344
|
#
|
325
345
|
# class Person
|
326
346
|
# include ActiveModel::AttributeMethods
|
@@ -328,6 +348,7 @@ module ActiveModel
|
|
328
348
|
# attr_accessor :name
|
329
349
|
# attribute_method_suffix '_short?'
|
330
350
|
# define_attribute_method :name
|
351
|
+
# alias_attribute :first_name, :name
|
331
352
|
#
|
332
353
|
# private
|
333
354
|
# def attribute_short?(attr)
|
@@ -337,19 +358,36 @@ module ActiveModel
|
|
337
358
|
#
|
338
359
|
# person = Person.new
|
339
360
|
# person.name = 'Bob'
|
361
|
+
# person.first_name # => "Bob"
|
340
362
|
# person.name_short? # => true
|
341
363
|
#
|
342
364
|
# Person.undefine_attribute_methods
|
343
365
|
#
|
344
366
|
# person.name_short? # => NoMethodError
|
367
|
+
# person.first_name # => NoMethodError
|
345
368
|
def undefine_attribute_methods
|
346
369
|
generated_attribute_methods.module_eval do
|
347
370
|
undef_method(*instance_methods)
|
348
371
|
end
|
349
|
-
|
372
|
+
attribute_method_patterns_cache.clear
|
373
|
+
end
|
374
|
+
|
375
|
+
def aliases_by_attribute_name # :nodoc:
|
376
|
+
@aliases_by_attribute_name ||= Hash.new { |h, k| h[k] = [] }
|
350
377
|
end
|
351
378
|
|
352
379
|
private
|
380
|
+
def inherited(base) # :nodoc:
|
381
|
+
super
|
382
|
+
base.class_eval do
|
383
|
+
@attribute_method_patterns_cache = nil
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def resolve_attribute_name(name)
|
388
|
+
attribute_aliases.fetch(super, &:itself)
|
389
|
+
end
|
390
|
+
|
353
391
|
def generated_attribute_methods
|
354
392
|
@generated_attribute_methods ||= Module.new.tap { |mod| include mod }
|
355
393
|
end
|
@@ -367,33 +405,34 @@ module ActiveModel
|
|
367
405
|
# used to alleviate the GC, which ultimately also speeds up the app
|
368
406
|
# significantly (in our case our test suite finishes 10% faster with
|
369
407
|
# this cache).
|
370
|
-
def
|
371
|
-
@
|
408
|
+
def attribute_method_patterns_cache
|
409
|
+
@attribute_method_patterns_cache ||= Concurrent::Map.new(initial_capacity: 4)
|
372
410
|
end
|
373
411
|
|
374
|
-
def
|
375
|
-
|
376
|
-
|
412
|
+
def attribute_method_patterns_matching(method_name)
|
413
|
+
attribute_method_patterns_cache.compute_if_absent(method_name) do
|
414
|
+
attribute_method_patterns.filter_map { |pattern| pattern.match(method_name) }
|
377
415
|
end
|
378
416
|
end
|
379
417
|
|
380
418
|
# Define a method `name` in `mod` that dispatches to `send`
|
381
419
|
# using the given `extra` args. This falls back on `send`
|
382
420
|
# if the called name cannot be compiled.
|
383
|
-
def define_proxy_call(code_generator, name,
|
421
|
+
def define_proxy_call(code_generator, name, proxy_target, parameters, *call_args, namespace:)
|
384
422
|
mangled_name = name
|
385
423
|
unless NAME_COMPILABLE_REGEXP.match?(name)
|
386
424
|
mangled_name = "__temp__#{name.unpack1("h*")}"
|
387
425
|
end
|
388
426
|
|
389
|
-
|
390
|
-
|
391
|
-
|
427
|
+
call_args.map!(&:inspect)
|
428
|
+
call_args << parameters if parameters
|
429
|
+
namespace = :"#{namespace}_#{proxy_target}_#{call_args.join("_")}}"
|
392
430
|
|
393
|
-
|
394
|
-
|
431
|
+
code_generator.define_cached_method(name, as: mangled_name, namespace: namespace) do |batch|
|
432
|
+
body = if CALL_COMPILABLE_REGEXP.match?(proxy_target)
|
433
|
+
"self.#{proxy_target}(#{call_args.join(", ")})"
|
395
434
|
else
|
396
|
-
call_args.unshift(":'#{
|
435
|
+
call_args.unshift(":'#{proxy_target}'")
|
397
436
|
"send(#{call_args.join(", ")})"
|
398
437
|
end
|
399
438
|
|
@@ -406,23 +445,23 @@ module ActiveModel
|
|
406
445
|
end
|
407
446
|
end
|
408
447
|
|
409
|
-
class
|
410
|
-
attr_reader :prefix, :suffix, :
|
448
|
+
class AttributeMethodPattern # :nodoc:
|
449
|
+
attr_reader :prefix, :suffix, :proxy_target, :parameters
|
411
450
|
|
412
|
-
|
451
|
+
AttributeMethod = Struct.new(:proxy_target, :attr_name)
|
413
452
|
|
414
453
|
def initialize(prefix: "", suffix: "", parameters: nil)
|
415
454
|
@prefix = prefix
|
416
455
|
@suffix = suffix
|
417
456
|
@parameters = parameters.nil? ? FORWARD_PARAMETERS : parameters
|
418
457
|
@regex = /\A(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})\z/
|
419
|
-
@
|
458
|
+
@proxy_target = "#{@prefix}attribute#{@suffix}"
|
420
459
|
@method_name = "#{prefix}%s#{suffix}"
|
421
460
|
end
|
422
461
|
|
423
462
|
def match(method_name)
|
424
463
|
if @regex =~ method_name
|
425
|
-
|
464
|
+
AttributeMethod.new(proxy_target, $1)
|
426
465
|
end
|
427
466
|
end
|
428
467
|
|
@@ -457,7 +496,7 @@ module ActiveModel
|
|
457
496
|
# attribute method. If so, we tell +attribute_missing+ to dispatch the
|
458
497
|
# attribute. This method can be overloaded to customize the behavior.
|
459
498
|
def attribute_missing(match, *args, &block)
|
460
|
-
__send__(match.
|
499
|
+
__send__(match.proxy_target, match.attr_name, *args, &block)
|
461
500
|
end
|
462
501
|
ruby2_keywords(:attribute_missing)
|
463
502
|
|
@@ -485,12 +524,12 @@ module ActiveModel
|
|
485
524
|
# Returns a struct representing the matching attribute method.
|
486
525
|
# The struct's attributes are prefix, base and suffix.
|
487
526
|
def matched_attribute_method(method_name)
|
488
|
-
matches = self.class.send(:
|
527
|
+
matches = self.class.send(:attribute_method_patterns_matching, method_name)
|
489
528
|
matches.detect { |match| attribute_method?(match.attr_name) }
|
490
529
|
end
|
491
530
|
|
492
531
|
def missing_attribute(attr_name, stack)
|
493
|
-
raise ActiveModel::MissingAttributeError, "missing attribute
|
532
|
+
raise ActiveModel::MissingAttributeError, "missing attribute '#{attr_name}' for #{self.class}", stack
|
494
533
|
end
|
495
534
|
|
496
535
|
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,6 +99,10 @@ 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
107
|
attributes == other.attributes
|
99
108
|
end
|
@@ -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,8 +70,7 @@ 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
|
@@ -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
|
@@ -4,14 +4,14 @@ require "active_support/core_ext/array/extract_options"
|
|
4
4
|
require "active_support/core_ext/hash/keys"
|
5
5
|
|
6
6
|
module ActiveModel
|
7
|
-
#
|
7
|
+
# = Active \Model \Callbacks
|
8
8
|
#
|
9
9
|
# Provides an interface for any class to have Active Record like callbacks.
|
10
10
|
#
|
11
11
|
# Like the Active Record methods, the callback chain is aborted as soon as
|
12
12
|
# one of the methods throws +:abort+.
|
13
13
|
#
|
14
|
-
# First, extend ActiveModel::Callbacks from the class you are creating:
|
14
|
+
# First, extend +ActiveModel::Callbacks+ from the class you are creating:
|
15
15
|
#
|
16
16
|
# class MyModel
|
17
17
|
# extend ActiveModel::Callbacks
|
@@ -69,7 +69,7 @@ module ActiveModel
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
# define_model_callbacks accepts the same options +define_callbacks+ does,
|
72
|
+
# +define_model_callbacks+ accepts the same options +define_callbacks+ does,
|
73
73
|
# in case you want to overwrite a default. Besides that, it also accepts an
|
74
74
|
# <tt>:only</tt> option, where you can choose if you want all types (before,
|
75
75
|
# around or after) or just some.
|
@@ -77,7 +77,7 @@ module ActiveModel
|
|
77
77
|
# define_model_callbacks :initialize, only: :after
|
78
78
|
#
|
79
79
|
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
|
80
|
-
# on that method call. To get around this you can call the define_model_callbacks
|
80
|
+
# on that method call. To get around this you can call the +define_model_callbacks+
|
81
81
|
# method as many times as you need.
|
82
82
|
#
|
83
83
|
# define_model_callbacks :create, only: :after
|
@@ -104,7 +104,7 @@ module ActiveModel
|
|
104
104
|
# end
|
105
105
|
# end
|
106
106
|
#
|
107
|
-
# NOTE: +method_name+ passed to define_model_callbacks must not end with
|
107
|
+
# NOTE: +method_name+ passed to +define_model_callbacks+ must not end with
|
108
108
|
# <tt>!</tt>, <tt>?</tt> or <tt>=</tt>.
|
109
109
|
def define_model_callbacks(*callbacks)
|
110
110
|
options = callbacks.extract_options!
|