Fastest/One-liner way to list attr_accessors in Ruby?

24,880

Solution 1

There is no way (one-liner or otherwise) to list all methods defined by attr_accessor and only methods defined by attr_accessor without defining your own attr_accessor.

Here's a solution that overrides attr_accessor in MyBaseClass to remember which methods have been created using attr_accessor:

class MyBaseClass
  def self.attr_accessor(*vars)
    @attributes ||= []
    @attributes.concat vars
    super(*vars)
  end

  def self.attributes
    @attributes
  end

  def attributes
    self.class.attributes
  end
end

class SubClass < MyBaseClass
  attr_accessor :id, :title, :body
end

SubClass.new.attributes.inspect #=> [:id, :title, :body]

Solution 2

Extract the attributes in to an array, assign them to a constant, then splat them in to attr_accessor.

class SubClass < MyBaseClass
  ATTRS = [:id, :title, :body]
  attr_accessor(*ATTRS)
end

Now you can access them via the constant:

puts SubClass.ATTRS #=> [:id, :title, :body]

Solution 3

Heres an alternative using a mixin rather than inheritance:

module TrackAttributes
  def attr_readers
    self.class.instance_variable_get('@attr_readers')
  end

  def attr_writers
    self.class.instance_variable_get('@attr_writers')
  end

  def attr_accessors
    self.class.instance_variable_get('@attr_accessors')
  end

  def self.included(klass)
    klass.send :define_singleton_method, :attr_reader, ->(*params) do
      @attr_readers ||= []
      @attr_readers.concat params
      super(*params)
    end

    klass.send :define_singleton_method, :attr_writer, ->(*params) do
      @attr_writers ||= []
      @attr_writers.concat params
      super(*params)
    end

    klass.send :define_singleton_method, :attr_accessor, ->(*params) do
      @attr_accessors ||= []
      @attr_accessors.concat params
      super(*params)
    end
  end
end

class MyClass
  include TrackAttributes

  attr_accessor :id, :title, :body
end

MyClass.new.attr_accessors #=> [:id, :title, :body]

Solution 4

Following up on Christian's response, but modifying to use ActiveSupport::Concern...

module TrackAttributes
  extend ActiveSupport::Concern

  included do
    define_singleton_method(:attr_reader) do |*params|
      @attr_readers ||= []
      @attr_readers.concat params
      super(*params)
    end

    define_singleton_method(:attr_writer) do |*params|
      @attr_writers ||= []
      @attr_writers.concat params
      super(*params)
    end

    define_singleton_method(:attr_accessor) do |*params|
      @attr_accessors ||= []
      @attr_accessors.concat params
      super(*params)
    end
  end

  def attr_readers
    self.class.instance_variable_get('@attr_readers')
  end

  def attr_writers
    self.class.instance_variable_get('@attr_writers')
  end

  def attr_accessors
    self.class.instance_variable_get('@attr_accessors')
  end

end

Solution 5

Class definitions

class MyBaseClass
  attr_writer :an_attr_writer
  attr_reader :an_attr_reader

  def instance_m
  end

  def self.class_m
  end
end

class SubClass < MyBaseClass
  attr_accessor :id

  def sub_instance_m
  end

  def self.class_sub_m
  end
end

Call as class methods

p SubClass.instance_methods - Object.methods    
p MyBaseClass.instance_methods - Object.methods

Call as instance methods

a = SubClass.new
b = MyBaseClass.new

p a.methods - Object.methods
p b.methods - Object.methods

Both will output the same

#=> [:id, :sub_instance_m, :id=, :an_attr_reader, :instance_m, :an_attr_writer=]
#=> [:an_attr_reader, :instance_m, :an_attr_writer=]

How to tell which are writer reader and accessor?

attr_accessor is both attr_writer and attr_reader

attr_reader outputs no = after the method name

attr_writer outputs an = after the method name

You can always use a regular expression to filter that output.

Share:
24,880
Lance
Author by

Lance

I read JavaScript and am attempting to emit Assembly from it.

Updated on December 05, 2020

Comments

  • Lance
    Lance over 3 years

    What's the shortest, one-liner way to list all methods defined with attr_accessor? I would like to make it so, if I have a class MyBaseClass, anything that extends that, I can get the attr_accessor's defined in the subclasses. Something like this:

    class MyBaseClass < Hash
      def attributes
        # ??
      end
    end
    
    class SubClass < MyBaseClass
      attr_accessor :id, :title, :body
    end
    
    puts SubClass.new.attributes.inspect #=> [id, title, body]
    

    What about to display just the attr_reader and attr_writer definitions?