Agreed with @benorenstein on making frequent and aggressive use of private. I donāt find occasion to use protected. Iām sure a computer science purist could point out places where I should be using protected methods, but I seem to be served fine between just public and private.
Basically every method should start out as private, and then be promoted to public as needed. As for protected, itās significantly less useful in Ruby than other languages, because private isnāt actually āprivateā.
In most languages, private would restrict access outside of where itās defined. The notable difference between this and what Ruby does is subclasses. Usually protected means itās not part of the public API of an object, but is part of the API available to subclasses.
In Ruby, private means that the method cannot be called with a subject (ironically including self), so you must use the implicit self to call the method. Protected allows you to call the method with a target, but only from within the same class. The only time Iāve ever found that this is useful is for methods used in comparison. So for example:
class Foo
def initialize(items)
@items = items
end
private
attr_reader :items
end
However, if I wanted to add an == method, itād probably look like this:
def ==(other)
items == other.items
end
However, items now needs to be protected for me to call it from inside of ==
Sorry for the delayed response. As a rule of thumb, I never access ivars outside of constructors, getters, and setters. By following this rule, it becomes trivial to move a local variable to an instance variable, or move a property from one object to another (adding a delegator), without having to change unrelated code. For example, if my code looked like this:
class Foo
def initialize(items)
@items = items
end
def fooable_items
@items.filter(&:fooable?)
end
end
and I wanted to refactor it to this:
class Foo
def initialize(bar)
@bar = bar
end
def fooable_items
bar.items.filter(&:fooable?)
end
end
class Bar
def initialize(items)
@items = items
end
attr_reader :items
end
My fooable_items method had to change as a result of me changing where @items lives. However, if Iād written it like this:
class Foo
def initialize(items)
@items = items
end
def fooable_items
items.select(&:fooable?)
end
private
attr_reader :items
end
then once I move items to Bar, I only have to change the attr_reader, instead of all of my code.
class Foo
extend Forwardable
def initialize(bar)
@bar = bar
end
def fooable_items
items.select(&:fooable?)
end
private
attr_reader :bar
delegate items: :bar
end
Also, the meaning of private in a module is the same as in anywhere else. It canāt be called on a target, so it must be called with the implicit form of self. That means that private methods in a module will still be visible to classes that mix in the module.