activemodel 3.2.22.5 → 4.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -64
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +61 -24
  5. data/lib/active_model.rb +21 -11
  6. data/lib/active_model/attribute_methods.rb +150 -125
  7. data/lib/active_model/callbacks.rb +49 -34
  8. data/lib/active_model/conversion.rb +39 -19
  9. data/lib/active_model/deprecated_mass_assignment_security.rb +21 -0
  10. data/lib/active_model/dirty.rb +48 -32
  11. data/lib/active_model/errors.rb +176 -88
  12. data/lib/active_model/forbidden_attributes_protection.rb +27 -0
  13. data/lib/active_model/lint.rb +42 -55
  14. data/lib/active_model/locale/en.yml +3 -1
  15. data/lib/active_model/model.rb +97 -0
  16. data/lib/active_model/naming.rb +191 -51
  17. data/lib/active_model/railtie.rb +11 -1
  18. data/lib/active_model/secure_password.rb +55 -25
  19. data/lib/active_model/serialization.rb +51 -27
  20. data/lib/active_model/serializers/json.rb +83 -46
  21. data/lib/active_model/serializers/xml.rb +46 -12
  22. data/lib/active_model/test_case.rb +0 -12
  23. data/lib/active_model/translation.rb +9 -10
  24. data/lib/active_model/validations.rb +154 -52
  25. data/lib/active_model/validations/absence.rb +31 -0
  26. data/lib/active_model/validations/acceptance.rb +10 -22
  27. data/lib/active_model/validations/callbacks.rb +78 -25
  28. data/lib/active_model/validations/clusivity.rb +41 -0
  29. data/lib/active_model/validations/confirmation.rb +13 -23
  30. data/lib/active_model/validations/exclusion.rb +26 -55
  31. data/lib/active_model/validations/format.rb +44 -34
  32. data/lib/active_model/validations/inclusion.rb +22 -52
  33. data/lib/active_model/validations/length.rb +48 -49
  34. data/lib/active_model/validations/numericality.rb +30 -32
  35. data/lib/active_model/validations/presence.rb +12 -22
  36. data/lib/active_model/validations/validates.rb +68 -36
  37. data/lib/active_model/validations/with.rb +28 -23
  38. data/lib/active_model/validator.rb +22 -22
  39. data/lib/active_model/version.rb +4 -4
  40. metadata +23 -24
  41. data/lib/active_model/mass_assignment_security.rb +0 -237
  42. data/lib/active_model/mass_assignment_security/permission_set.rb +0 -40
  43. data/lib/active_model/mass_assignment_security/sanitizer.rb +0 -59
  44. data/lib/active_model/observer_array.rb +0 -147
  45. data/lib/active_model/observing.rb +0 -252
@@ -1,34 +1,40 @@
1
- require 'active_support/core_ext/hash/keys'
2
- require 'active_support/core_ext/class/attribute'
3
- require 'active_support/deprecation'
1
+ require 'thread_safe'
4
2
 
5
3
  module ActiveModel
4
+ # Raised when an attribute is not defined.
5
+ #
6
+ # class User < ActiveRecord::Base
7
+ # has_many :pets
8
+ # end
9
+ #
10
+ # user = User.first
11
+ # user.pets.select(:id).first.user_id
12
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
6
13
  class MissingAttributeError < NoMethodError
7
14
  end
8
- # == Active Model Attribute Methods
15
+ # == Active \Model Attribute Methods
9
16
  #
10
- # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and suffixes
11
- # to your methods as well as handling the creation of Active Record like class methods
12
- # such as +table_name+.
17
+ # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
18
+ # suffixes to your methods as well as handling the creation of Active Record
19
+ # like class methods such as +table_name+.
13
20
  #
14
21
  # The requirements to implement ActiveModel::AttributeMethods are to:
15
22
  #
16
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object
23
+ # * <tt>include ActiveModel::AttributeMethods</tt> in your object.
17
24
  # * Call each Attribute Method module method you want to add, such as
18
- # attribute_method_suffix or attribute_method_prefix
19
- # * Call <tt>define_attribute_methods</tt> after the other methods are
20
- # called.
21
- # * Define the various generic +_attribute+ methods that you have declared
25
+ # +attribute_method_suffix+ or +attribute_method_prefix+.
26
+ # * Call +define_attribute_methods+ after the other methods are called.
27
+ # * Define the various generic +_attribute+ methods that you have declared.
22
28
  #
