Question

Considering this simple code :

class Yeah
  attr_reader :foo
  attr_reader :fool
  attr_reader :feel
  def initialize(foo: "test", fool: {}, feel: [])
    @foo = foo
    @fool = fool
  end
end

test = Yeah::new
pp test
test.fool[:one] = 10
pp test

Output :

#<Yeah:0x000008019a84a0 @foo="test", @fool={}>
#<Yeah:0x000008019a84a0 @foo="test", @fool={:one=>10}>

My Question is, there is a "simple","clean" way, to do read accesors to real readonly Array,Hash attributs or I need to inherit Array or Hash with a lot of locking hard to write, (undef,alias) or using Proxy, delegate or others patterns like this ?

Was it helpful?

Solution

You can think of something like below :

class Yeah
  def self.reader_meth
    %i(foo fool feel).each do |m|
      define_method(m){instance_variable_get("@#{m}").dup.freeze}
    end
  end
  def initialize(foo: "test", fool: {}, feel: [])
    @foo = foo
    @fool = fool
    @feel =feel
  end
  reader_meth
end

test = Yeah.new
test # => #<Yeah:0x8975498 @foo="test", @fool={}, @feel=[]>
test.fool[:one] = 10 # can't modify frozen Hash (RuntimeError)
test # => #<Yeah:0x8975498 @foo="test", @fool={}, @feel=[]>

OTHER TIPS

Because i want to generalize this solution and to prevent "evil" evals :

i finally, from the Arup solution arrived to this :

class Module
  def attr_readonly *syms
    syms.each do |method|
      define_method(method){
        return self.instance_variable_get("@#{method.to_s}").dup.freeze 
      }
    end
  end
end

class Yeah

  attr_reader :foo
  attr_readonly :fool
  attr_reader :feel
  def initialize(foo: "test", fool: {}, feel: [])
    @foo = foo
    @fool = fool
    @feel = feel
  end

end

What about Object#freeze:

class Yeah
  def fool
    @fool.freeze
  end 
  def initialize(fool={})
    @fool = fool
  end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top