Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are there good reasons for 'private' to work the way it does in Ruby?

It took me a while to understand how private methods work in Ruby, and it really strikes me as being very awkward. Does anyone know if there are good reasons for private methods to be handled the way they are? Is it just historic reasons? Or implementation reasons? Or are there good solid logical reasons (ie. semantic)?

For example:

class Person   private   attr_reader :weight end  class Spy < Person  private   attr_accessor :code  public   def test     code          #(1) OK: you can call a private method in self     Spy.new.code  #(2) ERROR: cannot call a private method on any other object     self.code     #(3) ERROR!!! cannot call a private method explicitly on 'self'     code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!     self.code="z" #(5) OK! This is the only case where explicit 'self' is ok     weight        #(6) OK! You can call a private method defined in a base class   end end 
  • Ruby's behaviour on lines (1), (2) and (5) seems reasonable.
  • The fact that (6) is ok is a bit strange, especially coming from Java and C++. Any good reason for this?
  • I really do not understand why (3) fails ! An explanation, anyone?
  • The problem on line (4) looks like an ambiguity in the grammar, which has nothing to do with 'private'.

Any ideas?

like image 973
MiniQuark Avatar asked Oct 14 '09 09:10

MiniQuark


People also ask

What does private do in Ruby?

The keyword private tells Ruby that all methods defined from now on, are supposed to be private. They can be called from within the object (from other methods that the class defines), but not from outside.

Are private methods inherited in Ruby?

In general, private methods can't be inherited in object-oriented programming languages. But in Ruby, private methods can also be inherited just like protected and public methods. The public method can be accessed outside the class in which they are defined.

How can we call private methods in Ruby?

Understanding Private Methods in Ruby You can only use a private method by itself. It's the same method, but you have to call it like this. Private methods are always called within the context of self .

What does private do in rails controller?

If a method is private, it may be called only within the context of the calling object---it is never possible to access another object instance's private methods directly, even if the object is of the same class as the caller. For protected methods, they are accessible from objects of the same class (or children).


1 Answers

You might find it helpful to read ruby's definition of public, private and protected. (Skip to Access Control)

Ruby's private is analogous to Java's protected. There is no Ruby equivalent of Java's private. EDIT: This solution now provides a method of faking it Java's ideal of private in Ruby objects.

Private is defined as methods/variables that can only be called implicitly. This is why statements 2 and 3 fail. In other words, private limits methods/variables to the context of a class or subclass in which they are defined. Inheritance passes private methods to the subclasses and can therefore be accessed with an implicit self. (Explaining why statement 6 works.)

I think you're looking for something closer to protected. Which behaves similarly to Java accessors that are not given a visibility (eg: public, private, protected) By changing the private in Spy to protected all 6 of your statements work. Protected methods can be called by any instance of the defining class or their subclasses. Either explicitly or implicitly called on self are valid statements for protected methods so long as the caller is either the class of the object responding to the call, or inherits from it.

class Person   private   attr_reader :weight end  class Spy < Person  protected   attr_accessor :code  public   def test     code          #(1) OK: you can call a private method in self     Spy.new.code  #(2) OK: Calling protected method on another instance from same class family or a descendant.     self.code     #(3) OK: Calling protected method on with explicit self is allowed with protected     code="xyz"    #(4) Ok, it runs, but it actually creates a local variable!!!     self.code="z" #(5) OK! This is the only case where explicit 'self' is ok     weight        #(6) OK! You can call a private method defined in a base class   end end  s = Spy.new s.test # succeeds s.code #(7) Error: Calling protected method outside of the class or its descendants. 

As for statement 4. You are correct in assuming this is to avoid ambiguity. It's more a safeguard to the potential harm of ruby's dynamic nature. It ensures that you cannot override accessors by opening up the class again later. A situation that can arise, for example by eval'ing tainted code.

I can only speculate on he design decisions that led to these behaviours. For most of it I feel it comes down to the dynamic nature of the language.

P.S. If you really want to give things the java definition of private. Only available to the class in which it's defined, not even subclasses. You could add an self.inherited method to your classes to remove references to the methods you want to limit access to.

Making the weight attribute inaccessible from subclasses:

class Person   private   attr_reader :weight    def initialize     @weight = 5   end    def self.inherited(subclass)     subclass.send :undef_method, :weight   end end  class Spy < Person  private   attr_accessor :code  public   def test      weight          end end  Person.new.send(:weight)  # => 5 Spy.new.send(:weight)  #=> Unhelpful undefined method error 

It may make more sense to replace the undef_method call to something like this:

  def self.inherited(subclass)     subclass.class_eval %{       def weight          raise "Private method called from subclass. Access Denied"       end      }   end 

Which provides a much more helpful error and the same functionality.

The send is necessary to get around calling private methods for other classes. Only used to prove that things are actually working.

Which in hindsight, makes private and protected useless. If you're really serious about protecting your methods you will have to override send to block them. The following code does that based on the private_methods of the object:

def send_that_blocks_private_methods(method, *args)   if private_methods.include?(method.to_s)     raise "Private method #{method} cannot called be called with send."   else     send_that_allows_private_methods(method, *args)   end end  alias_method :send_that_allows_private_methods, :send alias_method :send, :send_that_blocks_private_methods private :send_that_allows_private_methods 

You could specify a class_variable of private_methods you want to block access to instead of denying access to all private methods. You could also make send private, but there are legitimate uses of calling send from outside an object.

like image 100
EmFi Avatar answered Oct 21 '22 15:10

EmFi