23
29
  # A minimal implementation could be:
24
30
  #
25
31
  # class Person
26
32
  # include ActiveModel::AttributeMethods
27
33
  #
28
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
34
+ # attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
29
35
  # attribute_method_suffix '_contrived?'
30
36
  # attribute_method_prefix 'clear_'
31
- # define_attribute_methods ['name']
37
+ # define_attribute_methods :name
32
38
  #
33
39
  # attr_accessor :name
34
40
  #
@@ -43,17 +49,16 @@ module ActiveModel
43
49
  # end
44
50
  #
45
51
  # def reset_attribute_to_default!(attr)
46
- # send("#{attr}=", "Default Name")
52
+ # send("#{attr}=", 'Default Name')
47
53
  # end
48
54
  # end
49
55
  #
50
56
  # Note that whenever you include ActiveModel::AttributeMethods in your class,
51
- # it requires you to implement an <tt>attributes</tt> method which returns a hash
57
+ # it requires you to implement an +attributes+ method which returns a hash
52
58
  # with each attribute name in your model as hash key and the attribute value as
53
59
  # hash value.
54
60
  #
55
61
  # Hash keys must be strings.
56
- #
57
62
  module AttributeMethods
58
63
  extend ActiveSupport::Concern
59
64
 
@@ -61,52 +66,12 @@ module ActiveModel
61
66
  CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
62
67
 
63
68
  included do
64
- class_attribute :attribute_method_matchers, :instance_writer => false
69
+ class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
70
+ self.attribute_aliases = {}
65
71
  self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
66
72
  end
67
73
 
68
74
  module ClassMethods
69
- def define_attr_method(name, value=nil, deprecation_warning = true, &block) #:nodoc:
70
- # This deprecation_warning param is for internal use so that we can silence
71
- # the warning from Active Record, because we are implementing more specific
72
- # messages there instead.
73
- #
74
- # It doesn't apply to the original_#{name} method as we want to warn if
75
- # people are calling that regardless.
76
- if deprecation_warning
77
- ActiveSupport::Deprecation.warn("define_attr_method is deprecated and will be removed without replacement.")
78
- end
79
-
80
- sing = singleton_class
81
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
82
- remove_possible_method :'original_#{name}'
83
- remove_possible_method :'_original_#{name}'
84
- alias_method :'_original_#{name}', :'#{name}'
85
- define_method :'original_#{name}' do
86
- ActiveSupport::Deprecation.warn(
87
- "This method is generated by ActiveModel::AttributeMethods::ClassMethods#define_attr_method, " \
88
- "which is deprecated and will be removed."
89
- )
90
- send(:'_original_#{name}')
91
- end
92
- eorb
93
- if block_given?
94
- sing.send :define_method, name, &block
95
- else
96
- # If we can compile the method name, do it. Otherwise use define_method.
97
- # This is an important *optimization*, please don't change it. define_method
98
- # has slower dispatch and consumes more memory.
99
- if name =~ NAME_COMPILABLE_REGEXP
100
- sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
101
- def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
102
- RUBY
103
- else
104
- value = value.to_s if value
105
- sing.send(:define_method, name) { value }
106
- end
107
- end
108
- end
109
-
110
75
  # Declares a method available for all attributes with the given prefix.
111
76
  # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
112
77
  #
@@ -119,14 +84,12 @@ module ActiveModel
119
84
  # An instance method <tt>#{prefix}attribute</tt> must exist and accept
120
85
  # at least the +attr+ argument.
121
86
  #
122
- # For example:
123
- #
124
87
  # class Person
125
- #
126
88
  # include ActiveModel::AttributeMethods
89
+ #
127
90
  # attr_accessor :name
128
91
  # attribute_method_prefix 'clear_'
129
- # define_attribute_methods [:name]
92
+ # define_attribute_methods :name
130
93
  #
131
94
  # private
132
95
  #
@@ -136,12 +99,12 @@ module ActiveModel
136
99
  # end
137
100
  #
138
101
  # person = Person.new
139
- # person.name = "Bob"
102
+ # person.name = 'Bob'
140
103
  # person.name # => "Bob"
141
104
  # person.clear_name
142
105
  # person.name # => nil
143
106
  def attribute_method_prefix(*prefixes)
