activemodel 3.1.12 → 3.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (33) hide show
  1. data/CHANGELOG.md +81 -36
  2. data/README.rdoc +1 -1
  3. data/lib/active_model/attribute_methods.rb +123 -104
  4. data/lib/active_model/callbacks.rb +2 -2
  5. data/lib/active_model/conversion.rb +26 -2
  6. data/lib/active_model/dirty.rb +3 -3
  7. data/lib/active_model/errors.rb +63 -51
  8. data/lib/active_model/lint.rb +12 -3
  9. data/lib/active_model/mass_assignment_security.rb +27 -8
  10. data/lib/active_model/mass_assignment_security/permission_set.rb +5 -5
  11. data/lib/active_model/mass_assignment_security/sanitizer.rb +42 -6
  12. data/lib/active_model/naming.rb +18 -10
  13. data/lib/active_model/observer_array.rb +3 -3
  14. data/lib/active_model/observing.rb +1 -2
  15. data/lib/active_model/secure_password.rb +2 -2
  16. data/lib/active_model/serialization.rb +61 -10
  17. data/lib/active_model/serializers/json.rb +20 -14
  18. data/lib/active_model/serializers/xml.rb +55 -31
  19. data/lib/active_model/translation.rb +15 -3
  20. data/lib/active_model/validations.rb +1 -1
  21. data/lib/active_model/validations/acceptance.rb +3 -1
  22. data/lib/active_model/validations/confirmation.rb +3 -1
  23. data/lib/active_model/validations/exclusion.rb +5 -3
  24. data/lib/active_model/validations/format.rb +4 -2
  25. data/lib/active_model/validations/inclusion.rb +5 -3
  26. data/lib/active_model/validations/length.rb +22 -10
  27. data/lib/active_model/validations/numericality.rb +4 -2
  28. data/lib/active_model/validations/presence.rb +5 -3
  29. data/lib/active_model/validations/validates.rb +15 -3
  30. data/lib/active_model/validations/with.rb +4 -2
  31. data/lib/active_model/version.rb +3 -3
  32. metadata +21 -28
  33. checksums.yaml +0 -7
data/CHANGELOG.md CHANGED
@@ -1,71 +1,116 @@
1
- ## Rails 3.1.11 (Feb 11, 2011) ##
1
+ ## Rails 3.2.0 (unreleased) ##
2
2
 
3
- * Fix issue with `attr_protected` where malformed input could circumvent protection.
4
- CVE-2013-0276
3
+ * Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to
4
+ support methods like `set_table_name` in Active Record, which are themselves being deprecated.
5
5
 
6
- *joernchen*
6
+ *Jon Leighton*
7
7
 
8
- ## Rails 3.1.10 (Jan 8, 2013) ##
8
+ * Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin*
9
9
 
10
- * No changes.
10
+ * Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev*
11
+
12
+ * Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" *Grant Hutchins, Peter Jaros*
11
13
 
12
- ## Rails 3.1.9 (Jan 2, 2013) ##
14
+ * Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev*
15
+
16
+ ## Rails 3.1.0 (August 30, 2011) ##
17
+
18
+ * Alternate I18n namespace lookup is no longer supported.
19
+ Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead *José Valim*
13
20
 
14
- * Due to a change in builder, nil values now generates closed tags, so instead of this:
21
+ * attr_accessible and friends now accepts :as as option to specify a role *Josh Kalderimis*
22
+
23
+ * Add support for proc or lambda as an option for InclusionValidator,
24
+ ExclusionValidator, and FormatValidator *Prem Sichanugrist*
25
+
26
+ You can now supply Proc, lambda, or anything that respond to #call in those
27
+ validations, and it will be called with current record as an argument.
28
+ That given proc or lambda must returns an object which respond to #include? for
29
+ InclusionValidator and ExclusionValidator, and returns a regular expression
30
+ object for FormatValidator.
15
31
 
16
- <pseudonyms nil=\"true\"></pseudonyms>
32
+ * Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting *DHH*
17
33
 
