1
votes

If a method is protected, it may be called by any instance of the defining class or its subclasses. 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’s private methods directly, even if the object is of the same class as the caller.

Programming Ruby, "Classes, Objects, and Variables: Access Control"

This definition i got from net when searching for the difference between private and protected methods in ruby.

I have 2 doubts in this

class Abc
  def abc
    xyz
  end
 protected 
  def xyz
    p "hai"
  end
end

a=Abc.new
a.abc

In this i am calling xyz implicitly on object a, this is how i will call xyz even if it is private.So "it may be called by any instance of the defining class" what does this mean ?????

Secondly,

 class Abc
   def abc(obj)
     obj.xyz1
     obj.xyz2
     obj.xyz3
   end
 end

 class Xyz
   def xyz1
     p "called"
   end

  private 
   def xyz2
     p "called"
   end

  protected
   def xyz3
     p "called"
   end

 end

 a=Abc.new
 b=Xyz.new
 a.abc(b)

In this i can call obj b's xyz1 method on object a.But i cant call obj b's protected xyz3 and private method xyz2 method on object a.So "it is never possible to access another object’s private methods directly, even if the object is of the same class as the caller." what does this exact mean???

can anybody help me to understand this one with better examples????

3

3 Answers

7
votes

Private Methods

To define a private method, we use the private keyword, which is actually a built-in method implemented in a class called Module. A private method can only be called by another method within the class on which it was defined (or one of its subclasses).

class Koan
  def call_say_koan
    say_koan
  end

  private
    def say_koan
      puts "What is the sound of one hand clapping?"
    end
end

k = Koan.new
k.say_koan    # Output: NoMethodError: private method `say_koan' called for #<Koan:0x000000021e7380>
k.call_say_koan        # Output: What is the sound of one hand clapping?

In the above example, we could not call the say_koan private method directly (from outside the class), but we could call the call_say_koan public method which, in turn, called say_koan.

Also in the above example, the built-in private method was used with no arguments. Hence, all methods defined below it were made private.

The private method can also be used with previously defined method names (passed as symbols) as arguments.

class Foo
  def some_method
  end

  private :some_method
end

In order to make a class method private, use the private_class_method keyword/method instead of private.

Private methods can't be called with a receiver, such as self. Trying to call the say_koan method with self as a receiver (self.say_koan) within call_say_koan would result in the following exception:

NoMethodError: private method `say_koan' called for #<Koan:0x000000021eb548>

As of Ruby 2.0, the respond_to? method will return false when given a private method as an argument.

k.respond_to? :say_koan  # Output: => false

To list all private instance methods in a class, use the private_instance_methods built-in method. For private class methods, use private_methods.

Koan.private_instance_methods(false)  # Output => [:say_koan]

Protected Methods

To define a protected method, we use the protected keyword (which is actually a method). Like private methods, protected methods can also be called by other methods within the class on which it was defined (or one of its subclasses). The difference is, protected methods can also be called from within other instances of the same class.

There is no such thing as a protected a class method, Ruby only supports protected instance methods.

Let's suppose we need to select a few meditators to participate in a study. To find the most experienced meditators, we need to compare their total hours of meditation. However, we don't want the number of hours to be visible.

class Meditator
  def initialize(hours)
    @hours = hours
  end

  def more_experienced?(other_person)
    hours > other_person.hours
  end

  protected
    attr_reader :hours  # We have made the accessor protected
end

m1 = Meditator.new 3000
m2 = Meditator.new 5000

m2.more_experienced? m1  # Output: => true
m1.more_experienced? m2  # Output: => false

Similar code could be used to protect any kind of sensitive data from outside access (outside the class and its instances), although protected methods are not commonly employed in Ruby.

When called with no arguments (as in the above example), the protected method turns all methods defined below it into protected methods. It can also be used to protect previously defined methods, as in the following example.

class Foo
  def some_method
  end

  protected :some_method
end

To list all protected instance methods in a class, use the protected_instance_methods built-in method. For protected class methods, use protected_methods.

Meditator.protected_instance_methods(false)  # Output: => [:hours]
5
votes

The best way to think of protected and private methods is how they affect whether you can explicitly specify a receiver in front of a method call, i.e. think of it as a technical rule, rather than some meta idea.

  1. private method: You cannot explicitly specify a receiver in front of a private method call. Ever(see comment). Because ruby always calls a method with some receiver, ruby uses whatever object is currently assigned to the self variable as the receiver.

  2. protected method: In some situations, you can explicitly specify a receiver for a protected method call.

Then it's just a matter of exploring when ruby lets you explicitly specify a receiver in front of a protected method call:

class Dog
  def execute_pro(dog)
    dog.pro_meth
  end

  protected
  def pro_meth
    puts 'protected'
  end
end

d1 = Dog.new
p d1.protected_methods
d1.pro_meth

--output:--
[:pro_meth]

1.rb:15:in `<main>': protected method `pro_meth' called for #<Dog:0x007f8ef90e0eb8> (NoMethodError)

