activemodel 3.2.22.5 → 4.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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)