Ruby Access Control Basics: Public vs Private vs Protected methods
Ruby is a class-based object-oriented programming language. Meaning that every object is an instance of a class, and a class defines the state(variables) and behaviors(methods) of an object. An object is an entity with state and behavior, as defined by its class. As a Ruby developer, you’re going to be defining a lot of classes, especially when you start playing with Ruby on Rails; even before that, almost everything you interact with in Ruby is an object.
So, let’s talk access control. This is a way of protecting the behaviors(methods) defined in a class from being called by other objects not in the same class, or inherited from the same class. To limit a method’s access, Ruby uses the keywords, “private” and “protected”, to distinguish them from public methods. Below is a quick summary of what these different levels of access are, and where/when to use them. Or at least, how I use them.
Public methods: By default, all the methods you define will be public, unless you use the private or protected keyword to make them not public. Public methods describe the outside (outside of the class definition) behaviors of an object, and are called with the object as the explicit receiver(thing you’re calling the method on: receiver.method). Looking at our Person class below, our #speak method is public, so all instances of that class (read: objects) that you create, can call that #speak method.
class Person def speak
puts "Hey, Tj!"
end end you = Person.new
you.speak # "Hey, Tj!"
Private methods: are methods defined under the private keyword. Private methods can only be used within the class definition; they’re for internal usage. The only way to have external access to a private method is to call it within a public method. Also, private methods can not be called with an explicit receiver, the receiver is always implicitly self. Think of private methods as internal helper methods.
Going back to our previous example, I’ve added a private method to our Person class, #whisper, and created another Person object (a_hater), simply for my own comedic relief. Since our #whisper method is defined under “private”, it can’t be called with an explicit receiver, as we see the “NoMethodError” when a_hater tries to call it (lol).
class Person def speak
puts "Hey, Tj!"
end def whisper_louder
whisper
end # private methods are for internal usage within the defining class private def whisper
puts "His name's not really 'Tj'."
end endyou = Person.new
you.speak # "Hey, Tj!"a_hater = Person.new
a_hater.speak # "Hey, Tj!"
a_hater.whisper # NoMethodErrora_hater.whisper_louder # "His name's not really 'Tj'."
This “NoMethodError” lets us know that we’re attempting to call a private method “whisper” on a Person object. Which, we can’t do. We can only access the #whisper method externally by defining a public method, #whisper_louder, that calls it.
As you can see, our private method was only used internally (within the class definition) and not externally (called by an object). To reiterate, you define a method as private if you only want to use it internally, inside the class, and you don’t want it to be called externally, by objects, unless it’s within a public method that has access to that private method.
Protected methods: a protected method is similar to a private method, with the addition that it can be called with, or without, an explicit receiver, but that receiver is always self (it’s defining class) or an object that inherits from self (ex: is_a?(self)).
Extending upon the previous examples, I added a protected method, #greet, to our Person class, and created a new class, Me, which inherits from Person. In Ruby, public, private, and protected methods are all inherited, so the Me class can now call the #greet method defined in the Person class. But, the same rules apply: private and protected methods are for internal usage, and can only be called externally within a public method.
class Person def speak
puts "Hey, Tj!"
end def whisper_louder
whisper
end private def whisper
puts "His name's not really 'Tj'."
end protected def greet
puts "Hey, wassup!"
endendclass Me < Person
def be_nice
greet
end
endtj = Me.new
tj.be_nice # "Hey, wassup!"
tj.greet # NoMethodError
However, if the Me class didn’t inherit from Person, but we still wanted the Me class to have access to the #greet method from Person, what we could’ve done in our Person class was to define our #greet method to use self as #self.greet (see example below). This way, even without inheritance, we’re “protecting” this method to only be called explicitly with a receiver of self or any class that inherits from self:
class Person def speak
puts "Hey, Tj!"
end def whisper_louder
whisper
end private def whisper
puts "His name's not really 'Tj'."
end protected def self.greet
puts "Hey, wassup!"
endendclass Me
def be_nice
Person.greet
end
endtj = Me.new
tj.be_nice # "Hey, wassup!"
tj.greet # NoMethodError
In the Me class now, our #be_nice method can only call the #greet method by using its Person class as the explicit receiver.
Defining a protected method as self.method_name is especially useful when building a Ruby on Rails app and we want the bulk of our non-response related logic in our Models, as opposed to in our Controllers, following the “fat model, skinny controller” best practice. If you don’t know what I’m talking about right now, it’ll make sense later when you see Ruby on Rails.
In Summary, use private methods for internal usage without a receiver, and protected methods defined as self.method_name for internal usage in other classes whenever inheritance isn’t set up. Hope this helps you understand access control in Ruby a bit better. Leave any questions or feedback below.