activemodel 7.0.8 → 7.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -191
- 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 +14 -9
@@ -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!
|