The example above demonstrates that the statement:

If a method is protected, it may be called by any instance of the defining class or its subclasses.

is too general. But:

class Dog
  def execute_pro(dog)
    dog.pro_meth
  end

  protected
  def pro_meth
    puts 'protected'
  end
end

d1 = Dog.new
d2 = Dog.new
p d1.protected_methods

d1.execute_pro d2

--output:--
[:pro_meth]
protected

it is never possible to access another object’s private methods directly, even if the object is of the same class as the caller.

In the example above, the caller is d1, and inside of methods called by d1 you are able to access d2's protected methods directly, i.e. you can explicitly specify a receiver in front of a protected method call, because d2 is of the same class as d1. You would not be able to directly access d2's private methods there--because you can NEVER can't explicitly specify a receiver in front of a private method call. That might lead you to believe..."Okay, private and protected are the same thing: I just need to drop the explicit receiver if the method is private:

class Dog
  def execute_pro(dog)
    pro_meth #<*****CHANGE HERE
  end

  private   #<****CHANGE HERE
  def pro_meth
    puts 'protected'
  end
end

d1 = Dog.new
d2 = Dog.new

d1.execute_pro d2

--output:--
protected

The output is the same...but the conclusion, "Private and protected are the same thing: I just need to drop the explicit receiver if the method is private." is wrong. The example is too simple and it masks a subtle difference between private and protected. Here's a better example:

class Dog1
  def initialize(num)
    @num = num
  end

  def execute_pro(dog)
    dog.pro_meth 
  end

  protected  
  def pro_meth
    puts "protected -> #{@num}"
  end
end

d1 = Dog1.new 1
d2 = Dog1.new 2

d1.execute_pro d2

class Dog2
  def initialize(num)
    @num = num
  end

  def execute_pro(dog)
    pro_meth  #<****CHANGE HERE
  end

  private  #<******CHANGE HERE
  def pro_meth
    puts "protected -> #{@num}"
  end
end

d1 = Dog2.new 1
d2 = Dog2.new 2

d1.execute_pro d2

--output:--
protected -> 2
protected -> 1

The output shows that you cannot just convert a protected method to a private method and remove the explicit receiver from the method calls and always get the same results. In this case, ruby lets you explicitly specify a receiver in front of a protected method call in order to let you direct the method call to the proper object. But a protected method is not the same as a public method:

class Dog
  def execute_pro(dog)
    dog.pro_meth
  end

  protected
  def pro_meth
    puts 'protected'
  end
end

class Poodle < Dog
  def dostuff(dog)
    dog.pro_meth
  end
end

class Cat
  def dostuff(dog)
    dog.pro_meth
  end
end

d1 = Dog.new
p1 = Poodle.new
c1 = Cat.new

p1.dostuff d1
c1.dostuff d1

--output:--
protected

