Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby private and public accessors

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?

like image 539
A Fader Darkly Avatar asked Aug 29 '14 15:08

A Fader Darkly


People also ask

What are accessors in Ruby?

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.

Does Ruby have private methods?

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 .

What does private mean in Ruby?

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.

What are the ways to access private methods in Ruby?

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.


2 Answers

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.

like image 73
AlexChaffee Avatar answered Nov 03 '22 20:11

AlexChaffee


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)
like image 35
Dorian Avatar answered Nov 03 '22 20:11

Dorian