18
- It generates this:
34
+ * ActiveModel::AttributeMethods allows attributes to be defined on demand *Alexander Uvarov*
19
35
 
20
- <pseudonyms nil=\"true\"/>
36
+ * Add support for selectively enabling/disabling observers *Myron Marston*
21
37
 
22
- *Carlos Antonio da Silva*
23
38
 
24
- ## Rails 3.1.8 (Aug 9, 2012) ##
39
+ ## Rails 3.0.7 (April 18, 2011) ##
25
40
 
26
41
  * No changes.
27
42
 
28
- ## Rails 3.1.7 (Jul 26, 2012) ##
43
+
44
+ * Rails 3.0.6 (April 5, 2011)
45
+
46
+ * Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino*
47
+
48
+ * Fix length validation for fixnums #6556 *Andriy Tyurnikov*
49
+
50
+ * Fix i18n key collision with namespaced models #6448 *yves.senn*
51
+
52
+
53
+ ## Rails 3.0.5 (February 26, 2011) ##
29
54
 
30
55
  * No changes.
31
56
 
32
- ## Rails 3.1.6 (Jun 12, 2012) ##
57
+
58
+ ## Rails 3.0.4 (February 8, 2011) ##
33
59
 
34
60
  * No changes.
35
61
 
36
- ## Rails 3.1.5 (May 31, 2012) ##
62
+
63
+ ## Rails 3.0.3 (November 16, 2010) ##
37
64
 
38
65
  * No changes.
39
66
 
40
- ## Rails 3.1.1 (October 7, 2011) ##
41
67
 
42
- * Remove hard dependency on bcrypt-ruby to avoid make ActiveModel dependent on a binary library.
43
- You must add the gem explicitly to your Gemfile if you want use ActiveModel::SecurePassword:
68
+ ## Rails 3.0.2 (November 15, 2010) ##
44
69
 
45
- gem 'bcrypt-ruby', '~> 3.0.0'
70
+ * No changes
46
71
 
47
- See GH #2687. *Guillermo Iguaran*
48
72
 
49
- ## Rails 3.1.0 (August 30, 2011) ##
73
+ ## Rails 3.0.1 (October 15, 2010) ##
50
74
 
51
- * Alternate I18n namespace lookup is no longer supported.
52
- Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead *José Valim*
75
+ * No Changes, just a version bump.
53
76
 
54
- * attr_accessible and friends now accepts :as as option to specify a role *Josh Kalderimis*
55
77
 
56
- * Add support for proc or lambda as an option for InclusionValidator,
57
- ExclusionValidator, and FormatValidator *Prem Sichanugrist*
78
+ ## Rails 3.0.0 (August 29, 2010) ##
58
79
 
59
- You can now supply Proc, lambda, or anything that respond to #call in those
60
- validations, and it will be called with current record as an argument.
61
- That given proc or lambda must returns an object which respond to #include? for
62
- InclusionValidator and ExclusionValidator, and returns a regular expression
63
- object for FormatValidator.
80
+ * Added ActiveModel::MassAssignmentSecurity *Eric Chapweske, Josh Kalderimis*
64
81
 
65
- * Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting *DHH*
82
+ * JSON supports a custom root option: to_json(:root => 'custom') #4515 *Jatinder Singh*
66
83
 
67
- * ActiveModel::AttributeMethods allows attributes to be defined on demand *Alexander Uvarov*
84
+ * #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
85
+ persisted? instead. A model is persisted if it's not a new_record? and it was
86
+ not destroyed? *MG*
68
87
 
