activemodel 3.1.12 → 3.2.0.rc1

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 (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)