rubyaccess-modifiersaccessorattr-accessor

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?


Solution

  • attr_reader etc are just methods - there's no reason you can define variants for your own use (and I do share your sentiment) For example:

    class << Object
      def private_accessor(*names)
        names.each do |name|
          attr_accessor name
          private "#{name}="
        end
      end
    end
    

    Then use private_accessor as you would attr_accessor (I think you need a better name than private_accessor though)