New Alexandria
New Alexandria

Reputation: 7324

Can I get a list of the symbols passed to attr_accessor in a class when inspecting?

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

Answers (3)

user513951
user513951

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

J&#246;rg W Mittag
J&#246;rg W Mittag

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

Todd A. Jacobs
Todd A. Jacobs

Reputation: 84403

Grep an Instance for Setter Methods

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

Related Questions