Reputation: 11892
In Ruby, you can create getter/setter methods by adding the following to the class definition.
attr_reader :key1, :key2, :key4
attr_writer :key1, :key2, :key3
or equivalently
attr_accessor :key1, :key2, :key3
For example
class Foo
attr_reader :blah, :bar, :foo, :rah, :jar
def initialize a, b, c
@blah = calculate_blah(a,b)
@bar = calculate_bar(a)
@foo = calculate_foo(b,c)
@rah = calculate_rah(a,b,c)
@jar = calculate_jar(a,c)
end
# etc etc
end
Let's say there are many instance variables, and I want getter methods on all of them.
Is it possible to declare attr_reader
on all the instance variables without listing them all out?
The benefit is so that you don't have to maintain two lists of variables (which are identical) one in the initialize
method, another with attr_reader
. So later if you want to add another instance variable, you only have to set it up in the initialize
method, without needing to add it to the attr_reader
list too.
If this isn't readily available with attr_reader
, perhaps some metaprogramming could be summoned to help?
The main objective here is to understand if this is possible in Ruby. Using metaprogramming often incurs cost of performance and obscurity. But that's beyond the scope of this question.
I'm more interested in knowing if it's possible to do something, not whether that's the right thing to do.
Upvotes: 3
Views: 3097
Reputation: 110725
Suppose
class A
def initialize
@b=0
@c=1
end
end
Then
a = A.new
a.instance_variables.each { |iv| self.class.send(:attr_reader, iv.to_s[1..-1].to_sym) }
a.b #=> 0
a.c #=> 1
If all the instance variables are defined in initialize
you could write
class A
def initialize
@b=0
@c=1
instance_variables.each do |iv|
self.class.send(:attr_reader, iv.to_s[1..-1].to_sym)
end
end
end
a = A.new
a.b #=> 0
a.c #=> 1
Upvotes: 4
Reputation: 11892
Here is a solution using singleton method. Note create_getters
is a private method, so the outside world is not aware of the use of metaprogramming (implementation detail).
class Foo
def initialize a, b
@foo = a + b
@bar = a - b
@jar = a + b + 1000
create_getters
end
private
def create_getters
instance_variables.each do |v|
define_singleton_method(v.to_s.tr('@','')) do
instance_variable_get(v)
end
end
end
end
running this in irb:
2.2.1 :082 > x=Foo.new 100, 99
=> #<Foo:0x007fb4f3c31ce8 @foo=199, @bar=1, @jar=1199>
2.2.1 :083 > x.foo
=> 199
2.2.1 :084 > x.bar
=> 1
2.2.1 :085 > x.jar
=> 1199
Be warned: by doing it this way, object instantiation and getter method invocation are both SLOWER.
Upvotes: 2
Reputation: 21
Instantiate the class once to evaluate the instance variables and re-define the class
class Hey
def initialize
@foo, @bar, @shabaz = [1,2,3]
end
end
Hey.new.instance_variables.tap do |inst_vars|
Hey.class_eval do
attr_reader *inst_vars.map { |var| var.to_s.sub(/^@/, '').to_sym }
end
end
p Hey.new.foo
p Hey.new.bar
p Hey.new.shabaz
Upvotes: 0
Reputation: 897
Even though, as other have said, it is not a good idea to actually use similar code, it's possible to write the meta-function you are asking for. This is one of many possible different solution. The idea is that you augment every object you are using once in their life cycle, when the instance variable are already defined on the object. This could happen during, or after, the initialization.
class Foo
def initialize
@a = @b = @c = 33
# you could call define_attr_readers here
end
def define_attr_readers
# get the eigenclass of the current object
klass = class << self; self; end
symbols = instance_variables.map { |s|
# remove the @ at the start of the symbol
s.to_s[1..-1].to_sym
}
# augment the eigenclass
klass.class_eval do
symbols.each do |s|
attr_reader s
end
end
end
end
f = Foo.new
f.define_attr_readers
p f.a, f.b, f.c
Upvotes: 1