Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Let Ruby deal with method visibility.

Check respond_to_without_attributes? in method_missing. If there is any
method that responds (even private), let super handle it and raise
NoMethodError if necessary.
  • Loading branch information...
commit ac687ed651773fccecbc22cd6d8b07d5439ceb76 1 parent cf115d2
@jonleighton jonleighton authored
View
24 activemodel/lib/active_model/attribute_methods.rb
@@ -411,13 +411,18 @@ def plain?
# It's also possible to instantiate related objects, so a Client class
# belonging to the clients table with a +master_id+ foreign key can
# instantiate master through Client#master.
- def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
- if match = match_attribute_method?(method_name)
- guard_private_attribute_method!(method_name, args)
- return __send__(match.target, match.attr_name, *args, &block)
+ def method_missing(method, *args, &block)
+ if respond_to_without_attributes?(method, true)
+ super
+ else
+ match = match_attribute_method?(method.to_s)
+
+ if match
+ __send__(match.target, match.attr_name, *args, &block)
+ else
+ super
+ end
end
- super
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -450,13 +455,6 @@ def match_attribute_method?(method_name)
match && attribute_method?(match.attr_name) ? match : nil
end
- # prevent method_missing from calling private methods with #send
- def guard_private_attribute_method!(method_name, args)
- if self.class.private_method_defined?(method_name)
- raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
- end
- end
-
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
View
36 activemodel/test/cases/attribute_methods_test.rb
@@ -32,6 +32,16 @@ def attribute(name)
end
alias attribute_test attribute
+
+ def private_method
+ "<3 <3"
+ end
+
+protected
+
+ def protected_method
+ "O_o O_o"
+ end
end
class ModelWithAttributesWithSpaces
@@ -151,4 +161,30 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal 'value of foo', klass.new.foo
end
+
+ test 'should not interfere with method_missing if the attr has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ # dispatches to the *method*, not the attribute
+ assert_equal '<3 <3', m.send(:private_method)
+ assert_equal 'O_o O_o', m.send(:protected_method)
+
+ # sees that a method is already defined, so doesn't intervene
+ assert_raises(NoMethodError) { m.private_method }
+ assert_raises(NoMethodError) { m.protected_method }
+ end
+
+ test 'should not interfere with respond_to? if the attribute has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ assert !m.respond_to?(:private_method)
+ assert m.respond_to?(:private_method, true)
+
+ # This is messed up, but it's how Ruby works at the moment. Apparently it will be changed
+ # in the future.
+ assert m.respond_to?(:protected_method)
+ assert m.respond_to?(:protected_method, true)
+ end
end
View
17 activerecord/lib/active_record/attribute_methods.rb
@@ -61,14 +61,17 @@ def defined_activerecord_methods
end
end
- def method_missing(method_id, *args, &block)
- # If we haven't generated any methods yet, generate them, then
- # see if we've created the method we're looking for.
- if !self.class.attribute_methods_generated?
+ # If we haven't generated any methods yet, generate them, then
+ # see if we've created the method we're looking for.
+ def method_missing(method, *args, &block)
+ unless self.class.attribute_methods_generated?
self.class.define_attribute_methods
- method_name = method_id.to_s
- guard_private_attribute_method!(method_name, args)
- send(method_id, *args, &block)
+
+ if respond_to_without_attributes?(method)
+ send(method, *args, &block)
+ else
+ super
+ end
else
super
end
View
6 activerecord/test/cases/attribute_methods_test.rb
@@ -608,7 +608,7 @@ def test_read_attributes_respect_access_control
topic = @target.new(:title => "The pros and cons of programming naked.")
assert !topic.respond_to?(:title)
exception = assert_raise(NoMethodError) { topic.title }
- assert_match %r(^Attempt to call private method), exception.message
+ assert exception.message.include?("private method")
assert_equal "I'm private", topic.send(:title)
end
@@ -618,7 +618,7 @@ def test_write_attributes_respect_access_control
topic = @target.new
assert !topic.respond_to?(:title=)
exception = assert_raise(NoMethodError) { topic.title = "Pants"}
- assert_match %r(^Attempt to call private method), exception.message
+ assert exception.message.include?("private method")
topic.send(:title=, "Very large pants")
end
@@ -628,7 +628,7 @@ def test_question_attributes_respect_access_control
topic = @target.new(:title => "Isaac Newton's pants")
assert !topic.respond_to?(:title?)
exception = assert_raise(NoMethodError) { topic.title? }
- assert_match %r(^Attempt to call private method), exception.message
+ assert exception.message.include?("private method")
assert topic.send(:title?)
end
Please sign in to comment.
Something went wrong with that request. Please try again.