Search code examples
rubyoopconstructorencapsulationnomethoderror

Enabling changing value of an class instance attribute marked as 'attr_reader'


I have the following code:

class A
   attr_reader :x, :y

   private_class_method :new

   def self.with_data
     a = new
     a.x = 2
     a.y = 'sid'
     a
   end
 end

The intent is to restrict changing values of x and y variables once the class is initialized through the factory method with_data. However, I want this to be allowed when the object is initialized from within the class, as evident from the code above.

But I am getting the following error when I invoke obj = A.with_data:

NoMethodError: undefined method `x='

Should't this be allowed from inside class? Do I need to define attr_writer for this? That would jeopardize encapsulation.

Also, I don't want to define a private setter method for each attribute in the class, as it might run into upto 30 instance level variables. Does ruby provide any feature to get around this?

Versions: Ruby 1.9.3


Solution

  • So what you need in your case is Object#instance_variable_set:

    class A
      attr_reader :x, :y
    
      private_class_method :new
    
      def self.with_data
        a = new
        a.instance_variable_set(:@x, 2)
        a.instance_variable_set(:@y, 'sid')
        a
      end
    end
    

    Usage:

    a = A.with_data
    #=> #<A:0x007ff37c979d30 @x=2, @y="sid">
    a.x
    #=> 2
    a.x = 3
    #=> NoMethodError: undefined method `x=' for #<A:0x007ff37c979d30 @x=2, @y="sid">