omg-activemodel 8.0.0.alpha1
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 +7 -0
- data/CHANGELOG.md +67 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +266 -0
- data/lib/active_model/access.rb +16 -0
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute/user_provided_default.rb +55 -0
- data/lib/active_model/attribute.rb +277 -0
- data/lib/active_model/attribute_assignment.rb +78 -0
- data/lib/active_model/attribute_methods.rb +592 -0
- data/lib/active_model/attribute_mutation_tracker.rb +189 -0
- data/lib/active_model/attribute_registration.rb +117 -0
- data/lib/active_model/attribute_set/builder.rb +182 -0
- data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
- data/lib/active_model/attribute_set.rb +118 -0
- data/lib/active_model/attributes.rb +165 -0
- data/lib/active_model/callbacks.rb +155 -0
- data/lib/active_model/conversion.rb +121 -0
- data/lib/active_model/deprecator.rb +7 -0
- data/lib/active_model/dirty.rb +416 -0
- data/lib/active_model/error.rb +208 -0
- data/lib/active_model/errors.rb +547 -0
- data/lib/active_model/forbidden_attributes_protection.rb +33 -0
- data/lib/active_model/gem_version.rb +17 -0
- data/lib/active_model/lint.rb +118 -0
- data/lib/active_model/locale/en.yml +38 -0
- data/lib/active_model/model.rb +78 -0
- data/lib/active_model/naming.rb +359 -0
- data/lib/active_model/nested_error.rb +22 -0
- data/lib/active_model/railtie.rb +24 -0
- data/lib/active_model/secure_password.rb +231 -0
- data/lib/active_model/serialization.rb +198 -0
- data/lib/active_model/serializers/json.rb +154 -0
- data/lib/active_model/translation.rb +78 -0
- data/lib/active_model/type/big_integer.rb +36 -0
- data/lib/active_model/type/binary.rb +62 -0
- data/lib/active_model/type/boolean.rb +48 -0
- data/lib/active_model/type/date.rb +78 -0
- data/lib/active_model/type/date_time.rb +88 -0
- data/lib/active_model/type/decimal.rb +107 -0
- data/lib/active_model/type/float.rb +64 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
- data/lib/active_model/type/helpers/mutable.rb +24 -0
- data/lib/active_model/type/helpers/numeric.rb +61 -0
- data/lib/active_model/type/helpers/time_value.rb +127 -0
- data/lib/active_model/type/helpers/timezone.rb +23 -0
- data/lib/active_model/type/helpers.rb +7 -0
- data/lib/active_model/type/immutable_string.rb +71 -0
- data/lib/active_model/type/integer.rb +113 -0
- data/lib/active_model/type/registry.rb +37 -0
- data/lib/active_model/type/serialize_cast_value.rb +47 -0
- data/lib/active_model/type/string.rb +43 -0
- data/lib/active_model/type/time.rb +87 -0
- data/lib/active_model/type/value.rb +157 -0
- data/lib/active_model/type.rb +55 -0
- data/lib/active_model/validations/absence.rb +33 -0
- data/lib/active_model/validations/acceptance.rb +113 -0
- data/lib/active_model/validations/callbacks.rb +119 -0
- data/lib/active_model/validations/clusivity.rb +54 -0
- data/lib/active_model/validations/comparability.rb +18 -0
- data/lib/active_model/validations/comparison.rb +90 -0
- data/lib/active_model/validations/confirmation.rb +80 -0
- data/lib/active_model/validations/exclusion.rb +49 -0
- data/lib/active_model/validations/format.rb +112 -0
- data/lib/active_model/validations/helper_methods.rb +15 -0
- data/lib/active_model/validations/inclusion.rb +47 -0
- data/lib/active_model/validations/length.rb +130 -0
- data/lib/active_model/validations/numericality.rb +222 -0
- data/lib/active_model/validations/presence.rb +39 -0
- data/lib/active_model/validations/resolve_value.rb +26 -0
- data/lib/active_model/validations/validates.rb +175 -0
- data/lib/active_model/validations/with.rb +154 -0
- data/lib/active_model/validations.rb +489 -0
- data/lib/active_model/validator.rb +190 -0
- data/lib/active_model/version.rb +10 -0
- data/lib/active_model.rb +84 -0
- metadata +139 -0
@@ -0,0 +1,416 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/attribute_mutation_tracker"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
# = Active \Model \Dirty
|
7
|
+
#
|
8
|
+
# Provides a way to track changes in your object in the same way as
|
9
|
+
# Active Record does.
|
10
|
+
#
|
11
|
+
# The requirements for implementing ActiveModel::Dirty are:
|
12
|
+
#
|
13
|
+
# * <tt>include ActiveModel::Dirty</tt> in your object.
|
14
|
+
# * Call <tt>define_attribute_methods</tt> passing each method you want to
|
15
|
+
# track.
|
16
|
+
# * Call <tt>*_will_change!</tt> before each change to the tracked attribute.
|
17
|
+
# * Call <tt>changes_applied</tt> after the changes are persisted.
|
18
|
+
# * Call <tt>clear_changes_information</tt> when you want to reset the changes
|
19
|
+
# information.
|
20
|
+
# * Call <tt>restore_attributes</tt> when you want to restore previous data.
|
21
|
+
#
|
22
|
+
# A minimal implementation could be:
|
23
|
+
#
|
24
|
+
# class Person
|
25
|
+
# include ActiveModel::Dirty
|
26
|
+
#
|
27
|
+
# define_attribute_methods :name
|
28
|
+
#
|
29
|
+
# def initialize
|
30
|
+
# @name = nil
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def name
|
34
|
+
# @name
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# def name=(val)
|
38
|
+
# name_will_change! unless val == @name
|
39
|
+
# @name = val
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# def save
|
43
|
+
# # do persistence work
|
44
|
+
#
|
45
|
+
# changes_applied
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# def reload!
|
49
|
+
# # get the values from the persistence layer
|
50
|
+
#
|
51
|
+
# clear_changes_information
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# def rollback!
|
55
|
+
# restore_attributes
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# A newly instantiated +Person+ object is unchanged:
|
60
|
+
#
|
61
|
+
# person = Person.new
|
62
|
+
# person.changed? # => false
|
63
|
+
#
|
64
|
+
# Change the name:
|
65
|
+
#
|
66
|
+
# person.name = 'Bob'
|
67
|
+
# person.changed? # => true
|
68
|
+
# person.name_changed? # => true
|
69
|
+
# person.name_changed?(from: nil, to: "Bob") # => true
|
70
|
+
# person.name_was # => nil
|
71
|
+
# person.name_change # => [nil, "Bob"]
|
72
|
+
# person.name = 'Bill'
|
73
|
+
# person.name_change # => [nil, "Bill"]
|
74
|
+
#
|
75
|
+
# Save the changes:
|
76
|
+
#
|
77
|
+
# person.save
|
78
|
+
# person.changed? # => false
|
79
|
+
# person.name_changed? # => false
|
80
|
+
#
|
81
|
+
# Reset the changes:
|
82
|
+
#
|
83
|
+
# person.previous_changes # => {"name" => [nil, "Bill"]}
|
84
|
+
# person.name_previously_changed? # => true
|
85
|
+
# person.name_previously_changed?(from: nil, to: "Bill") # => true
|
86
|
+
# person.name_previous_change # => [nil, "Bill"]
|
87
|
+
# person.name_previously_was # => nil
|
88
|
+
# person.reload!
|
89
|
+
# person.previous_changes # => {}
|
90
|
+
#
|
91
|
+
# Rollback the changes:
|
92
|
+
#
|
93
|
+
# person.name = "Uncle Bob"
|
94
|
+
# person.rollback!
|
95
|
+
# person.name # => "Bill"
|
96
|
+
# person.name_changed? # => false
|
97
|
+
#
|
98
|
+
# Assigning the same value leaves the attribute unchanged:
|
99
|
+
#
|
100
|
+
# person.name = 'Bill'
|
101
|
+
# person.name_changed? # => false
|
102
|
+
# person.name_change # => nil
|
103
|
+
#
|
104
|
+
# Which attributes have changed?
|
105
|
+
#
|
106
|
+
# person.name = 'Bob'
|
107
|
+
# person.changed # => ["name"]
|
108
|
+
# person.changes # => {"name" => ["Bill", "Bob"]}
|
109
|
+
#
|
110
|
+
# If an attribute is modified in-place then make use of
|
111
|
+
# {*_will_change!}[rdoc-label:method-i-2A_will_change-21] to mark that the attribute is changing.
|
112
|
+
# Otherwise \Active \Model can't track changes to in-place attributes. Note
|
113
|
+
# that Active Record can detect in-place modifications automatically. You do
|
114
|
+
# not need to call <tt>*_will_change!</tt> on Active Record models.
|
115
|
+
#
|
116
|
+
# person.name_will_change!
|
117
|
+
# person.name_change # => ["Bill", "Bill"]
|
118
|
+
# person.name << 'y'
|
119
|
+
# person.name_change # => ["Bill", "Billy"]
|
120
|
+
#
|
121
|
+
# Methods can be invoked as +name_changed?+ or by passing an argument to the
|
122
|
+
# generic method <tt>attribute_changed?("name")</tt>.
|
123
|
+
module Dirty
|
124
|
+
extend ActiveSupport::Concern
|
125
|
+
include ActiveModel::AttributeMethods
|
126
|
+
|
127
|
+
included do
|
128
|
+
##
|
129
|
+
# :method: *_previously_changed?
|
130
|
+
#
|
131
|
+
# :call-seq: *_previously_changed?(**options)
|
132
|
+
#
|
133
|
+
# This method is generated for each attribute.
|
134
|
+
#
|
135
|
+
# Returns true if the attribute previously had unsaved changes.
|
136
|
+
#
|
137
|
+
# person = Person.new
|
138
|
+
# person.name = 'Britanny'
|
139
|
+
# person.save
|
140
|
+
# person.name_previously_changed? # => true
|
141
|
+
# person.name_previously_changed?(from: nil, to: 'Britanny') # => true
|
142
|
+
|
143
|
+
##
|
144
|
+
# :method: *_changed?
|
145
|
+
#
|
146
|
+
# This method is generated for each attribute.
|
147
|
+
#
|
148
|
+
# Returns true if the attribute has unsaved changes.
|
149
|
+
#
|
150
|
+
# person = Person.new
|
151
|
+
# person.name = 'Andrew'
|
152
|
+
# person.name_changed? # => true
|
153
|
+
|
154
|
+
##
|
155
|
+
# :method: *_change
|
156
|
+
#
|
157
|
+
# This method is generated for each attribute.
|
158
|
+
#
|
159
|
+
# Returns the old and the new value of the attribute.
|
160
|
+
#
|
161
|
+
# person = Person.new
|
162
|
+
# person.name = 'Nick'
|
163
|
+
# person.name_change # => [nil, 'Nick']
|
164
|
+
|
165
|
+
##
|
166
|
+
# :method: *_will_change!
|
167
|
+
#
|
168
|
+
# This method is generated for each attribute.
|
169
|
+
#
|
170
|
+
# If an attribute is modified in-place then make use of
|
171
|
+
# <tt>*_will_change!</tt> to mark that the attribute is changing.
|
172
|
+
# Otherwise Active Model can’t track changes to in-place attributes. Note
|
173
|
+
# that Active Record can detect in-place modifications automatically. You
|
174
|
+
# do not need to call <tt>*_will_change!</tt> on Active Record
|
175
|
+
# models.
|
176
|
+
#
|
177
|
+
# person = Person.new('Sandy')
|
178
|
+
# person.name_will_change!
|
179
|
+
# person.name_change # => ['Sandy', 'Sandy']
|
180
|
+
|
181
|
+
##
|
182
|
+
# :method: *_was
|
183
|
+
#
|
184
|
+
# This method is generated for each attribute.
|
185
|
+
#
|
186
|
+
# Returns the old value of the attribute.
|
187
|
+
#
|
188
|
+
# person = Person.new(name: 'Steph')
|
189
|
+
# person.name = 'Stephanie'
|
190
|
+
# person.name_was # => 'Steph'
|
191
|
+
|
192
|
+
##
|
193
|
+
# :method: *_previous_change
|
194
|
+
#
|
195
|
+
# This method is generated for each attribute.
|
196
|
+
#
|
197
|
+
# Returns the old and the new value of the attribute before the last save.
|
198
|
+
#
|
199
|
+
# person = Person.new
|
200
|
+
# person.name = 'Emmanuel'
|
201
|
+
# person.save
|
202
|
+
# person.name_previous_change # => [nil, 'Emmanuel']
|
203
|
+
|
204
|
+
##
|
205
|
+
# :method: *_previously_was
|
206
|
+
#
|
207
|
+
# This method is generated for each attribute.
|
208
|
+
#
|
209
|
+
# Returns the old value of the attribute before the last save.
|
210
|
+
#
|
211
|
+
# person = Person.new
|
212
|
+
# person.name = 'Sage'
|
213
|
+
# person.save
|
214
|
+
# person.name_previously_was # => nil
|
215
|
+
|
216
|
+
##
|
217
|
+
# :method: restore_*!
|
218
|
+
#
|
219
|
+
# This method is generated for each attribute.
|
220
|
+
#
|
221
|
+
# Restores the attribute to the old value.
|
222
|
+
#
|
223
|
+
# person = Person.new
|
224
|
+
# person.name = 'Amanda'
|
225
|
+
# person.restore_name!
|
226
|
+
# person.name # => nil
|
227
|
+
|
228
|
+
##
|
229
|
+
# :method: clear_*_change
|
230
|
+
#
|
231
|
+
# This method is generated for each attribute.
|
232
|
+
#
|
233
|
+
# Clears all dirty data of the attribute: current changes and previous changes.
|
234
|
+
#
|
235
|
+
# person = Person.new(name: 'Chris')
|
236
|
+
# person.name = 'Jason'
|
237
|
+
# person.name_change # => ['Chris', 'Jason']
|
238
|
+
# person.clear_name_change
|
239
|
+
# person.name_change # => nil
|
240
|
+
|
241
|
+
attribute_method_suffix "_previously_changed?", "_changed?", parameters: "**options"
|
242
|
+
attribute_method_suffix "_change", "_will_change!", "_was", parameters: false
|
243
|
+
attribute_method_suffix "_previous_change", "_previously_was", parameters: false
|
244
|
+
attribute_method_affix prefix: "restore_", suffix: "!", parameters: false
|
245
|
+
attribute_method_affix prefix: "clear_", suffix: "_change", parameters: false
|
246
|
+
end
|
247
|
+
|
248
|
+
def initialize_dup(other) # :nodoc:
|
249
|
+
super
|
250
|
+
if self.class.respond_to?(:_default_attributes)
|
251
|
+
@attributes = self.class._default_attributes.map do |attr|
|
252
|
+
attr.with_value_from_user(@attributes.fetch_value(attr.name))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
@mutations_from_database = nil
|
256
|
+
end
|
257
|
+
|
258
|
+
def as_json(options = {}) # :nodoc:
|
259
|
+
except = [*options[:except], "mutations_from_database", "mutations_before_last_save"]
|
260
|
+
options = options.merge except: except
|
261
|
+
super(options)
|
262
|
+
end
|
263
|
+
|
264
|
+
# Clears dirty data and moves +changes+ to +previous_changes+ and
|
265
|
+
# +mutations_from_database+ to +mutations_before_last_save+ respectively.
|
266
|
+
def changes_applied
|
267
|
+
unless defined?(@attributes)
|
268
|
+
mutations_from_database.finalize_changes
|
269
|
+
end
|
270
|
+
@mutations_before_last_save = mutations_from_database
|
271
|
+
forget_attribute_assignments
|
272
|
+
@mutations_from_database = nil
|
273
|
+
end
|
274
|
+
|
275
|
+
# Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
|
276
|
+
#
|
277
|
+
# person.changed? # => false
|
278
|
+
# person.name = 'bob'
|
279
|
+
# person.changed? # => true
|
280
|
+
def changed?
|
281
|
+
mutations_from_database.any_changes?
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns an array with the name of the attributes with unsaved changes.
|
285
|
+
#
|
286
|
+
# person.changed # => []
|
287
|
+
# person.name = 'bob'
|
288
|
+
# person.changed # => ["name"]
|
289
|
+
def changed
|
290
|
+
mutations_from_database.changed_attribute_names
|
291
|
+
end
|
292
|
+
|
293
|
+
# Dispatch target for {*_changed?}[rdoc-label:method-i-2A_changed-3F] attribute methods.
|
294
|
+
def attribute_changed?(attr_name, **options)
|
295
|
+
mutations_from_database.changed?(attr_name.to_s, **options)
|
296
|
+
end
|
297
|
+
|
298
|
+
# Dispatch target for {*_was}[rdoc-label:method-i-2A_was] attribute methods.
|
299
|
+
def attribute_was(attr_name)
|
300
|
+
mutations_from_database.original_value(attr_name.to_s)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Dispatch target for {*_previously_changed?}[rdoc-label:method-i-2A_previously_changed-3F] attribute methods.
|
304
|
+
def attribute_previously_changed?(attr_name, **options)
|
305
|
+
mutations_before_last_save.changed?(attr_name.to_s, **options)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Dispatch target for {*_previously_was}[rdoc-label:method-i-2A_previously_was] attribute methods.
|
309
|
+
def attribute_previously_was(attr_name)
|
310
|
+
mutations_before_last_save.original_value(attr_name.to_s)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Restore all previous data of the provided attributes.
|
314
|
+
def restore_attributes(attr_names = changed)
|
315
|
+
attr_names.each { |attr_name| restore_attribute!(attr_name) }
|
316
|
+
end
|
317
|
+
|
318
|
+
# Clears all dirty data: current changes and previous changes.
|
319
|
+
def clear_changes_information
|
320
|
+
@mutations_before_last_save = nil
|
321
|
+
forget_attribute_assignments
|
322
|
+
@mutations_from_database = nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def clear_attribute_changes(attr_names)
|
326
|
+
attr_names.each do |attr_name|
|
327
|
+
clear_attribute_change(attr_name)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
# Returns a hash of the attributes with unsaved changes indicating their original
|
332
|
+
# values like <tt>attr => original value</tt>.
|
333
|
+
#
|
334
|
+
# person.name # => "bob"
|
335
|
+
# person.name = 'robert'
|
336
|
+
# person.changed_attributes # => {"name" => "bob"}
|
337
|
+
def changed_attributes
|
338
|
+
mutations_from_database.changed_values
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns a hash of changed attributes indicating their original
|
342
|
+
# and new values like <tt>attr => [original value, new value]</tt>.
|
343
|
+
#
|
344
|
+
# person.changes # => {}
|
345
|
+
# person.name = 'bob'
|
346
|
+
# person.changes # => { "name" => ["bill", "bob"] }
|
347
|
+
def changes
|
348
|
+
mutations_from_database.changes
|
349
|
+
end
|
350
|
+
|
351
|
+
# Returns a hash of attributes that were changed before the model was saved.
|
352
|
+
#
|
353
|
+
# person.name # => "bob"
|
354
|
+
# person.name = 'robert'
|
355
|
+
# person.save
|
356
|
+
# person.previous_changes # => {"name" => ["bob", "robert"]}
|
357
|
+
def previous_changes
|
358
|
+
mutations_before_last_save.changes
|
359
|
+
end
|
360
|
+
|
361
|
+
def attribute_changed_in_place?(attr_name) # :nodoc:
|
362
|
+
mutations_from_database.changed_in_place?(attr_name.to_s)
|
363
|
+
end
|
364
|
+
|
365
|
+
private
|
366
|
+
def init_internals
|
367
|
+
super
|
368
|
+
@mutations_before_last_save = nil
|
369
|
+
@mutations_from_database = nil
|
370
|
+
end
|
371
|
+
|
372
|
+
def clear_attribute_change(attr_name)
|
373
|
+
mutations_from_database.forget_change(attr_name.to_s)
|
374
|
+
end
|
375
|
+
|
376
|
+
def mutations_from_database
|
377
|
+
@mutations_from_database ||= if defined?(@attributes)
|
378
|
+
ActiveModel::AttributeMutationTracker.new(@attributes)
|
379
|
+
else
|
380
|
+
ActiveModel::ForcedMutationTracker.new(self)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
def forget_attribute_assignments
|
385
|
+
@attributes = @attributes.map(&:forgetting_assignment) if defined?(@attributes)
|
386
|
+
end
|
387
|
+
|
388
|
+
def mutations_before_last_save
|
389
|
+
@mutations_before_last_save ||= ActiveModel::NullMutationTracker.instance
|
390
|
+
end
|
391
|
+
|
392
|
+
# Dispatch target for <tt>*_change</tt> attribute methods.
|
393
|
+
def attribute_change(attr_name)
|
394
|
+
mutations_from_database.change_to_attribute(attr_name.to_s)
|
395
|
+
end
|
396
|
+
|
397
|
+
# Dispatch target for <tt>*_previous_change</tt> attribute methods.
|
398
|
+
def attribute_previous_change(attr_name)
|
399
|
+
mutations_before_last_save.change_to_attribute(attr_name.to_s)
|
400
|
+
end
|
401
|
+
|
402
|
+
# Dispatch target for <tt>*_will_change!</tt> attribute methods.
|
403
|
+
def attribute_will_change!(attr_name)
|
404
|
+
mutations_from_database.force_change(attr_name.to_s)
|
405
|
+
end
|
406
|
+
|
407
|
+
# Dispatch target for <tt>restore_*!</tt> attribute methods.
|
408
|
+
def restore_attribute!(attr_name)
|
409
|
+
attr_name = attr_name.to_s
|
410
|
+
if attribute_changed?(attr_name)
|
411
|
+
__send__("#{attr_name}=", attribute_was(attr_name))
|
412
|
+
clear_attribute_change(attr_name)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/class/attribute"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
# = Active \Model \Error
|
7
|
+
#
|
8
|
+
# Represents one single error
|
9
|
+
class Error
|
10
|
+
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
11
|
+
MESSAGE_OPTIONS = [:message]
|
12
|
+
|
13
|
+
class_attribute :i18n_customize_full_message, default: false
|
14
|
+
|
15
|
+
def self.full_message(attribute, message, base) # :nodoc:
|
16
|
+
return message if attribute == :base
|
17
|
+
|
18
|
+
base_class = base.class
|
19
|
+
attribute = attribute.to_s
|
20
|
+
|
21
|
+
if i18n_customize_full_message && base_class.respond_to?(:i18n_scope)
|
22
|
+
attribute = attribute.remove(/\[\d+\]/)
|
23
|
+
parts = attribute.split(".")
|
24
|
+
attribute_name = parts.pop
|
25
|
+
namespace = parts.join("/") unless parts.empty?
|
26
|
+
attributes_scope = "#{base_class.i18n_scope}.errors.models"
|
27
|
+
|
28
|
+
if namespace
|
29
|
+
defaults = base_class.lookup_ancestors.map do |klass|
|
30
|
+
[
|
31
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format",
|
32
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format",
|
33
|
+
]
|
34
|
+
end
|
35
|
+
else
|
36
|
+
defaults = base_class.lookup_ancestors.map do |klass|
|
37
|
+
[
|
38
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format",
|
39
|
+
:"#{attributes_scope}.#{klass.model_name.i18n_key}.format",
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
defaults.flatten!
|
45
|
+
else
|
46
|
+
defaults = []
|
47
|
+
end
|
48
|
+
|
49
|
+
defaults << :"errors.format"
|
50
|
+
defaults << "%{attribute} %{message}"
|
51
|
+
|
52
|
+
attr_name = attribute.remove(/\.base\z/).tr(".", "_").humanize
|
53
|
+
attr_name = base_class.human_attribute_name(attribute, {
|
54
|
+
default: attr_name,
|
55
|
+
base: base,
|
56
|
+
})
|
57
|
+
|
58
|
+
I18n.t(defaults.shift,
|
59
|
+
default: defaults,
|
60
|
+
attribute: attr_name,
|
61
|
+
message: message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.generate_message(attribute, type, base, options) # :nodoc:
|
65
|
+
type = options.delete(:message) if options[:message].is_a?(Symbol)
|
66
|
+
value = (attribute != :base ? base.read_attribute_for_validation(attribute) : nil)
|
67
|
+
|
68
|
+
options = {
|
69
|
+
model: base.model_name.human,
|
70
|
+
attribute: base.class.human_attribute_name(attribute, { base: base }),
|
71
|
+
value: value,
|
72
|
+
object: base
|
73
|
+
}.merge!(options)
|
74
|
+
|
75
|
+
if base.class.respond_to?(:i18n_scope)
|
76
|
+
i18n_scope = base.class.i18n_scope.to_s
|
77
|
+
attribute = attribute.to_s.remove(/\[\d+\]/)
|
78
|
+
|
79
|
+
defaults = base.class.lookup_ancestors.flat_map do |klass|
|
80
|
+
[ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
81
|
+
:"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
82
|
+
end
|
83
|
+
defaults << :"#{i18n_scope}.errors.messages.#{type}"
|
84
|
+
|
85
|
+
catch(:exception) do
|
86
|
+
translation = I18n.translate(defaults.first, **options.merge(default: defaults.drop(1), throw: true))
|
87
|
+
return translation unless translation.nil?
|
88
|
+
end unless options[:message]
|
89
|
+
else
|
90
|
+
defaults = []
|
91
|
+
end
|
92
|
+
|
93
|
+
defaults << :"errors.attributes.#{attribute}.#{type}"
|
94
|
+
defaults << :"errors.messages.#{type}"
|
95
|
+
|
96
|
+
key = defaults.shift
|
97
|
+
defaults = options.delete(:message) if options[:message]
|
98
|
+
options[:default] = defaults
|
99
|
+
|
100
|
+
I18n.translate(key, **options)
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize(base, attribute, type = :invalid, **options)
|
104
|
+
@base = base
|
105
|
+
@attribute = attribute
|
106
|
+
@raw_type = type
|
107
|
+
@type = type || :invalid
|
108
|
+
@options = options
|
109
|
+
end
|
110
|
+
|
111
|
+
def initialize_dup(other) # :nodoc:
|
112
|
+
@attribute = @attribute.dup
|
113
|
+
@raw_type = @raw_type.dup
|
114
|
+
@type = @type.dup
|
115
|
+
@options = @options.deep_dup
|
116
|
+
end
|
117
|
+
|
118
|
+
# The object which the error belongs to
|
119
|
+
attr_reader :base
|
120
|
+
# The attribute of +base+ which the error belongs to
|
121
|
+
attr_reader :attribute
|
122
|
+
# The type of error, defaults to +:invalid+ unless specified
|
123
|
+
attr_reader :type
|
124
|
+
# The raw value provided as the second parameter when calling
|
125
|
+
# <tt>errors#add</tt>
|
126
|
+
attr_reader :raw_type
|
127
|
+
# The options provided when calling <tt>errors#add</tt>
|
128
|
+
attr_reader :options
|
129
|
+
|
130
|
+
# Returns the error message.
|
131
|
+
#
|
132
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
133
|
+
# error.message
|
134
|
+
# # => "is too short (minimum is 5 characters)"
|
135
|
+
def message
|
136
|
+
case raw_type
|
137
|
+
when Symbol
|
138
|
+
self.class.generate_message(attribute, raw_type, @base, options.except(*CALLBACKS_OPTIONS))
|
139
|
+
else
|
140
|
+
raw_type
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Returns the error details.
|
145
|
+
#
|
146
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
147
|
+
# error.details
|
148
|
+
# # => { error: :too_short, count: 5 }
|
149
|
+
def details
|
150
|
+
{ error: raw_type }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
|
151
|
+
end
|
152
|
+
alias_method :detail, :details
|
153
|
+
|
154
|
+
# Returns the full error message.
|
155
|
+
#
|
156
|
+
# error = ActiveModel::Error.new(person, :name, :too_short, count: 5)
|
157
|
+
# error.full_message
|
158
|
+
# # => "Name is too short (minimum is 5 characters)"
|
159
|
+
def full_message
|
160
|
+
self.class.full_message(attribute, message, @base)
|
161
|
+
end
|
162
|
+
|
163
|
+
# See if error matches provided +attribute+, +type+, and +options+.
|
164
|
+
#
|
165
|
+
# Omitted params are not checked for a match.
|
166
|
+
def match?(attribute, type = nil, **options)
|
167
|
+
if @attribute != attribute || (type && @type != type)
|
168
|
+
return false
|
169
|
+
end
|
170
|
+
|
171
|
+
options.each do |key, value|
|
172
|
+
if @options[key] != value
|
173
|
+
return false
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
true
|
178
|
+
end
|
179
|
+
|
180
|
+
# See if error matches provided +attribute+, +type+, and +options+ exactly.
|
181
|
+
#
|
182
|
+
# All params must be equal to Error's own attributes to be considered a
|
183
|
+
# strict match.
|
184
|
+
def strict_match?(attribute, type, **options)
|
185
|
+
return false unless match?(attribute, type)
|
186
|
+
|
187
|
+
options == @options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)
|
188
|
+
end
|
189
|
+
|
190
|
+
def ==(other) # :nodoc:
|
191
|
+
other.is_a?(self.class) && attributes_for_hash == other.attributes_for_hash
|
192
|
+
end
|
193
|
+
alias eql? ==
|
194
|
+
|
195
|
+
def hash # :nodoc:
|
196
|
+
attributes_for_hash.hash
|
197
|
+
end
|
198
|
+
|
199
|
+
def inspect # :nodoc:
|
200
|
+
"#<#{self.class.name} attribute=#{@attribute}, type=#{@type}, options=#{@options.inspect}>"
|
201
|
+
end
|
202
|
+
|
203
|
+
protected
|
204
|
+
def attributes_for_hash
|
205
|
+
[@base, @attribute, @raw_type, @options.except(*CALLBACKS_OPTIONS)]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|