10

I am trying to work our for myself access modifiers in Ruby. I have:

class Person
  def initialize (first_name, last_name, age)
        @first_name=first_name
        @last_name=last_name
        @age=age
    end


    def show()
        puts @first_name
        puts @last_name
        puts @age
    end

protected
  def compare(other)
    self.instance_variable_get(:@age)<=>other.instance_variable_get(:@age)
  end

end

p1=Person.new("Some", "Body", "99")
p1.show
puts "\n"

p2=Person.new("Who", "Ever", "21")
p2.show
puts "\n"

p1.compare(p2)

I am getting the error "protected method `compare' called for # (NoMethodError)" I have tried calling from within the class and without. I pasted the without version here. I thought that protected methods could be called on other objects of the same class. What does this error mean and how would I properly use a protected method here? Thank you for your help.

e_n
  • 103
  • 1
  • 5
  • I see, so a protected and private class differ only in that the protected allows for use with a subclass of the main class, but neither are directly accessible. Thank you so much, that was driving me nuts. – e_n Sep 08 '14 at 13:29

2 Answers2

13

You got the wrong view of the protected visibility. The Ruby doc says:

The second visibility is protected. When calling a protected method the sender must be a subclass of the receiver or the receiver must be a subclass of the sender. Otherwise a NoMethodError will be raised.

So the restriction of the visibility is applied to the sender, not the receiver as what you thought.

If you want to call compare outside of the instance methods, you need to use public visibility. You need to remove the protected modifier if you can. This is the recommended way.

If the code is fixed and you cannot modify that piece of code, you can use the Object#send method. Object#send will bypass the visibility constraint and can access even private methods.

p1.send(:compare, p2)

Or you can reopen the class and change the visibility of the compare class:

# you code here

# reopen and modify visibility
class Person
  public :compare
end

p1.compare(p2)
Arie Xiao
  • 13,909
  • 3
  • 31
  • 30
  • Whoa, so there is no way to truly have a secure method in ruby? Is it considered bad form to use the send method? Why would there be the method in the first place, is it only to circumvent access restrictions, or is there a more pointed reason or circumstance it was designed for? I appreciate the help, I am just trying to understand. – e_n Sep 08 '14 at 13:46
  • `Object#send` really isn't that big a deal since you could always monkey patch and change the method visibility anyway. If you need private methods to be _impossible_ to mess with, Ruby probably isn't the language for you. – ZombieDev Sep 09 '15 at 21:16
-1

You can call a protected method in a public method of the class...

class Person
  def initialize (first_name, last_name, age)
    @first_name=first_name
    @last_name=last_name
    @age=age
  end

  def same_age?(other)
    age == other.age
  end

  def show
    puts @first_name
    puts @last_name
    puts @age
  end

  protected

  def age
    @age
  end

end

p1=Person.new("Some", "Body", "99")
p1.show
puts "\n"

p2=Person.new("Who", "Ever", "21")
p2.show
puts "\n"

# calls a method that calls a protected method
p1.same_age?(p2)
=> false

# but you can't call #age directly...
begin 
 p1.age
rescue NoMethodError
  puts "no method error (protected)"
end
SteveTurczyn
  • 36,057
  • 6
  • 41
  • 53