Ruby 2 Access Control

The only easy way to change an object's state in Ruby is by calling one of its methods. Control access to the methods, and you have controlled access to the object. A good rule of the thumb is never to expose methods that could leave an object in an invalid state.
Ruby gives you three levels of protection:
  1. Public methods can be called by everyone - no access control is enforced. A class's instance methods (these do not belong only to one object; instead, every instance of the class can call them) are public by default; anyone can call them. The initialize method is always private.
  2. Protected methods can be invoked only by objects of the defining class and its subclasses. Access is kept within the family. However, usage of protected is limited.
  3. Private methods cannot be called with an explicit receiver - the receiver is always self. This means that private methods can be called only in the context of the current object; you cannot invoke another object's private methods.
Access control is determined dynamically, as the program runs, not statically. You will get an access violation only when the code attempts to execute the restricted method. Let us refer to the program p047classaccess.rb below:
  1. # p047classaccess.rb  
  2. class ClassAccess  
  3.   def m1          # this method is public  
  4.   end  
  5.   protected  
  6.     def m2        # this method is protected  
  7.     end  
  8.   private  
  9.     def m3        # this method is private  
  10.     end  
  11. end  
  12. ca = ClassAccess.new  
  13. ca.m1  
  14. #ca.m2  
  15. #ca.m3  
If you remove the comments of the last two statements in the above program, you will get an access violation runtime error.
Alternatively, you can set access levels of named methods by listing them as arguments to the access control functions.
  1. class ClassAccess  
  2.   def m1       # this method is public  
  3.   end  
  4.   public :m1  
  5.   protected :m2:m3  
  6.   private :m4:m5  
  7. end  
Here is an example (p047zclassaccess.rb) for 'protected' access control:
  1. # p047zclassaccess.rb  
  2. class Person  
  3.   def initialize(age)  
  4.     @age = age  
  5.   end  
  6.   def age  
  7.     @age  
  8.   end  
  9.   def compare_age(c)  
  10.     if c.age > age  
  11.       "The other object's age is bigger."  
  12.     else  
  13.       "The other object's age is the same or smaller."  
  14.     end  
  15.   end  
  16.   protected :age  
  17. end  
  18.   
  19. chris = Person.new(25)  
  20. marcos = Person.new(34)  
  21. puts chris.compare_age(marcos)  
  22. #puts chris.age  
The output is:
  1. >ruby p047zclassaccess.rb  
  2. The other object's age is bigger.  
  3. >Exit code: 0  
In the preceding example, we compare one Person instance with another Person instance. The comparison, however, depends on the result of a call to the method age. The object doing the comparing (chris, in the example) has to ask the other object (marcos) to execute its age method. So, age can't be private.

That's where the protected level comes in. With age protected rather than private, chris can ask marcos to execute age, because chris and marcos are both instances of the same class. But if you try to call the age method of a Person object when self is anything other than a Person object, the method will fail.

A protected method is thus like a private method, but with an exemption for cases where the class of self (chris) and the class of the object having the method called on it (marcos) are the same.

Note that if you remove the comment from the last statement in the program ie. when you use age directly, Ruby throws an exception.
In Ruby, public, private and protected apply only to methods. Instance and class variables are encapsulated and effectively private, and constants are effectively public. There is no way to make an instance variable accessible from outside a class (except by defining an accessor method). And there is no way to define a constant that is inaccessible to outside use.

Overriding private methods

Private methods cannot normally be invoked from outside the class that defines them. But they are inherited by subclasses. This means that subclasses can invoke them and can override them.
Classes often use private methods as internal helper methods. They are not part of the public API of the class and are not intended to be visible. If you happen to define a method in your subclass that has the same name as a private method in the superclass, you will have inadvertently overridden the superclass's internal utility method, and this will almost certainly cause unintended behavior.

Accessor methods

Encapsulation is achieved when the instance variables are private to an object and you have public getters and setters (in Ruby, we call them attribute readers and attribute writers). To make instance variables available, Ruby providesaccessor methods that return their values. The program p048accessor.rb illustrates the same.
  1. # p048accessor.rb  
  2. # First without accessor methods  
  3. class Song  
  4.   def initialize(name, artist)  
  5.     @name     = name  
  6.     @artist   = artist  
  7.   end  
  8.   def name  
  9.     @name  
  10.   end  
  11.   def artist  
  12.     @artist  
  13.   end  
  14. end  
  15.   
  16. song = Song.new("Brazil""Ivete Sangalo")  
  17. puts song.name  
  18. puts song.artist  
  19.   
  20. # Now, with accessor methods  
  21. class Song  
  22.   def initialize(name, artist)  
  23.     @name     = name  
  24.     @artist   = artist  
  25.   end  
  26.   attr_reader :name:artist  # create reader only  
  27.   # For creating reader and writer methods  
  28.   # attr_accessor :name  
  29.   # For creating writer methods  
  30.   # attr_writer :name  
  31.   
  32. end  
  33.   
  34. song = Song.new("Brazil""Ivete Sangalo")  
  35. puts song.name  
  36. puts song.artist  

Are instance variables inherited by a sub-class?

David Black, the author of Ruby for Rails, has this to say: Instance variables are per-object, not per-class, and they're not inherited. But if a method uses one, and that method is available to subclasses, then it will still use the variable -- but "the variable" in the sense of one per object. See the following program - p049instvarinherit.rb:
  1. # p049instvarinherit.rb  
  2. class C  
  3.   def initialize  
  4.     @n = 100  
  5.   end  
  6.   
  7.   def increase_n  
  8.     @n *= 20  
  9.   end  
  10. end  
  11.   
  12. class D < C  
  13.   def show_n  
  14.     puts "n is #{@n}"  
  15.   end  
  16. end  
  17.   
  18. d = D.new  
  19. d.increase_n  
  20. d.show_n  
The output is:
  1. >ruby p049instvarinherit.rb  
  2. n is 2000  
  3. >Exit code: 0  
The @n in D's methods is the same name (for each instance) as the one in C.
All Ruby objects have a set of instance variables. These are not defined by the object's class - they are simply created when a value is assigned to them. Because instance variables are not defined by a class, they are unrelated to subclassing and the inheritance mechanism.

Top-level methods

When you write code at the top level, Ruby provides you automatically with a default self. This object is a direct instance of Object. When you ask it to describe itself:
  1. puts self  
it says:
  1. main  
The object main is the current object as soon as your program starts up.

Suppose you define a method at the top level:
  1. def talk  
  2.   puts "Hello"  
  3. end  
Who, or what, does the method belong to? It's not inside a class or module definition block, so it doesn't appear to be an instance method of a class or module. It's not attached to any particular object (as in def obj.talk). What is it? When we define top-level methods, we're actually creating (private) instance methods for class Object.
Because top-level methods are private, you can't call them with an explicit receiver; you can only call them by using the implied receiver, self. That means self must be an object on whose method search path the given top-level method lies. But every object's search path includes the Kernel module, because the class Object mixes in Kernel, and every object's class has Object as an ancestor. That means you can always call any top-level method, wherever you are in your program. It also means you can never use an explicit receiver on a top-level method.
From our earliest examples onward, we've been making bareword-style calls to puts and print, like this one:
  1. puts 'Hello'  
puts and print are built-in private instance methods of Kernel. That's why you can - indeed, must - call them without a receiver.