When defining accessors in Ruby, there can be a tension between brevity (which we all love) and best practice.
For example, if I wanted to expose a value on an instance but prohibit any external objects from updating it, I could do the following:
class Pancake
attr_reader :has_sauce
def initialize(toppings)
sauces = [:maple, :butterscotch]
@has_sauce = toppings.size != (toppings - sauces).size
...
But suddenly I'm using a raw instance variable, which makes me twitch. I mean, if I needed to process has_sauce before setting at a future date, I'd potentially need to do a lot more refactoring than just overriding the accessor. And come on, raw instance variables? Blech.
I could just ignore the issue and use attr_accessor
. I mean, anyone can set the attribute if they really want to; this is, after all, Ruby. But then I lose the idea of data encapsulation, the object's interface is less well defined and the system is potentially that much more chaotic.
Another solution would be to define a pair of accessors under different access modifiers:
class Pancake
attr_reader :has_sauce
private
attr_writer :has_sauce
public
def initialize(toppings)
sauces = [:maple, :butterscotch]
self.has_sauce = toppings.size != (toppings - sauces).size
end
end
Which gets the job done, but that's a chunk of boilerplate for a simple accessor and quite frankly: ew.
So is there a better, more Ruby way?
To fix these growing lines of code, Ruby provides us with a quick way to generate the getter and setter methods without explicitly writing them as we did in the above examples. These methods are known as accessor methods. Their purpose is the same as that of a getter or setter.
A Ruby method can be:By default ALL your methods are public . Anyone can use them! But you can change this, by making a method private or protected .
Private: – When a method is declared private in Ruby, it means this method can never be called with an explicit receiver. Any time we're able to call a private method with an implicit receiver. We can call a private method from within a class it is declared in as well as all subclasses of this class e.g. class Foo.
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.
private
can take a symbol arg, so...
class Pancake
attr_accessor :has_sauce
private :has_sauce=
end
or
class Pancake
attr_reader :has_sauce
attr_writer :has_sauce; private :has_sauce=
end
etc...
But what's the matter with "raw" instance variables? They are internal to your instance; the only code that will call them by name is code inside pancake.rb
which is all yours. The fact that they start with @
, which I assume made you say "blech", is what makes them private. Think of @
as shorthand for private
if you like.
As for processing, I think your instincts are good: do the processing in the constructor if you can, or in a custom accessor if you must.
You can put attr_reader
in a private
scope, like this:
class School
def initialize(students)
@students = students
end
def size
students.size
end
private
attr_reader :students
end
School.new([1, 2, 3]).students
This will raise an error as expected:
private method `students' called for #<School:0x00007fcc56932d60 @students=[1, 2, 3]> (NoMethodError)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With