69
- * Add support for selectively enabling/disabling observers *Myron Marston*
88
+ * Added validations reflection in ActiveModel::Validations *JV*
89
+
90
+ Model.validators
91
+ Model.validators_on(:field)
92
+
93
+ * #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
94
+ AMo objects with composite keys *MG*
95
+
96
+ * ActiveModel::Observer#add_observer!
97
+
98
+ It has a custom hook to define after_find that should really be in a
99
+ ActiveRecord::Observer subclass:
100
+
101
+ def add_observer!(klass)
102
+ klass.add_observer(self)
103
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
104
+ end
105
+
106
+ * Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 *DHH*
107
+
108
+ * Add validates_format_of :without => /regexp/ option. #430 *Elliot Winkler, Peer Allan*
109
+
110
+ Example :
111
+
112
+ validates_format_of :subdomain, :without => /www|admin|mail/
113
+
114
+ * Introduce validates_with to encapsulate attribute validations in a class. #2630 *Jeff Dean*
70
115
 
71
- Please check [3-0-stable](https://github.com/rails/rails/blob/3-0-stable/activemodel/CHANGELOG) for previous changes.
116
+ * Extracted from Active Record and Active Resource.
data/README.rdoc CHANGED
@@ -186,7 +186,7 @@ modules:
186
186
 
187
187
  == Download and installation
188
188
 
189
- The latest version of Active Model can be installed with Rubygems:
189
+ The latest version of Active Model can be installed with RubyGems:
190
190
 
191
191
  % [sudo] gem install activemodel
192
192
 
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
2
  require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/deprecation'
3
4
 
4
5
  module ActiveModel
5
6
  class MissingAttributeError < NoMethodError
@@ -56,54 +57,38 @@ module ActiveModel
56
57
  module AttributeMethods
57
58
  extend ActiveSupport::Concern
58
59
 
59
- COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
60
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
61
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
60
62
 
61
63
  included do
62
64
  class_attribute :attribute_method_matchers, :instance_writer => false
63
- self.attribute_method_matchers = []
65
+ self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
64
66
  end
65
67
 
66
68
  module ClassMethods
67
- # Defines an "attribute" method (like +inheritance_column+ or +table_name+).
68
- # A new (class) method will be created with the given name. If a value is
69
- # specified, the new method will return that value (as a string).
70
- # Otherwise, the given block will be used to compute the value of the
71
- # method.
72
- #
73
- # The original method will be aliased, with the new name being prefixed
74
- # with "original_". This allows the new method to access the original
75
- # value.
76
- #
77
- # Example:
78
- #
79
- # class Person
80
- #
81
- # include ActiveModel::AttributeMethods
82
- #
83
- # cattr_accessor :primary_key
84
- # cattr_accessor :inheritance_column
85
- #
86
- # define_attr_method :primary_key, "sysid"
87
- # define_attr_method( :inheritance_column ) do
88
- # original_inheritance_column + "_id"
89
- # end
90
- #
91
- # end
92
- #
93
- # Provides you with:
94
- #
95
- # Person.primary_key
96
- # # => "sysid"
97
- # Person.inheritance_column = 'address'
98
- # Person.inheritance_column
99
- # # => 'address_id'
100
- def define_attr_method(name, value=nil, &block)
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
+
101
80
  sing = singleton_class
102
81
  sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
103
- if method_defined?('original_#{name}')
104
- undef :'original_#{name}'
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}')
105
91
  end
106
- alias_method :'original_#{name}', :'#{name}'
107
92
  eorb
108
93
  if block_given?
109
94
  sing.send :define_method, name, &block
@@ -111,7 +96,7 @@ module ActiveModel
111
96
  # If we can compile the method name, do it. Otherwise use define_method.
112
97
  # This is an important *optimization*, please don't change it. define_method
113
98
  # has slower dispatch and consumes more memory.
114
- if name =~ COMPILABLE_REGEXP
99
+ if name =~ NAME_COMPILABLE_REGEXP
115
100
  sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
116
101
  def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
117
102
  RUBY
@@ -239,18 +224,7 @@ module ActiveModel
239
224
  attribute_method_matchers.each do |matcher|
240
225
  matcher_new = matcher.method_name(new_name).to_s
241
226
  matcher_old = matcher.method_name(old_name).to_s