1.rb:21:in `dostuff': protected method `pro_meth' called for #<Dog:0x007fe9040d28f8> (NoMethodError)
    from 1.rb:30:in `<main>'

In the line c1.dostuff d1, the caller is c1, and inside methods called by c1, you cannot call Dog's protected methods--because the Cat class is not the same class or a subclass of the Dog class. Also note that if you tried removing the explicit receiver:

class Cat
  def dostuff(dog)
    pro_meth  #<****CHANGE HERE
  end
end 

that won't call Dog's protected method either, because inside of dostuff(), ruby assigns the caller to the self variable, and the caller is c1, so you get:

class Cat
  def dostuff(dog)
    #self = c1
    pro_meth
  end
end 

...
c1.dostuff d1

and ruby converts the method call pro_meth into self.pro_meth, which is equivalent to c1.pro_meth, and the Cat class does not define a method named pro_meth.

3
votes

The examples you picked somewhere are really confusing (especially the names being either abc or xyz, with very little context). Please allow me to use a different example.

The theories are like this :

– Both Private and Protected be accessed from outside the class through a public method.

The differences between Protected and Private are :

– Private method can not be called with a receiver (not even with #self). Unless … calling a Private setter method. If you try to remove the receiver, Ruby will create a local variable. Self is a must in this case.

– Protected may or may not use a receiver such as #self.

– Protected can access another object’s protected method that comes from the same class, Private can't. (see code for method #eat_more_than(other))

Now when it comes to Inheritance …

– Private methods can only be called on subclasses implicitly (simply just the name of the method) but not explicitly (using #self).

– Protected can be called both ways (with or without #self || implicitly or explicitly).

Let's take a look on the example below :

 class Dog
  attr_accessor :name, :age

  def initialize(n, a)
    self.name = n
    self.age = a
  end

  def accessing_private
    "#{self.name} in human years is #{human_years}. This is secret!"
  end

  def accessing_protected
    "Will this work? " + a_protected_method
  end

  def eat_more_than(other) 
  # accessing other instance's protected method from the same class
    daily_diet < other.daily_diet 
    "#{name} eats more than #{other.name}"
  end

  def boy 
    gender_method("boy") # accessing private setter method
  end

  protected

  def daily_diet 
    age * 2 # the younger, the more they have to eat 
  end

  def a_protected_method
    "Yes, I'm protected!"
  end

  private

  attr_writer :gender

  def gender_method(gender)
    self.gender = gender # private setter method requires self
    "#{name} is a #{gender}"
  end

  def human_years
    age * 8
  end
end

# Create the first object of Dog
blake = Dog.new("Blake", 5)

p blake.accessing_private # "Blake in human years is 16. This is secret!"

p blake.accessing_protected # "Will this work? Yes, I'm protected!"

# Create the second object of Dog
jackson = Dog.new("Jackson", 1)

# Below, protected methods from different objects of the same type/class 
# are proven to share access
p jackson.eat_more_than(blake) # true -> "Jackson eats more than Blake"

# Below, accessing private setter method through a public method.
p blake.boy # Blake is a boy 

Now on a subclass, you can not call an inherited private method with a receiver, but protected will do just fine either way (with or without a receiver) :

class Puppy < Dog 

  def accessing_protected_explicitly
    "Explicitly calls '#{self.a_protected_method}'"
  end

  def accessing_protected_implicitly
    "Implicitly calls '#{a_protected_method}'"
  end 

  def accessing_private_implicitly 
    "#{self.name} is #{human_years} years old in human years. This is a secret!" # implicit
  end 

  def accessing_private_explicitly
    "#{self.name} is #{self.human_years} years old in human years" # explicit -> error
  end 
end 

# Below, testing them on a subclass 
booboo = Puppy.new("Booboo", 1 )
p booboo.accessing_protected_explicitly # works 
p booboo.accessing_protected_implicitly # works
p booboo.accessing_private_implicitly # works 
p booboo.accessing_private_explicitly # error, called on a receiver

I hope that helps!