Reputation: 7324
Assuming I have defined a class with accessors defined using attr_accessor
:
class A
attr_accessor :alpha, :beta, :gamma
def initialize
self.alpha = 1
end
end
Is there a built-in method that gives the list of method names passed to an attr_accessor
call? Or do I have to define a constant with the symbols, and pass it to attr_accessor
?
Upvotes: 1
Views: 369
Reputation: 13690
There's no built-in method. Your solution of storing the method names at creation time will work, as long as you know in advance and can control what the method names are.
In my answer to a different but similar question, I showed how to get the names of the methods dynamically, after the fact, using TracePoint
. I've updated it below to include :attr_reader
and :attr_writer
.
module MethodTracer
TracePoint.trace(:c_call) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
t.self.extend(MethodTracer)
methods = t.self::Methods ||= []
MethodTracer.send(:define_method, :method_added) {|m| methods << m }
end
end
TracePoint.trace(:c_return) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
MethodTracer.send(:remove_method, :method_added)
end
end
end
class Foo
attr_accessor :a
attr_reader :b
attr_writer :c
def foo; end
end
Foo::Methods # => [:a, :a=, :b, :c=]
I've stored the method names in the Methods
constant, but obviously you can store them wherever is most convenient for you.
Defining/removing method_added
on MethodTracer
ensures that you don't clobber any Foo.method_added
you've defined yourself. This methodology, however, does require that if you define Foo.method_added
before your calls to attr_*
, you will need to call super
inside it. Otherwise you will skip the temporary method_added
defined by MethodTracer
.
Upvotes: 2
Reputation: 369556
No, that's not possible. Methods generated by attr_accessor
, attr_reader
and attr_writer
are indistinguishable from ones written by hand. In fact, they must be indistinguishable from ones written by hand!
Say, you have a simple attr_accessor
, but you later want to refactor it to do something more intelligent (e.g. caching). This is a purely internal change, a client must not be able to observe the difference, otherwise it would be a breach of encapsulation!
If you simply want a list of setters, that's easy enough: setters are methods whose name ends with an =
sign:
A.public_instance_methods(false).grep(/=$/)
# => [:alpha=, :beta=, :gamma=]
For getters, it's trickier: any method that doesn't take an argument could be a getter, but it could also be a side-effecting method (e.g. Array#clear
):
A.public_instance_methods(false).select {|m|
A.public_instance_method(m).arity.zero?
}
# => [:alpha, :beta, :gamma]
Upvotes: 2
Reputation: 84403
One way to do this would be to grep an instance of the class for setters. For example:
A.new.methods.grep(/\p{alnum}+=\z/)
#=> [:alpha=, :beta=, :gamma=]
Upvotes: 2