242
-
243
- if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
244
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
245
- def #{matcher_new}(*args)
246
- send(:#{matcher_old}, *args)
247
- end
248
- RUBY
249
- else
250
- define_method(matcher_new) do |*args|
251
- send(matcher_old, *args)
252
- end
253
- end
227
+ define_optimized_call self, matcher_new, matcher_old
254
228
  end
255
229
  end
256
230
 
@@ -284,36 +258,19 @@ module ActiveModel
284
258
 
285
259
  def define_attribute_method(attr_name)
286
260
  attribute_method_matchers.each do |matcher|
287
- unless instance_method_already_implemented?(matcher.method_name(attr_name))
288
- generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
261
+ method_name = matcher.method_name(attr_name)
262
+
263
+ unless instance_method_already_implemented?(method_name)
264
+ generate_method = "define_method_#{matcher.method_missing_target}"
289
265
 
290
- if respond_to?(generate_method, true)
266
+ if respond_to?(generate_method)
291
267
  send(generate_method, attr_name)
292
268
  else
293
- method_name = matcher.method_name(attr_name)
294
-
295
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
296
- if method_defined?('#{method_name}')
297
- undef :'#{method_name}'
298
- end
299
- RUBY
300
-
301
- if method_name.to_s =~ COMPILABLE_REGEXP
302
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
303
- def #{method_name}(*args)
304
- send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
305
- end
306
- RUBY
307
- else
308
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
309
- define_method('#{method_name}') do |*args|
310
- send('#{matcher.method_missing_target}', '#{attr_name}', *args)
311
- end
312
- RUBY
313
- end
269
+ define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
314
270
  end
315
271
  end
316
272
  end
273
+ attribute_method_matchers_cache.clear
317
274
  end
318
275
 
319
276
  # Removes all the previously dynamically defined methods from the class
@@ -321,6 +278,7 @@ module ActiveModel
321
278
  generated_attribute_methods.module_eval do
322
279
  instance_methods.each { |m| undef_method(m) }
323
280
  end
281
+ attribute_method_matchers_cache.clear
324
282
  end
325
283
 
326
284
  # Returns true if the attribute methods defined have been generated.
@@ -334,26 +292,87 @@ module ActiveModel
334
292
 
335
293
  protected
336
294
  def instance_method_already_implemented?(method_name)
337
- method_defined?(method_name)
295
+ generated_attribute_methods.method_defined?(method_name)
338
296
  end
339
297
 
340
298
  private
299
+ # The methods +method_missing+ and +respond_to?+ of this module are
300
+ # invoked often in a typical rails, both of which invoke the method
301
+ # +match_attribute_method?+. The latter method iterates through an
302
+ # array doing regular expression matches, which results in a lot of
303
+ # object creations. Most of the times it returns a +nil+ match. As the
304
+ # match result is always the same given a +method_name+, this cache is
305
+ # used to alleviate the GC, which ultimately also speeds up the app
306
+ # significantly (in our case our test suite finishes 10% faster with
307
+ # this cache).
308
+ def attribute_method_matchers_cache #:nodoc:
309
+ @attribute_method_matchers_cache ||= {}
310
+ end
311
+
312
+ 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
316
+ # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
317
+ # will match every time.
318
+ matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
319
+ match = nil
320
+ matchers.detect { |method| match = method.match(method_name) }
321
+ attribute_method_matchers_cache[method_name] = match
322
+ end
323
+ end
324
+
325
+ # Define a method `name` in `mod` that dispatches to `send`
326
+ # using the given `extra` args. This fallbacks `define_method`
327
+ # 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)"
331
+ else
332
+ defn = "define_method(:'#{name}') do |*args|"
333
+ end
334
+
335
+ extra = (extra.map(&:inspect) << "*args").join(", ")
336
+
337
+ if send =~ CALL_COMPILABLE_REGEXP
338
+ target = "#{send}(#{extra})"
339
+ else
340
+ target = "send(:'#{send}', #{extra})"
341
+ end
342
+
343
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
344
+ #{defn}
345
+ #{target}
346
+ end
347
+ RUBY
348
+ end
349
+
341
350
  class AttributeMethodMatcher
342
351
  attr_reader :prefix, :suffix, :method_missing_target
343
352
 
344
- AttributeMethodMatch = Struct.new(:target, :attr_name)
353
+ AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
345
354
 
346
355
  def initialize(options = {})
347
356
  options.symbolize_keys!
357
+
358
+ 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
+ )
365
+ end
366
+
348
367
  @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
