Permalink
Browse files

Get method_missing working again. I'm not sure how it was working bef…

…ore, but the tests didn't pass.
  • Loading branch information...
1 parent 11c8748 commit 018b25811118c3bb95162324b3feae3a1e2b9923 @wycats wycats committed Jul 3, 2011
Showing with 39 additions and 22 deletions.
  1. +19 −7 lib/thor/task.rb
  2. +20 −15 spec/task_spec.rb
View
@@ -18,8 +18,15 @@ def hidden?
# By default, a task invokes a method in the thor class. You can change this
# implementation to create custom tasks.
def run(instance, args=[])
- public_method?(instance) ?
- instance.send(name, *args) : instance.class.handle_no_task_error(name)
+ if private_method?(instance)
+ instance.class.handle_no_task_error(name)
+ elsif public_method?(instance)
+ instance.send(name, *args)
+ elsif local_method?(instance, :method_missing)
+ instance.send(:method_missing, name.to_sym, *args)
+ else
+ instance.class.handle_no_task_error(name)
+ end
rescue ArgumentError => e
handle_argument_error?(instance, e, caller) ?
instance.class.handle_argument_error(self, e) : (raise e)
@@ -70,6 +77,15 @@ def public_method?(instance) #:nodoc:
!(instance.public_methods & [name.to_s, name.to_sym]).empty?
end
+ def private_method?(instance)
+ !(instance.private_methods & [name.to_s, name.to_sym]).empty?
+ end
+
+ def local_method?(instance, name)
+ methods = instance.public_methods(false) + instance.private_methods(false) + instance.protected_methods(false)
+ !(methods & [name.to_s, name.to_sym]).empty?
+ end
+
def sans_backtrace(backtrace, caller) #:nodoc:
saned = backtrace.reject { |frame| frame =~ FILE_REGEXP }
saned -= caller
@@ -104,11 +120,7 @@ def initialize(name, options=nil)
def run(instance, args=[])
if (instance.methods & [name.to_s, name.to_sym]).empty?
- if ((instance.protected_methods + instance.public_methods) & ([:method_missing, "method_missing"])).empty?
- super
- else
- instance.send(:method_missing, name.to_sym, *args)
- end
+ super
else
instance.class.handle_no_task_error(name)
end
View
@@ -11,21 +11,18 @@ def task(options={})
describe "#formatted_usage" do
it "includes namespace within usage" do
- Object.stub!(:namespace).and_return("foo")
- Object.stub!(:arguments).and_return([])
- task(:bar => :required).formatted_usage(Object).should == "foo:can_has --bar=BAR"
+ object = Struct.new(:namespace, :arguments).new("foo", [])
+ task(:bar => :required).formatted_usage(object).should == "foo:can_has --bar=BAR"
end
it "removes default from namespace" do
- Object.stub!(:namespace).and_return("default:foo")
- Object.stub!(:arguments).and_return([])
- task(:bar => :required).formatted_usage(Object).should == ":foo:can_has --bar=BAR"
+ object = Struct.new(:namespace, :arguments).new("default:foo", [])
+ task(:bar => :required).formatted_usage(object).should == ":foo:can_has --bar=BAR"
end
it "injects arguments into usage" do
- Object.stub!(:namespace).and_return("foo")
- Object.stub!(:arguments).and_return([ Thor::Argument.new(:bar, nil, true, :string) ])
- task(:foo => :required).formatted_usage(Object).should == "foo:can_has BAR --foo=FOO"
+ object = Struct.new(:namespace, :arguments).new("foo", [Thor::Argument.new(:bar, nil, true, :string)])
+ task(:foo => :required).formatted_usage(object).should == "foo:can_has BAR --foo=FOO"
end
end
@@ -55,15 +52,23 @@ def task(options={})
describe "#run" do
it "runs a task by calling a method in the given instance" do
mock = mock()
- mock.should_receive(:send).with("can_has", 1, 2, 3)
- task.run(mock, [1, 2, 3])
+ mock.should_receive(:can_has).and_return {|*args| args }
+ task.run(mock, [1, 2, 3]).should == [1, 2, 3]
end
it "raises an error if the method to be invoked is private" do
- mock = mock()
- mock.should_receive(:private_methods).and_return(['can_has'])
- mock.class.should_receive(:handle_no_task_error).with("can_has")
- task.run(mock)
+ klass = Class.new do
+ def self.handle_no_task_error(name)
+ name
+ end
+
+ private
+ def can_has
+ "fail"
+ end
+ end
+
+ task.run(klass.new).should == "can_has"
end
end
end

1 comment on commit 018b258

rleber commented on 018b258 Jul 7, 2011

I'm pretty sure this is going to break issue 144 (MacRuby bug) again. See previous commit 677b1e0 and my proposed patch in issue 146.

In a nutshell, there is a bug in MacRuby (#204 https://www.macruby.org/trac/ticket/204) whereby a method can be BOTH public and private. Specifically, Kernel#exec is, which causes bundler to break.

I think a fix like changing private_method? to the following might work, even in MacRuby:

def private_method?(instance)
  !(instance.private_methods & [name.to_s, name.to_sym]).empty? &&
  (instance.private_methods & instance.public_methods & [name.to_s, name.to_sym).empty?
end
Please sign in to comment.