activemodel 6.1.4.1 → 7.0.0.rc2
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 +77 -66
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute.rb +4 -0
- data/lib/active_model/attribute_methods.rb +65 -81
- data/lib/active_model/attribute_set/builder.rb +1 -10
- data/lib/active_model/attribute_set.rb +4 -1
- data/lib/active_model/attributes.rb +15 -12
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/conversion.rb +2 -2
- data/lib/active_model/dirty.rb +6 -5
- data/lib/active_model/errors.rb +35 -235
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +6 -59
- data/lib/active_model/naming.rb +15 -8
- data/lib/active_model/secure_password.rb +2 -1
- data/lib/active_model/serialization.rb +7 -2
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/date.rb +1 -1
- data/lib/active_model/type/helpers/numeric.rb +9 -1
- data/lib/active_model/type/helpers/time_value.rb +3 -3
- data/lib/active_model/type/integer.rb +4 -1
- data/lib/active_model/type/registry.rb +9 -41
- data/lib/active_model/type/time.rb +1 -1
- data/lib/active_model/type.rb +6 -5
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/comparability.rb +29 -0
- data/lib/active_model/validations/comparison.rb +82 -0
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/numericality.rb +28 -21
- data/lib/active_model/validations.rb +4 -4
- data/lib/active_model/validator.rb +2 -2
- data/lib/active_model.rb +2 -1
- metadata +14 -10
@@ -4,25 +4,26 @@ require "active_model/attribute_set"
|
|
4
4
|
require "active_model/attribute/user_provided_default"
|
5
5
|
|
6
6
|
module ActiveModel
|
7
|
-
module Attributes
|
7
|
+
module Attributes # :nodoc:
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
include ActiveModel::AttributeMethods
|
10
10
|
|
11
11
|
included do
|
12
|
-
attribute_method_suffix "="
|
12
|
+
attribute_method_suffix "=", parameters: "value"
|
13
13
|
class_attribute :attribute_types, :_default_attributes, instance_accessor: false
|
14
14
|
self.attribute_types = Hash.new(Type.default_value)
|
15
15
|
self._default_attributes = AttributeSet.new({})
|
16
16
|
end
|
17
17
|
|
18
18
|
module ClassMethods
|
19
|
-
def attribute(name,
|
19
|
+
def attribute(name, cast_type = nil, default: NO_DEFAULT_PROVIDED, **options)
|
20
20
|
name = name.to_s
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
|
22
|
+
cast_type = Type.lookup(cast_type, **options) if Symbol === cast_type
|
23
|
+
cast_type ||= attribute_types[name]
|
24
|
+
|
25
|
+
self.attribute_types = attribute_types.merge(name => cast_type)
|
26
|
+
define_default_attribute(name, default, cast_type)
|
26
27
|
define_attribute_method(name)
|
27
28
|
end
|
28
29
|
|
@@ -46,10 +47,12 @@ module ActiveModel
|
|
46
47
|
ActiveModel::AttributeMethods::AttrNames.define_attribute_accessor_method(
|
47
48
|
owner, name, writer: true,
|
48
49
|
) do |temp_method_name, attr_name_expr|
|
49
|
-
owner
|
50
|
-
|
51
|
-
|
52
|
-
|
50
|
+
owner.define_cached_method("#{name}=", as: temp_method_name, namespace: :active_model) do |batch|
|
51
|
+
batch <<
|
52
|
+
"def #{temp_method_name}(value)" <<
|
53
|
+
" _write_attribute(#{attr_name_expr}, value)" <<
|
54
|
+
"end"
|
55
|
+
end
|
53
56
|
end
|
54
57
|
end
|
55
58
|
|
@@ -63,7 +63,7 @@ module ActiveModel
|
|
63
63
|
# NOTE: Calling the same callback multiple times will overwrite previous callback definitions.
|
64
64
|
#
|
65
65
|
module Callbacks
|
66
|
-
def self.extended(base)
|
66
|
+
def self.extended(base) # :nodoc:
|
67
67
|
base.class_eval do
|
68
68
|
include ActiveSupport::Callbacks
|
69
69
|
end
|
@@ -96,10 +96,10 @@ module ActiveModel
|
|
96
96
|
self.class._to_partial_path
|
97
97
|
end
|
98
98
|
|
99
|
-
module ClassMethods
|
99
|
+
module ClassMethods # :nodoc:
|
100
100
|
# Provide a class level cache for #to_partial_path. This is an
|
101
101
|
# internal method and should not be accessed directly.
|
102
|
-
def _to_partial_path
|
102
|
+
def _to_partial_path # :nodoc:
|
103
103
|
@_to_partial_path ||= begin
|
104
104
|
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
|
105
105
|
collection = ActiveSupport::Inflector.tableize(name)
|
data/lib/active_model/dirty.rb
CHANGED
@@ -123,10 +123,11 @@ module ActiveModel
|
|
123
123
|
include ActiveModel::AttributeMethods
|
124
124
|
|
125
125
|
included do
|
126
|
-
attribute_method_suffix "
|
127
|
-
attribute_method_suffix "
|
128
|
-
|
129
|
-
attribute_method_affix prefix: "
|
126
|
+
attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
|
127
|
+
attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
|
128
|
+
attribute_method_suffix "_previous_change", "_previously_was", parameters: false
|
129
|
+
attribute_method_affix prefix: "restore_", suffix: "!", parameters: false
|
130
|
+
attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false
|
130
131
|
end
|
131
132
|
|
132
133
|
def initialize_dup(other) # :nodoc:
|
@@ -140,7 +141,7 @@ module ActiveModel
|
|
140
141
|
end
|
141
142
|
|
142
143
|
def as_json(options = {}) # :nodoc:
|
143
|
-
options[:except] = [options[:except], "mutations_from_database"]
|
144
|
+
options[:except] = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
144
145
|
super(options)
|
145
146
|
end
|
146
147
|
|
data/lib/active_model/errors.rb
CHANGED
@@ -63,12 +63,19 @@ module ActiveModel
|
|
63
63
|
include Enumerable
|
64
64
|
|
65
65
|
extend Forwardable
|
66
|
-
def_delegators :@errors, :size, :clear, :blank?, :empty?, :uniq!, :any?
|
67
|
-
# TODO: forward all enumerable methods after `each` deprecation is removed.
|
68
|
-
def_delegators :@errors, :count
|
69
66
|
|
70
|
-
|
71
|
-
|
67
|
+
# :method: each
|
68
|
+
#
|
69
|
+
# :call-seq: each(&block)
|
70
|
+
#
|
71
|
+
# Iterates through each error object.
|
72
|
+
#
|
73
|
+
# person.errors.add(:name, :too_short, count: 2)
|
74
|
+
# person.errors.each do |error|
|
75
|
+
# # Will yield <#ActiveModel::Error attribute=name, type=too_short,
|
76
|
+
# options={:count=>3}>
|
77
|
+
# end
|
78
|
+
def_delegators :@errors, :each, :clear, :empty?, :size, :uniq!
|
72
79
|
|
73
80
|
# The actual array of +Error+ objects
|
74
81
|
# This method is aliased to <tt>objects</tt>.
|
@@ -133,30 +140,13 @@ module ActiveModel
|
|
133
140
|
#
|
134
141
|
# person.errors.merge!(other)
|
135
142
|
def merge!(other)
|
143
|
+
return errors if equal?(other)
|
144
|
+
|
136
145
|
other.errors.each { |error|
|
137
146
|
import(error)
|
138
147
|
}
|
139
148
|
end
|
140
149
|
|
141
|
-
# Removes all errors except the given keys. Returns a hash containing the removed errors.
|
142
|
-
#
|
143
|
-
# person.errors.keys # => [:name, :age, :gender, :city]
|
144
|
-
# person.errors.slice!(:age, :gender) # => { :name=>["cannot be nil"], :city=>["cannot be nil"] }
|
145
|
-
# person.errors.keys # => [:age, :gender]
|
146
|
-
def slice!(*keys)
|
147
|
-
deprecation_removal_warning(:slice!)
|
148
|
-
|
149
|
-
keys = keys.map(&:to_sym)
|
150
|
-
|
151
|
-
results = messages.dup.slice!(*keys)
|
152
|
-
|
153
|
-
@errors.keep_if do |error|
|
154
|
-
keys.include?(error.attribute)
|
155
|
-
end
|
156
|
-
|
157
|
-
results
|
158
|
-
end
|
159
|
-
|
160
150
|
# Search for errors matching +attribute+, +type+ or +options+.
|
161
151
|
#
|
162
152
|
# Only supplied params will be matched.
|
@@ -205,76 +195,7 @@ module ActiveModel
|
|
205
195
|
# person.errors[:name] # => ["cannot be nil"]
|
206
196
|
# person.errors['name'] # => ["cannot be nil"]
|
207
197
|
def [](attribute)
|
208
|
-
|
209
|
-
end
|
210
|
-
|
211
|
-
# Iterates through each error object.
|
212
|
-
#
|
213
|
-
# person.errors.add(:name, :too_short, count: 2)
|
214
|
-
# person.errors.each do |error|
|
215
|
-
# # Will yield <#ActiveModel::Error attribute=name, type=too_short,
|
216
|
-
# options={:count=>3}>
|
217
|
-
# end
|
218
|
-
#
|
219
|
-
# To be backward compatible with past deprecated hash-like behavior,
|
220
|
-
# when block accepts two parameters instead of one, it
|
221
|
-
# iterates through each error key, value pair in the error messages hash.
|
222
|
-
# Yields the attribute and the error for that attribute. If the attribute
|
223
|
-
# has more than one error message, yields once for each error message.
|
224
|
-
#
|
225
|
-
# person.errors.add(:name, :blank, message: "can't be blank")
|
226
|
-
# person.errors.each do |attribute, message|
|
227
|
-
# # Will yield :name and "can't be blank"
|
228
|
-
# end
|
229
|
-
#
|
230
|
-
# person.errors.add(:name, :not_specified, message: "must be specified")
|
231
|
-
# person.errors.each do |attribute, message|
|
232
|
-
# # Will yield :name and "can't be blank"
|
233
|
-
# # then yield :name and "must be specified"
|
234
|
-
# end
|
235
|
-
def each(&block)
|
236
|
-
if block.arity <= 1
|
237
|
-
@errors.each(&block)
|
238
|
-
else
|
239
|
-
ActiveSupport::Deprecation.warn(<<~MSG)
|
240
|
-
Enumerating ActiveModel::Errors as a hash has been deprecated.
|
241
|
-
In Rails 6.1, `errors` is an array of Error objects,
|
242
|
-
therefore it should be accessed by a block with a single block
|
243
|
-
parameter like this:
|
244
|
-
|
245
|
-
person.errors.each do |error|
|
246
|
-
attribute = error.attribute
|
247
|
-
message = error.message
|
248
|
-
end
|
249
|
-
|
250
|
-
You are passing a block expecting two parameters,
|
251
|
-
so the old hash behavior is simulated. As this is deprecated,
|
252
|
-
this will result in an ArgumentError in Rails 6.2.
|
253
|
-
MSG
|
254
|
-
@errors.
|
255
|
-
sort { |a, b| a.attribute <=> b.attribute }.
|
256
|
-
each { |error| yield error.attribute, error.message }
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
|
-
# Returns all message values.
|
261
|
-
#
|
262
|
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
263
|
-
# person.errors.values # => [["cannot be nil", "must be specified"]]
|
264
|
-
def values
|
265
|
-
deprecation_removal_warning(:values, "errors.map { |error| error.message }")
|
266
|
-
@errors.map(&:message).freeze
|
267
|
-
end
|
268
|
-
|
269
|
-
# Returns all message keys.
|
270
|
-
#
|
271
|
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
|
272
|
-
# person.errors.keys # => [:name]
|
273
|
-
def keys
|
274
|
-
deprecation_removal_warning(:keys, "errors.attribute_names")
|
275
|
-
keys = @errors.map(&:attribute)
|
276
|
-
keys.uniq!
|
277
|
-
keys.freeze
|
198
|
+
messages_for(attribute)
|
278
199
|
end
|
279
200
|
|
280
201
|
# Returns all error attribute names
|
@@ -285,22 +206,6 @@ module ActiveModel
|
|
285
206
|
@errors.map(&:attribute).uniq.freeze
|
286
207
|
end
|
287
208
|
|
288
|
-
# Returns an xml formatted representation of the Errors hash.
|
289
|
-
#
|
290
|
-
# person.errors.add(:name, :blank, message: "can't be blank")
|
291
|
-
# person.errors.add(:name, :not_specified, message: "must be specified")
|
292
|
-
# person.errors.to_xml
|
293
|
-
# # =>
|
294
|
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
295
|
-
# # <errors>
|
296
|
-
# # <error>name can't be blank</error>
|
297
|
-
# # <error>name must be specified</error>
|
298
|
-
# # </errors>
|
299
|
-
def to_xml(options = {})
|
300
|
-
deprecation_removal_warning(:to_xml)
|
301
|
-
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
|
302
|
-
end
|
303
|
-
|
304
209
|
# Returns a Hash that can be used as the JSON representation for this
|
305
210
|
# object. You can pass the <tt>:full_messages</tt> option. This determines
|
306
211
|
# if the json object should contain full messages or not (false by default).
|
@@ -323,33 +228,26 @@ module ActiveModel
|
|
323
228
|
end
|
324
229
|
end
|
325
230
|
|
326
|
-
|
327
|
-
ActiveSupport::Deprecation.warn(<<~EOM)
|
328
|
-
ActiveModel::Errors#to_h is deprecated and will be removed in Rails 6.2.
|
329
|
-
Please use `ActiveModel::Errors.to_hash` instead. The values in the hash
|
330
|
-
returned by `ActiveModel::Errors.to_hash` is an array of error messages.
|
331
|
-
EOM
|
231
|
+
undef :to_h
|
332
232
|
|
333
|
-
|
334
|
-
end
|
233
|
+
EMPTY_ARRAY = [].freeze # :nodoc:
|
335
234
|
|
336
235
|
# Returns a Hash of attributes with an array of their error messages.
|
337
|
-
#
|
338
|
-
# Updating this hash would still update errors state for backward
|
339
|
-
# compatibility, but this behavior is deprecated.
|
340
236
|
def messages
|
341
|
-
|
237
|
+
hash = to_hash
|
238
|
+
hash.default = EMPTY_ARRAY
|
239
|
+
hash.freeze
|
240
|
+
hash
|
342
241
|
end
|
343
242
|
|
344
243
|
# Returns a Hash of attributes with an array of their error details.
|
345
|
-
#
|
346
|
-
# Updating this hash would still update errors state for backward
|
347
|
-
# compatibility, but this behavior is deprecated.
|
348
244
|
def details
|
349
245
|
hash = group_by_attribute.transform_values do |errors|
|
350
246
|
errors.map(&:details)
|
351
247
|
end
|
352
|
-
|
248
|
+
hash.default = EMPTY_ARRAY
|
249
|
+
hash.freeze
|
250
|
+
hash
|
353
251
|
end
|
354
252
|
|
355
253
|
# Returns a Hash of attributes with an array of their Error objects.
|
@@ -378,6 +276,14 @@ module ActiveModel
|
|
378
276
|
# If +type+ is a symbol, it will be translated using the appropriate
|
379
277
|
# scope (see +generate_message+).
|
380
278
|
#
|
279
|
+
# person.errors.add(:name, :blank)
|
280
|
+
# person.errors.messages
|
281
|
+
# # => {:name=>["can't be blank"]}
|
282
|
+
#
|
283
|
+
# person.errors.add(:name, :too_long, { count: 25 })
|
284
|
+
# person.errors.messages
|
285
|
+
# # => ["is too long (maximum is 25 characters)"]
|
286
|
+
#
|
381
287
|
# If +type+ is a proc, it will be called, allowing for things like
|
382
288
|
# <tt>Time.now</tt> to be used within an error.
|
383
289
|
#
|
@@ -542,25 +448,10 @@ module ActiveModel
|
|
542
448
|
Error.generate_message(attribute, type, @base, options)
|
543
449
|
end
|
544
450
|
|
545
|
-
def
|
546
|
-
|
547
|
-
@errors = []
|
548
|
-
@base = array[0]
|
549
|
-
add_from_legacy_details_hash(array[2])
|
550
|
-
end
|
551
|
-
|
552
|
-
def init_with(coder) # :nodoc:
|
553
|
-
data = coder.map
|
451
|
+
def inspect # :nodoc:
|
452
|
+
inspection = @errors.inspect
|
554
453
|
|
555
|
-
|
556
|
-
next if LEGACY_ATTRIBUTES.include?(k.to_sym)
|
557
|
-
instance_variable_set(:"@#{k}", v)
|
558
|
-
}
|
559
|
-
|
560
|
-
@errors ||= []
|
561
|
-
|
562
|
-
# Legacy support Rails 5.x details hash
|
563
|
-
add_from_legacy_details_hash(data["details"]) if data.key?("details")
|
454
|
+
"#<#{self.class.name} #{inspection}>"
|
564
455
|
end
|
565
456
|
|
566
457
|
private
|
@@ -572,97 +463,6 @@ module ActiveModel
|
|
572
463
|
|
573
464
|
[attribute.to_sym, type, options]
|
574
465
|
end
|
575
|
-
|
576
|
-
def add_from_legacy_details_hash(details)
|
577
|
-
details.each { |attribute, errors|
|
578
|
-
errors.each { |error|
|
579
|
-
type = error.delete(:error)
|
580
|
-
add(attribute, type, **error)
|
581
|
-
}
|
582
|
-
}
|
583
|
-
end
|
584
|
-
|
585
|
-
def deprecation_removal_warning(method_name, alternative_message = nil)
|
586
|
-
message = +"ActiveModel::Errors##{method_name} is deprecated and will be removed in Rails 6.2."
|
587
|
-
if alternative_message
|
588
|
-
message << "\n\nTo achieve the same use:\n\n "
|
589
|
-
message << alternative_message
|
590
|
-
end
|
591
|
-
ActiveSupport::Deprecation.warn(message)
|
592
|
-
end
|
593
|
-
|
594
|
-
def deprecation_rename_warning(old_method_name, new_method_name)
|
595
|
-
ActiveSupport::Deprecation.warn("ActiveModel::Errors##{old_method_name} is deprecated. Please call ##{new_method_name} instead.")
|
596
|
-
end
|
597
|
-
end
|
598
|
-
|
599
|
-
class DeprecationHandlingMessageHash < SimpleDelegator
|
600
|
-
def initialize(errors)
|
601
|
-
@errors = errors
|
602
|
-
super(prepare_content)
|
603
|
-
end
|
604
|
-
|
605
|
-
def []=(attribute, value)
|
606
|
-
ActiveSupport::Deprecation.warn("Calling `[]=` to an ActiveModel::Errors is deprecated. Please call `ActiveModel::Errors#add` instead.")
|
607
|
-
|
608
|
-
@errors.delete(attribute)
|
609
|
-
Array(value).each do |message|
|
610
|
-
@errors.add(attribute, message)
|
611
|
-
end
|
612
|
-
|
613
|
-
__setobj__ prepare_content
|
614
|
-
end
|
615
|
-
|
616
|
-
def delete(attribute)
|
617
|
-
ActiveSupport::Deprecation.warn("Calling `delete` to an ActiveModel::Errors messages hash is deprecated. Please call `ActiveModel::Errors#delete` instead.")
|
618
|
-
|
619
|
-
@errors.delete(attribute)
|
620
|
-
end
|
621
|
-
|
622
|
-
private
|
623
|
-
def prepare_content
|
624
|
-
content = @errors.to_hash
|
625
|
-
content.each do |attribute, value|
|
626
|
-
content[attribute] = DeprecationHandlingMessageArray.new(value, @errors, attribute)
|
627
|
-
end
|
628
|
-
content.default_proc = proc do |hash, attribute|
|
629
|
-
hash = hash.dup
|
630
|
-
hash[attribute] = DeprecationHandlingMessageArray.new([], @errors, attribute)
|
631
|
-
__setobj__ hash.freeze
|
632
|
-
hash[attribute]
|
633
|
-
end
|
634
|
-
content.freeze
|
635
|
-
end
|
636
|
-
end
|
637
|
-
|
638
|
-
class DeprecationHandlingMessageArray < SimpleDelegator
|
639
|
-
def initialize(content, errors, attribute)
|
640
|
-
@errors = errors
|
641
|
-
@attribute = attribute
|
642
|
-
super(content.freeze)
|
643
|
-
end
|
644
|
-
|
645
|
-
def <<(message)
|
646
|
-
ActiveSupport::Deprecation.warn("Calling `<<` to an ActiveModel::Errors message array in order to add an error is deprecated. Please call `ActiveModel::Errors#add` instead.")
|
647
|
-
|
648
|
-
@errors.add(@attribute, message)
|
649
|
-
__setobj__ @errors.messages_for(@attribute)
|
650
|
-
self
|
651
|
-
end
|
652
|
-
|
653
|
-
def clear
|
654
|
-
ActiveSupport::Deprecation.warn("Calling `clear` to an ActiveModel::Errors message array in order to delete all errors is deprecated. Please call `ActiveModel::Errors#delete` instead.")
|
655
|
-
|
656
|
-
@errors.delete(@attribute)
|
657
|
-
end
|
658
|
-
end
|
659
|
-
|
660
|
-
class DeprecationHandlingDetailsHash < SimpleDelegator
|
661
|
-
def initialize(details)
|
662
|
-
details.default = []
|
663
|
-
details.freeze
|
664
|
-
super(details)
|
665
|
-
end
|
666
466
|
end
|
667
467
|
|
668
468
|
# Raised when a validation cannot be corrected by end users and are considered
|
data/lib/active_model/model.rb
CHANGED
@@ -3,11 +3,10 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
# == Active \Model \Basic \Model
|
5
5
|
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
# hash of attributes, pretty much like Active Record does.
|
6
|
+
# Allows implementing models similar to <tt>ActiveRecord::Base</tt>.
|
7
|
+
# Includes <tt>ActiveModel::API</tt> for the required interface for an
|
8
|
+
# object to interact with Action Pack and Action View, but can be
|
9
|
+
# extended with other functionalities.
|
11
10
|
#
|
12
11
|
# A minimal implementation could be:
|
13
12
|
#
|
@@ -20,23 +19,7 @@ module ActiveModel
|
|
20
19
|
# person.name # => "bob"
|
21
20
|
# person.age # => "18"
|
22
21
|
#
|
23
|
-
#
|
24
|
-
# to return +false+, which is the most common case. You may want to override
|
25
|
-
# it in your class to simulate a different scenario:
|
26
|
-
#
|
27
|
-
# class Person
|
28
|
-
# include ActiveModel::Model
|
29
|
-
# attr_accessor :id, :name
|
30
|
-
#
|
31
|
-
# def persisted?
|
32
|
-
# self.id == 1
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
#
|
36
|
-
# person = Person.new(id: 1, name: 'bob')
|
37
|
-
# person.persisted? # => true
|
38
|
-
#
|
39
|
-
# Also, if for some reason you need to run code on <tt>initialize</tt>, make
|
22
|
+
# If for some reason you need to run code on <tt>initialize</tt>, make
|
40
23
|
# sure you call +super+ if you want the attributes hash initialization to
|
41
24
|
# happen.
|
42
25
|
#
|
@@ -58,42 +41,6 @@ module ActiveModel
|
|
58
41
|
# (see below).
|
59
42
|
module Model
|
60
43
|
extend ActiveSupport::Concern
|
61
|
-
include ActiveModel::
|
62
|
-
include ActiveModel::Validations
|
63
|
-
include ActiveModel::Conversion
|
64
|
-
|
65
|
-
included do
|
66
|
-
extend ActiveModel::Naming
|
67
|
-
extend ActiveModel::Translation
|
68
|
-
end
|
69
|
-
|
70
|
-
# Initializes a new model with the given +params+.
|
71
|
-
#
|
72
|
-
# class Person
|
73
|
-
# include ActiveModel::Model
|
74
|
-
# attr_accessor :name, :age
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# person = Person.new(name: 'bob', age: '18')
|
78
|
-
# person.name # => "bob"
|
79
|
-
# person.age # => "18"
|
80
|
-
def initialize(attributes = {})
|
81
|
-
assign_attributes(attributes) if attributes
|
82
|
-
|
83
|
-
super()
|
84
|
-
end
|
85
|
-
|
86
|
-
# Indicates if the model is persisted. Default is +false+.
|
87
|
-
#
|
88
|
-
# class Person
|
89
|
-
# include ActiveModel::Model
|
90
|
-
# attr_accessor :id, :name
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# person = Person.new(id: 1, name: 'bob')
|
94
|
-
# person.persisted? # => false
|
95
|
-
def persisted?
|
96
|
-
false
|
97
|
-
end
|
44
|
+
include ActiveModel::API
|
98
45
|
end
|
99
46
|
end
|
data/lib/active_model/naming.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "active_support/core_ext/hash/except"
|
4
4
|
require "active_support/core_ext/module/introspection"
|
5
5
|
require "active_support/core_ext/module/redefine_method"
|
6
|
+
require "active_support/core_ext/module/delegation"
|
6
7
|
|
7
8
|
module ActiveModel
|
8
9
|
class Name
|
@@ -153,6 +154,7 @@ module ActiveModel
|
|
153
154
|
# Returns a new ActiveModel::Name instance. By default, the +namespace+
|
154
155
|
# and +name+ option will take the namespace and name of the given class
|
155
156
|
# respectively.
|
157
|
+
# Use +locale+ argument for singularize and pluralize model name.
|
156
158
|
#
|
157
159
|
# module Foo
|
158
160
|
# class Bar
|
@@ -161,7 +163,7 @@ module ActiveModel
|
|
161
163
|
#
|
162
164
|
# ActiveModel::Name.new(Foo::Bar).to_s
|
163
165
|
# # => "Foo::Bar"
|
164
|
-
def initialize(klass, namespace = nil, name = nil)
|
166
|
+
def initialize(klass, namespace = nil, name = nil, locale = :en)
|
165
167
|
@name = name || klass.name
|
166
168
|
|
167
169
|
raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
|
@@ -169,16 +171,17 @@ module ActiveModel
|
|
169
171
|
@unnamespaced = @name.delete_prefix("#{namespace.name}::") if namespace
|
170
172
|
@klass = klass
|
171
173
|
@singular = _singularize(@name)
|
172
|
-
@plural = ActiveSupport::Inflector.pluralize(@singular)
|
174
|
+
@plural = ActiveSupport::Inflector.pluralize(@singular, locale)
|
175
|
+
@uncountable = @plural == @singular
|
173
176
|
@element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
|
174
177
|
@human = ActiveSupport::Inflector.humanize(@element)
|
175
178
|
@collection = ActiveSupport::Inflector.tableize(@name)
|
176
179
|
@param_key = (namespace ? _singularize(@unnamespaced) : @singular)
|
177
180
|
@i18n_key = @name.underscore.to_sym
|
178
181
|
|
179
|
-
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
|
180
|
-
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
|
181
|
-
@route_key << "_index" if @
|
182
|
+
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key, locale) : @plural.dup)
|
183
|
+
@singular_route_key = ActiveSupport::Inflector.singularize(@route_key, locale)
|
184
|
+
@route_key << "_index" if @uncountable
|
182
185
|
end
|
183
186
|
|
184
187
|
# Transform the model name into a more human format, using I18n. By default,
|
@@ -206,6 +209,10 @@ module ActiveModel
|
|
206
209
|
I18n.translate(defaults.shift, **options)
|
207
210
|
end
|
208
211
|
|
212
|
+
def uncountable?
|
213
|
+
@uncountable
|
214
|
+
end
|
215
|
+
|
209
216
|
private
|
210
217
|
def _singularize(string)
|
211
218
|
ActiveSupport::Inflector.underscore(string).tr("/", "_")
|
@@ -232,7 +239,7 @@ module ActiveModel
|
|
232
239
|
# is required to pass the \Active \Model Lint test. So either extending the
|
233
240
|
# provided method below, or rolling your own is required.
|
234
241
|
module Naming
|
235
|
-
def self.extended(base)
|
242
|
+
def self.extended(base) # :nodoc:
|
236
243
|
base.silence_redefinition_of_method :model_name
|
237
244
|
base.delegate :model_name, to: :class
|
238
245
|
end
|
@@ -279,7 +286,7 @@ module ActiveModel
|
|
279
286
|
# ActiveModel::Naming.uncountable?(Sheep) # => true
|
280
287
|
# ActiveModel::Naming.uncountable?(Post) # => false
|
281
288
|
def self.uncountable?(record_or_class)
|
282
|
-
|
289
|
+
model_name_from_record_or_class(record_or_class).uncountable?
|
283
290
|
end
|
284
291
|
|
285
292
|
# Returns string to use while generating route names. It differs for
|
@@ -321,7 +328,7 @@ module ActiveModel
|
|
321
328
|
model_name_from_record_or_class(record_or_class).param_key
|
322
329
|
end
|
323
330
|
|
324
|
-
def self.model_name_from_record_or_class(record_or_class)
|
331
|
+
def self.model_name_from_record_or_class(record_or_class) # :nodoc:
|
325
332
|
if record_or_class.respond_to?(:to_model)
|
326
333
|
record_or_class.to_model.model_name
|
327
334
|
else
|
@@ -94,6 +94,7 @@ module ActiveModel
|
|
94
94
|
|
95
95
|
define_method("#{attribute}=") do |unencrypted_password|
|
96
96
|
if unencrypted_password.nil?
|
97
|
+
instance_variable_set("@#{attribute}", nil)
|
97
98
|
self.public_send("#{attribute}_digest=", nil)
|
98
99
|
elsif !unencrypted_password.empty?
|
99
100
|
instance_variable_set("@#{attribute}", unencrypted_password)
|
@@ -118,7 +119,7 @@ module ActiveModel
|
|
118
119
|
# user.authenticate_password('mUc3m00RsqyRe') # => user
|
119
120
|
define_method("authenticate_#{attribute}") do |unencrypted_password|
|
120
121
|
attribute_digest = public_send("#{attribute}_digest")
|
121
|
-
BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
122
|
+
attribute_digest.present? && BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
|
122
123
|
end
|
123
124
|
|
124
125
|
alias_method :authenticate, :authenticate_password if attribute == :password
|
@@ -123,7 +123,7 @@ module ActiveModel
|
|
123
123
|
# user.serializable_hash(include: { notes: { only: 'title' }})
|
124
124
|
# # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
|
125
125
|
def serializable_hash(options = nil)
|
126
|
-
attribute_names =
|
126
|
+
attribute_names = self.attribute_names
|
127
127
|
|
128
128
|
return serializable_attributes(attribute_names) if options.blank?
|
129
129
|
|
@@ -148,6 +148,11 @@ module ActiveModel
|
|
148
148
|
hash
|
149
149
|
end
|
150
150
|
|
151
|
+
# Returns an array of attribute names as strings
|
152
|
+
def attribute_names # :nodoc:
|
153
|
+
attributes.keys
|
154
|
+
end
|
155
|
+
|
151
156
|
private
|
152
157
|
# Hook method defining how an attribute value should be retrieved for
|
153
158
|
# serialization. By default this is assumed to be an instance named after
|
@@ -177,7 +182,7 @@ module ActiveModel
|
|
177
182
|
# +association+ - name of the association
|
178
183
|
# +records+ - the association record(s) to be serialized
|
179
184
|
# +opts+ - options for the association records
|
180
|
-
def serializable_add_includes(options = {})
|
185
|
+
def serializable_add_includes(options = {}) # :nodoc:
|
181
186
|
return unless includes = options[:include]
|
182
187
|
|
183
188
|
unless includes.is_a?(Hash)
|