144
- self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }
107
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
145
108
  undefine_attribute_methods
146
109
  end
147
110
 
@@ -154,17 +117,15 @@ module ActiveModel
154
117
  #
155
118
  # attribute#{suffix}(#{attr}, *args, &block)
156
119
  #
157
- # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
158
- # the +attr+ argument.
159
- #
160
- # For example:
120
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at
121
+ # least the +attr+ argument.
161
122
  #
162
123
  # class Person
163
- #
164
124
  # include ActiveModel::AttributeMethods
125
+ #
165
126
  # attr_accessor :name
166
127
  # attribute_method_suffix '_short?'
167
- # define_attribute_methods [:name]
128
+ # define_attribute_methods :name
168
129
  #
169
130
  # private
170
131
  #
@@ -174,11 +135,11 @@ module ActiveModel
174
135
  # end
175
136
  #
176
137
  # person = Person.new
177
- # person.name = "Bob"
138
+ # person.name = 'Bob'
178
139
  # person.name # => "Bob"
179
140
  # person.name_short? # => true
180
141
  def attribute_method_suffix(*suffixes)
181
- self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }
142
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
182
143
  undefine_attribute_methods
183
144
  end
184
145
 
@@ -195,14 +156,12 @@ module ActiveModel
195
156
  # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
196
157
  # accept at least the +attr+ argument.
197
158
  #
198
- # For example:
199
- #
200
159
  # class Person
201
- #
202
160
  # include ActiveModel::AttributeMethods
161
+ #
203
162
  # attr_accessor :name
204
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
205
- # define_attribute_methods [:name]
163
+ # attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
164
+ # define_attribute_methods :name
206
165
  #
207
166
  # private
208
167
  #
@@ -216,35 +175,61 @@ module ActiveModel
216
175
  # person.reset_name_to_default!
217
176
  # person.name # => 'Gemma'
218
177
  def attribute_method_affix(*affixes)
219
- self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }
178
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
220
179
  undefine_attribute_methods
221
180
  end
222
181
 
182
+
183
+ # Allows you to make aliases for attributes.
184
+ #
185
+ # class Person
186
+ # include ActiveModel::AttributeMethods
187
+ #
188
+ # attr_accessor :name
189
+ # attribute_method_suffix '_short?'
190
+ # define_attribute_methods :name
191
+ #
192
+ # alias_attribute :nickname, :name
193
+ #
194
+ # private
195
+ #
196
+ # def attribute_short?(attr)
197
+ # send(attr).length < 5
198
+ # end
199
+ # end
200
+ #
201
+ # person = Person.new
202
+ # person.name = 'Bob'
203
+ # person.name # => "Bob"
204
+ # person.nickname # => "Bob"
205
+ # person.name_short? # => true
206
+ # person.nickname_short? # => true
223
207
  def alias_attribute(new_name, old_name)
208
+ self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
224
209
  attribute_method_matchers.each do |matcher|
225
210
  matcher_new = matcher.method_name(new_name).to_s
226
211
  matcher_old = matcher.method_name(old_name).to_s
227
- define_optimized_call self, matcher_new, matcher_old
212
+ define_proxy_call false, self, matcher_new, matcher_old
228
213
  end
229
214
  end
230
215
 
231
216
  # Declares the attributes that should be prefixed and suffixed by
232
217
  # ActiveModel::AttributeMethods.
233
218
  #
234
- # To use, pass in an array of attribute names (as strings or symbols),
235
- # be sure to declare +define_attribute_methods+ after you define any
236
- # prefix, suffix or affix methods, or they will not hook in.
219
+ # To use, pass attribute names (as strings or symbols), be sure to declare
220
+ # +define_attribute_methods+ after you define any prefix, suffix or affix
221
+ # methods, or they will not hook in.
237
222
  #
238
223
  # class Person
239
- #
240
224
  # include ActiveModel::AttributeMethods
225
+ #
241
226
  # attr_accessor :name, :age, :address
242
227
  # attribute_method_prefix 'clear_'
243
228
  #
244
229
  # # Call to define_attribute_methods must appear after the
245
230
  # # attribute_method_prefix, attribute_method_suffix or
246
231
  # # attribute_method_affix declares.
247
- # define_attribute_methods [:name, :age, :address]
232
+ # define_attribute_methods :name, :age, :address
248
233
  #
