views:

314

answers:

4
w = Widget.new # Create a Widget
w.send :utility_method # Invoke private method!
w.instance_eval { utility_method } # Another way to invoke it
w.instance_eval { @x } # Read instance variable of w

Looking at the example above which relates to the Widget class (below), the send and instance_eval methods violate all of the protections provided by private and protected visibility. If so, why bother with private and protected access in Ruby at all since there is no guarantee that your definitions will be honored?

class Widget
  def x # Accessor method for @x
   @x
  end
  protected :x # Make it protected
  def utility_method # Define a method
   nil
  end
  private :utility_method # And make it private
end
+1  A: 

At the very least you express what the public API of the Widget class is.

bartt
+5  A: 

ruby believes in giving you the power to do what you want. it just doesn't make it easy to inadvertently shoot your foot off - if you want to subvert the private declarations, you have to use syntax that makes it clear you are doing so. note that the person finally deciding what the code should or shouldn't do is the person using a library, not the person writing it.

Martin DeMello
+1  A: 

If you really want to protect instances of Widget, you can do this (and a bunch of other stuff; the code here is not a complete security solution, merely indicative):

class Widget

  def some_public_method
    ...
  end

  private

  def utility_method
    ...
  end

  def send(method, *args, &block)
    raise NotImplementedError.new('Widget is secure. Stop trying to hack me.')
  end

  def instance_eval(&block)
    raise NotImplementedError.new('Widget is secure. Stop trying to hack me.')
  end

  class <<self
    private
    def class_eval(&block)
      raise NotImplementedError.new('Widget is secure. Stop trying to hack me.')
    end
  end
end

Widget.freeze
James A. Rosen
(But I wouldn't recommend it, since Widget would be incompatible with nearly all libraries that use metaprogramming.)
James A. Rosen
Interesting potential solution. What are you preventing by redefining class_eval? I don't understand that last section: "class << self .. end"
pez_dispenser
class <<self ... end evaluates the contents in the metaclass scope. That is, I'm privatizing the _class_ method class_eval, not the instance method. See http://stackoverflow.com/questions/678037/. The reason I'm protecting that is so you can't modify the class to unsecure it. You should probably also freeze the class.
James A. Rosen
Don't forget __send__. And even then this is prehackable by overriding `Module#method_added` before they `require 'widget'`. Overriding class_eval doesn't buy you much without freezing, b/c then you can still use the `class Widget ... end` syntax. In short, people can modify your code. Learn to live with it, it's an interpreted language.
rampion
+5  A: 

I cannot comment, because of low rep :/.

Redefining send is no use, because send is just the common name for __send__ (thats underscore,underscore,"send",underscore,underscore), which is the method actually implementing message sending. Redefining any __method__ is not recommended. Additionally, the other person can also reopen the class and revert the definition:

class Widget
  def send(method, *args, &block)
    super
  end
  #and so on
end

In Ruby 1.9, the behaviour is slightly different: #send actually honors visibility, __send__ doesn't.

private in Ruby has more of a declarative purpose: methods declared as private are an implementation detail and not an API detail. You are not allowed to send a message from the outside by accident. But anyone can still forcefully circumvent that restriction, if they see fit - on their own account.

Skade