It’s easy to code in Ruby for years, and never pay attention to method permissions. I know, because I did it. I came from a C/C++ background, so I understood the concepts of
private. And I vaguely understood that Ruby’s
protected fell somewhere in between. I just never really cared enough to look it up. Eventually I got curious. I’ll run down the different types of method visibility here.
It’s hard to think of a good, simple example to use for this. I’ll go with hobbits. They’re friendly to other species, but they have private lives, as well. Much of their privacy seems to be open to other hobbits, since they appear to be able to wander into each others’ houses in the shire at will. Of course, they also have a few choice secrets kept just to themselves. They have all the qualities of a well-rounded Ruby method. Let’s define their class here:
class Hobbit def initialize(name, rooms, has_ring) @name, @rooms, @has_ring = name, rooms, has_ring end def name @name end def name_of(hobbit) hobbit.name end def rooms_of(hobbit) hobbit.rooms end def hobbit_has_ring?(hobbit) hobbit.has_ring? end protected def rooms @rooms end private def has_ring? @has_ring end end
Each hobbit has three attributes. Their name is public, the number of rooms in their house is protected, and whether or not they have The Ring is private. There are methods to read these attributes, and to try to read them from other hobbits. Now let’s create a couple hobbits to work with:
irb(main):001:0> require 'hobbit.rb' => true irb(main):002:0> frodo = Hobbit.new('Frodo', 3, true) => #<Hobbit:0x10038b5c0 @rooms=3, @name="Frodo", @has_ring=true> irb(main):003:0> samwise = Hobbit.new('Samwise', 2, false) => #<Hobbit:0x1003769e0 @rooms=2, @name="samwise", @has_ring=false>
This is the default method type in Ruby (and most languages), and it means that it can be called from anywhere, by any hunk of code that knows what the object is. We can easily find out the names of our hobbits:
irb(main):004:0> frodo.name => "Frodo" irb(main):005:0> samwise.name => "Samwise"
Now, what about the rooms in their houses? This info is accessible only through protected methods:
irb(main):006:0> frodo.rooms NoMethodError: protected method `rooms' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true> from (irb):6 from :0 irb(main):007:0> frodo.rooms_of(frodo) => 3 irb(main):008:0> frodo.rooms_of(samwise) => 2
As you can see, we can’t call the hobbits’ room methods directly, but one hobbit can call the protected rooms method for itself, or any other hobbit. In purely programming terms, public methods are globally accessible, where protected methods can only be called by other methods in the class.
What about ring status? This info is locked in
private methods. How can we access this?
irb(main):013:0> frodo.has_ring? NoMethodError: private method `has_ring?' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true> from (irb):13 from :0 irb(main):014:0> frodo.hobbit_has_ring?(frodo) NoMethodError: private method `has_ring?' called for #<Hobbit:0x10034a4a8 @rooms=3, @name="Frodo", @has_ring=true> from ./hobbit.rb:19:in `hobbit_has_ring?' from (irb):14 from :0 irb(main):015:0> frodo.hobbit_has_ring?(samwise) NoMethodError: private method `has_ring?' called for #<Hobbit:0x100374c80 @rooms=2, @name="Samwise", @has_ring=false> from ./hobbit.rb:19:in `hobbit_has_ring?' from (irb):15 from :0
We can’t access the
has_ring? method directly. We can’t access it indirectly from another method, specifying the hobbit we’re interested in. Not even if that hobbit is ourselves! Let’s reopen our class and add the
class Hobbit def i_have_the_ring? has_ring? end end
Now frodo has a way to check if he has the ring:
irb(main):021:0> frodo.i_have_the_ring? => true
In Ruby, protected methods are still open to other objects of the same type. So as long as the “receiver” (
hobbit.rooms) is the same class as the calling object, method access is granted.
But private methods are not allowed to specify a receiver at all. I can’t say
frodo.has_ring? or even
self.has_ring? inside the frodo object at all. I’m only allowed to say
has_ring? and the object will assume it’s calling its own method.
public methods are fairly straightforward, the difference between
private can cause problems if you’re not aware of it. Ruby’s
private implementation is even stricter than C’s, not allowing receivers. But
protected balances that out with the ability to “walk into sibling objects’ houses”.
I tend to use
protected methods when I don’t intend for them to be called directly, and I don’t write tests for them. I write tests for the
public methods that call them. And I generally avoid
private methods, because I can’t envision needing to block sibling-to-sibling access (although I probably should if I’m not testing them, right?) but I don’t want to spend hours debugging a method call that breaks the no-receiver rule.