249
234
  # private
250
235
  #
@@ -252,10 +237,39 @@ module ActiveModel
252
237
  # ...
253
238
  # end
254
239
  # end
255
- def define_attribute_methods(attr_names)
256
- attr_names.each { |attr_name| define_attribute_method(attr_name) }
240
+ def define_attribute_methods(*attr_names)
241
+ attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
257
242
  end
258
243
 
244
+ # Declares an attribute that should be prefixed and suffixed by
245
+ # ActiveModel::AttributeMethods.
246
+ #
247
+ # To use, pass an attribute name (as string or symbol), be sure to declare
248
+ # +define_attribute_method+ after you define any prefix, suffix or affix
249
+ # method, or they will not hook in.
250
+ #
251
+ # class Person
252
+ # include ActiveModel::AttributeMethods
253
+ #
254
+ # attr_accessor :name
255
+ # attribute_method_suffix '_short?'
256
+ #
257
+ # # Call to define_attribute_method must appear after the
258
+ # # attribute_method_prefix, attribute_method_suffix or
259
+ # # attribute_method_affix declares.
260
+ # define_attribute_method :name
261
+ #
262
+ # private
263
+ #
264
+ # def attribute_short?(attr)
265
+ # send(attr).length < 5
266
+ # end
267
+ # end
268
+ #
269
+ # person = Person.new
270
+ # person.name = 'Bob'
271
+ # person.name # => "Bob"
272
+ # person.name_short? # => true
259
273
  def define_attribute_method(attr_name)
260
274
  attribute_method_matchers.each do |matcher|
261
275
  method_name = matcher.method_name(attr_name)
@@ -266,14 +280,36 @@ module ActiveModel
266
280
  if respond_to?(generate_method, true)
267
281
  send(generate_method, attr_name)
268
282
  else
269
- define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
283
+ define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
270
284
  end
271
285
  end
272
286
  end
273
287
  attribute_method_matchers_cache.clear
274
288
  end
275
289
 
276
- # Removes all the previously dynamically defined methods from the class
290
+ # Removes all the previously dynamically defined methods from the class.
291
+ #
292
+ # class Person
293
+ # include ActiveModel::AttributeMethods
294
+ #
295
+ # attr_accessor :name
296
+ # attribute_method_suffix '_short?'
297
+ # define_attribute_method :name
298
+ #
299
+ # private
300
+ #
301
+ # def attribute_short?(attr)
302
+ # send(attr).length < 5
303
+ # end
304
+ # end
305
+ #
306
+ # person = Person.new
307
+ # person.name = 'Bob'
308
+ # person.name_short? # => true
309
+ #
310
+ # Person.undefine_attribute_methods
311
+ #
312
+ # person.name_short? # => NoMethodError
277
313
  def undefine_attribute_methods
278
314
  generated_attribute_methods.module_eval do
279
315
  instance_methods.each { |m| undef_method(m) }
@@ -283,15 +319,11 @@ module ActiveModel
283
319
 
284
320
  # Returns true if the attribute methods defined have been generated.
285
321
  def generated_attribute_methods #:nodoc:
286
- @generated_attribute_methods ||= begin
287
- mod = Module.new
288
- include mod
289
- mod
290
- end
322
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
291
323
  end
292
324
 
293
325
  protected
294
- def instance_method_already_implemented?(method_name)
326
+ def instance_method_already_implemented?(method_name) #:nodoc:
295
327
  generated_attribute_methods.method_defined?(method_name)
296
328
  end
297
329
 
@@ -306,38 +338,36 @@ module ActiveModel
306
338
  # significantly (in our case our test suite finishes 10% faster with
307
339
  # this cache).
308
340
  def attribute_method_matchers_cache #:nodoc:
309
- @attribute_method_matchers_cache ||= {}
341
+ @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(:initial_capacity => 4)
310
342
  end
311
343
 
312
344
  def attribute_method_matcher(method_name) #:nodoc:
313
- if attribute_method_matchers_cache.key?(method_name)
314
- attribute_method_matchers_cache[method_name]
315
- else
345
+ attribute_method_matchers_cache.compute_if_absent(method_name) do
316
346
  # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
317
347
  # will match every time.
318
348
  matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
319
349
  match = nil
320
350
  matchers.detect { |method| match = method.match(method_name) }