349
- @regex = /\A(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})\z/
368
+ @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
350
369
  @method_missing_target = "#{@prefix}attribute#{@suffix}"
351
370
  @method_name = "#{prefix}%s#{suffix}"
352
371
  end
353
372
 
354
373
  def match(method_name)
355
374
  if @regex =~ method_name
356
- AttributeMethodMatch.new(method_missing_target, $2)
375
+ AttributeMethodMatch.new(method_missing_target, $2, method_name)
357
376
  else
358
377
  nil
359
378
  end
@@ -362,6 +381,10 @@ module ActiveModel
362
381
  def method_name(attr_name)
363
382
  @method_name % attr_name
364
383
  end
384
+
385
+ def plain?
386
+ prefix.empty? && suffix.empty?
387
+ end
365
388
  end
366
389
  end
367
390
 
@@ -376,13 +399,21 @@ module ActiveModel
376
399
  # It's also possible to instantiate related objects, so a Client class
377
400
  # belonging to the clients table with a +master_id+ foreign key can
378
401
  # instantiate master through Client#master.
379
- def method_missing(method_id, *args, &block)
380
- method_name = method_id.to_s
381
- if match = match_attribute_method?(method_name)
382
- guard_private_attribute_method!(method_name, args)
383
- return __send__(match.target, match.attr_name, *args, &block)
402
+ def method_missing(method, *args, &block)
403
+ if respond_to_without_attributes?(method, true)
404
+ super
405
+ else
406
+ match = match_attribute_method?(method.to_s)
407
+ match ? attribute_missing(match, *args, &block) : super
384
408
  end
385
- super
409
+ end
410
+
411
+ # attribute_missing is like method_missing, but for attributes. When method_missing is
412
+ # called we check to see if there is a matching attribute method. If so, we call
413
+ # attribute_missing to dispatch the attribute. This method can be overloaded to
414
+ # customise the behaviour.
415
+ def attribute_missing(match, *args, &block)
416
+ __send__(match.target, match.attr_name, *args, &block)
386
417
  end
387
418
 
388
419
  # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -391,15 +422,14 @@ module ActiveModel
391
422
  alias :respond_to_without_attributes? :respond_to?
392
423
  def respond_to?(method, include_private_methods = false)
393
424
  if super
394
- return true
425
+ true
395
426
  elsif !include_private_methods && super(method, true)
396
427
  # If we're here then we haven't found among non-private methods
397
428
  # but found among all methods. Which means that the given method is private.
398
- return false
399
- elsif match_attribute_method?(method.to_s)
400
- return true
429
+ false
430
+ else
431
+ !match_attribute_method?(method.to_s).nil?
401
432
  end
402
- super
403
433
  end
404
434
 
405
435
  protected
@@ -411,19 +441,8 @@ module ActiveModel
411
441
  # Returns a struct representing the matching attribute method.
412
442
  # The struct's attributes are prefix, base and suffix.
413
443
  def match_attribute_method?(method_name)
414
- self.class.attribute_method_matchers.each do |method|
415
- if (match = method.match(method_name)) && attribute_method?(match.attr_name)
416
- return match
417
- end
418
- end
419
- nil
420
- end
421
-
422
- # prevent method_missing from calling private methods with #send
423
- def guard_private_attribute_method!(method_name, args)
424
- if self.class.private_method_defined?(method_name)
425
- raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
426
- end
444
+ match = self.class.send(:attribute_method_matcher, method_name)
445
+ match && attribute_method?(match.attr_name) ? match : nil
427
446
  end
428
447
 
429
448
  def missing_attribute(attr_name, stack)