Fastest/One-liner way to list attr_accessors in Ruby?
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 :i开发者_运维百科d, :title, :body
end
puts SubClass.new.attributes.inspect #=> [id, title, body]
What about to display just the attr_reader and attr_writer definitions?
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]
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]
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]
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
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.
It could be filtered with the equal sign matching logic:
methods = (SubClass.instance_methods - SubClass.superclass.instance_methods).map(&:to_s)
methods.select { |method| !method.end_with?('=') && methods.include?(method + '=') }
#> ["id", "title", "body"]
 
         加载中,请稍侯......
 加载中,请稍侯......
      
精彩评论