321
- attribute_method_matchers_cache[method_name] = match
351
+ match
322
352
  end
323
353
  end
324
354
 
325
355
  # Define a method `name` in `mod` that dispatches to `send`
326
356
  # using the given `extra` args. This fallbacks `define_method`
327
357
  # and `send` if the given names cannot be compiled.
328
- def define_optimized_call(mod, name, send, *extra) #:nodoc:
329
- if name =~ NAME_COMPILABLE_REGEXP
330
- defn = "def #{name}(*args)"
358
+ def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
359
+ defn = if name =~ NAME_COMPILABLE_REGEXP
360
+ "def #{name}(*args)"
331
361
  else
332
- defn = "define_method(:'#{name}') do |*args|"
362
+ "define_method(:'#{name}') do |*args|"
333
363
  end
334
364
 
335
- extra = (extra.map(&:inspect) << "*args").join(", ")
365
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
336
366
 
337
- if send =~ CALL_COMPILABLE_REGEXP
338
- target = "#{send}(#{extra})"
367
+ target = if send =~ CALL_COMPILABLE_REGEXP
368
+ "#{"self." unless include_private}#{send}(#{extra})"
339
369
  else
340
- target = "send(:'#{send}', #{extra})"
370
+ "send(:'#{send}', #{extra})"
341
371
  end
342
372
 
343
373
  mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -347,34 +377,29 @@ module ActiveModel
347
377
  RUBY
348
378
  end
349
379
 
350
- class AttributeMethodMatcher
380
+ class AttributeMethodMatcher #:nodoc:
351
381
  attr_reader :prefix, :suffix, :method_missing_target
352
382
 
353
383
  AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
354
384
 
355
385
  def initialize(options = {})
356
- options.symbolize_keys!
357
-
358
386
  if options[:prefix] == '' || options[:suffix] == ''
359
- ActiveSupport::Deprecation.warn(
360
- "Specifying an empty prefix/suffix for an attribute method is no longer " \
361
- "necessary. If the un-prefixed/suffixed version of the method has not been " \
362
- "defined when `define_attribute_methods` is called, it will be defined " \
363
- "automatically."
364
- )
387
+ message = "Specifying an empty prefix/suffix for an attribute method is no longer " \
388
+ "necessary. If the un-prefixed/suffixed version of the method has not been " \
389
+ "defined when `define_attribute_methods` is called, it will be defined " \
390
+ "automatically."
391
+ ActiveSupport::Deprecation.warn message
365
392
  end
366
393
 
367
- @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
368
- @regex = /\A(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})\z/
394
+ @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
395
+ @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
369
396
  @method_missing_target = "#{@prefix}attribute#{@suffix}"
370
397
  @method_name = "#{prefix}%s#{suffix}"
371
398
  end
372
399
 
373
400
  def match(method_name)
374
401
  if @regex =~ method_name
375
- AttributeMethodMatch.new(method_missing_target, $2, method_name)
376
- else
377
- nil
402
+ AttributeMethodMatch.new(method_missing_target, $1, method_name)
378
403
  end
379
404
  end
380
405
 
@@ -411,7 +436,7 @@ module ActiveModel
411
436
  # attribute_missing is like method_missing, but for attributes. When method_missing is
412
437
  # called we check to see if there is a matching attribute method. If so, we call
413
438
  # attribute_missing to dispatch the attribute. This method can be overloaded to
414
- # customise the behaviour.
439
+ # customize the behavior.
415
440
  def attribute_missing(match, *args, &block)
416
441
  __send__(match.target, match.attr_name, *args, &block)
417
442
  end
@@ -433,7 +458,7 @@ module ActiveModel
433
458
  end
434
459
 
435
460
  protected
436
- def attribute_method?(attr_name)
461
+ def attribute_method?(attr_name) #:nodoc:
437
462
  respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
438
463
  end
439
464
 
@@ -442,7 +467,7 @@ module ActiveModel
442
467
  # The struct's attributes are prefix, base and suffix.
443
468
  def match_attribute_method?(method_name)
444
469
  match = self.class.send(:attribute_method_matcher, method_name)
445
- match && attribute_method?(match.attr_name) ? match : nil
470
+ match if match && attribute_method?(match.attr_name)
446
471
  end
447
472
 
448
473
  def missing_attribute(attr_name, stack)