It is possible (but inadvisable) to do exactly what you are asking.
There are two different elements of the desired behavior. The first is storing x
in a read-only value, and the second is protecting the getter from being altered in subclasses.
Read-only value
It is possible in Ruby to store read-only values at initialization time. To do this, we use the closure behavior of Ruby blocks.
class Foo
def initialize (x)
define_singleton_method(:x) { x }
end
end
The initial value of x
is now locked up inside the block we used to define the getter #x
and can never be accessed except by calling foo.x
, and it can never be altered.
foo = Foo.new(2)
foo.x # => 2
foo.instance_variable_get(:@x) # => nil
Note that it is not stored as the instance variable @x
, yet it is still available via the getter we created using define_singleton_method
.
Protecting the getter
In Ruby, almost any method of any class can be overwritten at runtime. There is a way to prevent this using the method_added
hook.
class Foo
def self.method_added (name)
raise(NameError, "cannot change x getter") if name == :x
end
end
class Bar < Foo
def x
20
end
end
# => NameError: cannot change x getter
This is a very heavy-handed method of protecting the getter.
It requires that we add each protected getter to the method_added
hook individually, and even then, you will need to add another level of method_added
protection to Foo
and its subclasses to prevent a coder from overwriting the method_added
method itself.
Better to come to terms with the fact that code replacement at runtime is a fact of life when using Ruby.