Permalink
Browse files

Merge pull request #1 from stephankaag/master

Support (un-)extending private / protected methods
  • Loading branch information...
2 parents a728fbe + 3741d4a commit e73c5a3031fe600d32a1f3c09759e2fce08ca8db Paul Engel committed Aug 15, 2012
Showing with 47 additions and 9 deletions.
  1. +6 −0 lib/unextendable/module.rb
  2. +6 −5 lib/unextendable/object.rb
  3. +35 −4 test/object_test.rb
@@ -1,5 +1,11 @@
class Module
+ def my_methods(include_private = true)
+ public_instance_methods.tap do |methods|
+ return methods + private_instance_methods + protected_instance_methods if include_private
+ end
+ end
+
def unextendable
@unextendable = true
end
View
@@ -43,20 +43,20 @@ def unextend?(mod, &block)
def wrap_unextendable_module(mod)
return unless (mod.class == Module) && mod.unextendable?
- mod.instance_methods.each do |method_name|
+ mod.my_methods.each do |method_name|
wrap_unextendable_method method_name
end
return if @wrapped_respond_to
instance_eval <<-CODE
def respond_to?(symbol, include_private = false)
- meta_class.extended_modules.any?{|x| x.instance_methods.collect(&:to_s).include? symbol.to_s} ||
+ meta_class.extended_modules.any?{|x| x.my_methods(include_private).collect(&:to_s).include? symbol.to_s} ||
begin
if meta_class.method_procs.has_key? symbol.to_s
meta_class.method_procs[symbol.to_s].class == Proc
else
- self.class.instance_methods.collect(&:to_s).include? symbol.to_s
+ self.class.my_methods(include_private).collect(&:to_s).include? symbol.to_s
end
end
end
@@ -68,7 +68,7 @@ def respond_to?(symbol, include_private = false)
def wrap_unextendable_method(method_name)
return if meta_class.method_procs.key? method_name.to_s
- meta_class.method_procs[method_name.to_s] = respond_to?(method_name) ? method(method_name.to_s).to_proc : nil
+ meta_class.method_procs[method_name.to_s] = respond_to?(method_name, true) ? method(method_name.to_s).to_proc : nil
instance_eval <<-CODE
def #{method_name}(*args, &block)
@@ -86,12 +86,13 @@ def call_unextendable_method(method_name, *args, &block)
if method = method_for(method_name)
method.call(*args, &block)
else
+ binding.pry if method_name.to_s == "id"
raise NoMethodError, "undefined method `#{method_name}' for #{self.inspect}"
end
end
def method_for(method_name)
- mod = meta_class.extended_modules.detect{|x| x.instance_methods.collect(&:to_s).include? method_name.to_s}
+ mod = meta_class.extended_modules.detect{|x| x.my_methods.collect(&:to_s).include? method_name.to_s}
mod ? mod.instance_method(method_name).bind(self) : proc_for(method_name)
end
View
@@ -17,6 +17,10 @@ def salutation
def name
"C"
end
+ private
+ def id
+ "C"
+ end
end
@c = C.new
@c.title = "Mr."
@@ -72,6 +76,14 @@ module B; end
context "which are unextendable" do
setup do
+ module B
+ unextendable
+ public
+ def id
+ "B"
+ end
+ end
+
module U
unextendable
def name
@@ -80,6 +92,12 @@ def name
def foo
"bar"
end
+
+ private
+
+ def id
+ "U"
+ end
end
end
@@ -95,14 +113,14 @@ def foo
end
should "call wrap_unextendable_method" do
- @c.expects(:wrap_unextendable_method).twice
+ @c.expects(:wrap_unextendable_method).times(3)
@c.extend U
end
should "add nil value as method proc when not responding to module method name" do
@c.extend U
assert_equal 1, @c.meta_class.method_procs.select{|k, v| v.nil?}.size
- assert_equal 1, @c.meta_class.method_procs.select{|k, v| v.class == Proc}.size
+ assert_equal 2, @c.meta_class.method_procs.select{|k, v| v.class == Proc}.size
end
should "add the module to extended_modules" do
@@ -114,7 +132,7 @@ def foo
should "add method proc to method_procs" do
assert @c.meta_class.send(:method_procs).empty?
@c.extend U
- assert_equal 2, @c.meta_class.send(:method_procs).size
+ assert_equal 3, @c.meta_class.send(:method_procs).size
end
context "when calling an unextendable method" do
@@ -151,11 +169,24 @@ def foo
context "when unextending the module afterwards" do
should "remove the module from extended_modules" do
+ exception = assert_raises(NoMethodError) do
+ @c.id
+ end
+ assert_equal "private method `id' called for", exception.message[0..29]
+ assert_equal "C", @c.send(:id)
+
@c.extend U
assert @c.meta_class.extended_modules.include?(U)
+ assert_equal "private method `id' called for", exception.message[0..29]
+ assert_equal "U", @c.send(:id)
+
+ @c.extend B
+ assert_equal "B", @c.send(:id)
- @c.unextend U
+ @c.unextend
assert !@c.meta_class.extended_modules.include?(U)
+ assert_equal "private method `id' called for", exception.message[0..29]
+ assert_equal "C", @c.send(:id)
end
should "remove the module but when passed a block only when it passes" do

0 comments on commit e73c5a3

Please sign in to comment.