views:

103

answers:

5

Using dynamic method calls (#send or #method) the methods visibility is ignored.
is there a simple way to dynamically call a method that will fail calling a private method?

+1  A: 

Thought I don't understand why you want to do so, you can use eval.

class Klass
  private
    def private_method(arg)
    end
end

k = Klass.new
m = "private_method"
eval "k.#{m}('an arg')"

NoMethodError: private method `private_method' called for #<Klass:0x128a7c0>
    from (irb):15
    from (irb):15
Simone Carletti
Eval is realy bad. You need to check beforehand, if m really containts only a method name. `l= "inspect; \`rm -rf *\`; puts"` Then the attaker will try to fool you by thinking of something that looks to you like a method name, but realy is not.
johannes
A: 

If you are using ruby-1.9, you can use Object#public_send which does what you want.

If you use ruby-1.8.7 or earlier you have to write your own Object#public_send

class Object
  def public_send(name, *args)
    unless public_methods.include?(name.to_s)
      raise NoMethodError.new("undefined method `#{name}' for \"#{self.inspect}\":#{self.class}")
    end
    send(name, *args)
  end
end

Or you could write your own Object#public_method which behaves like Object#method but only for public methods

class Object
  def public_method(name)
    unless public_methods.include?(name.to_s)
      raise NameError.new("undefined method `#{name}' for class `#{self.class}'")
    end
    method(name)
  end
end
johannes
+2  A: 

As I know - you need method public_send:

----------------------------------------------------- Object#public_send
     obj.public_send(symbol [, args...])  => obj

     From Ruby 1.9.1
------------------------------------------------------------------------
     Invokes the method identified by _symbol_, passing it any arguments
     specified. Unlike send, public_send calls public methods only.

        1.public_send(:puts, "hello")  # causes NoMethodError
aaz
this is not available in ruby 1.8.7
johannes
Actually, send is sufficient in 1.9 I believe. Rumor has it send cares in 1.9 but __send__ doesn't. But I haven't confirmed this.
Chuck Vose
A: 

It's true though, eval really is I think the only way to actually do it pre-1.9. If you want to read more about visibility Jamis Buck wrote an awesome article about what method visibility actually means in Ruby.

Much like other things in Ruby visibility is ever so slightly different than other languages.

Chuck Vose
A: 
require 'backports'  # Needed for Ruby < 1.9

my_object.public_send :method, *args

My backports gem defines all of Ruby 1.8.7 and many Ruby 1.9 methods, including public_send, which is what you want to use here.

Marc-André Lafortune