aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/public_send.rb25
-rw-r--r--activesupport/test/core_ext/object/public_send_test.rb117
3 files changed, 143 insertions, 0 deletions
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 9ad1e12699..249c2e93c5 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
+require 'active_support/core_ext/object/public_send'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
diff --git a/activesupport/lib/active_support/core_ext/object/public_send.rb b/activesupport/lib/active_support/core_ext/object/public_send.rb
new file mode 100644
index 0000000000..233e69b4f8
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/object/public_send.rb
@@ -0,0 +1,25 @@
+require 'active_support/core_ext/kernel/singleton_class'
+
+class Object
+ unless Object.public_method_defined?(:public_send)
+ # Backports Object#public_send from 1.9
+ def public_send(method, *args, &block) # :nodoc:
+ # Don't create a singleton class for the object if it doesn't already have one
+ # (This also protects us from classes like Fixnum and Symbol, which cannot have a
+ # singleton class.)
+ klass = singleton_methods.any? ? self.singleton_class : self.class
+
+ if klass.public_method_defined?(method)
+ send(method, *args, &block)
+ else
+ if klass.private_method_defined?(method)
+ raise NoMethodError, "private method `#{method}' called for #{inspect}"
+ elsif klass.protected_method_defined?(method)
+ raise NoMethodError, "protected method `#{method}' called for #{inspect}"
+ else
+ raise NoMethodError, "undefined method `#{method}' for #{inspect}"
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/object/public_send_test.rb b/activesupport/test/core_ext/object/public_send_test.rb
new file mode 100644
index 0000000000..7dc542e51c
--- /dev/null
+++ b/activesupport/test/core_ext/object/public_send_test.rb
@@ -0,0 +1,117 @@
+require 'abstract_unit'
+require 'active_support/core_ext/object/public_send'
+
+module PublicSendReceiver
+ def receive_public_method(*args)
+ return args + [yield]
+ end
+
+ protected
+
+ def receive_protected_method(*args)
+ return args + [yield]
+ end
+
+ private
+
+ def receive_private_method(*args)
+ return args + [yield]
+ end
+end
+
+# Note, running this on 1.9 will be testing the Ruby core implementation, but it is good to
+# do this to ensure that our backport functions the same as Ruby core in 1.9
+class PublicSendTest < Test::Unit::TestCase
+ def instance
+ @instance ||= begin
+ klass = Class.new do
+ include PublicSendReceiver
+ end
+ klass.new
+ end
+ end
+
+ def singleton_instance
+ @singleton_instance ||= begin
+ object = Object.new
+ object.singleton_class.send(:include, PublicSendReceiver)
+ object
+ end
+ end
+
+ def test_should_receive_public_method
+ assert_equal(
+ [:foo, :bar, :baz],
+ instance.public_send(:receive_public_method, :foo, :bar) { :baz }
+ )
+ end
+
+ def test_should_receive_public_singleton_method
+ assert_equal(
+ [:foo, :bar, :baz],
+ singleton_instance.public_send(:receive_public_method, :foo, :bar) { :baz }
+ )
+ end
+
+ def test_should_raise_on_protected_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_protected_singleton_method
+ assert_raises(NoMethodError) do
+ singleton_instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_private_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_singleton_private_method
+ assert_raises(NoMethodError) do
+ singleton_instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_should_raise_on_undefined_method
+ assert_raises(NoMethodError) do
+ instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
+ end
+ end
+
+ def test_protected_method_message
+ instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "protected method `receive_protected_method' called for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_private_method_message
+ instance.public_send(:receive_private_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "private method `receive_private_method' called for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_undefined_method_message
+ instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
+ rescue NoMethodError => exception
+ assert_equal(
+ "undefined method `receive_undefined_method' for #{instance.inspect}",
+ exception.message
+ )
+ end
+
+ def test_receiver_with_no_singleton
+ assert_equal "5", 5.public_send(:to_s)
+ assert_equal "foo", :foo.public_send(:to_s)
+ end
+end