在Ruby中列出attr_accessor的最快/一行代码方法是什么?

38

有没有一种最短的单行代码方式来列出使用attr_accessor定义的所有方法?我想要这样做,如果我有一个类MyBaseClass,任何扩展它的类都可以获取子类中的attr_accessor。类似于这样:

class MyBaseClass < Hash
  def attributes
    # ??
  end
end

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

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

只展示attr_readerattr_writer的定义怎么样?

6个回答

57
将属性提取到数组中,将它们分配给 常量,然后将它们 扩展attr_accessor 中。
class SubClass < MyBaseClass
  ATTRS = [:id, :title, :body]
  attr_accessor(*ATTRS)
end

现在您可以通过常量访问它们:
puts SubClass.ATTRS #=> [:id, :title, :body]

3
不是应该写成 SubClass::ATTRS 吗?你目前的语法似乎是将 ATTRS 消息发送给了一个 SubClass 类型的实例。 - eggie5

43

没有办法(一行代码或其他方式)仅列出由attr_accessor定义的所有方法,而不定义自己的attr_accessor。

这里提供了一种解决方案,即在MyBaseClass中覆盖attr_accessor以记住使用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]

5
第五行有一点小问题,你不需要调用 super(*vars),因为你传递给它的参数与该方法接收到的参数相同;你只需调用没有任何参数或括号的 super,它会自动传递 *vars - Mark Rushakoff
3
@MarkRushakoff: 我这样做是为了让情况更加明显。很多人认为supersuper()是同一个东西(老实说,如果是我,我也不确定它们是否应该是同一个东西)。 - sepp2k
4
默认情况下传递参数(如果有的话还包括块)的基本原理是,通常接口不会发生改变,因此这正是你想要的。 我想说的是,拥抱Ruby吧,让其他人也能拥抱它,即使这意味着他们必须学习super不等同于super()... - Marc-André Lafortune
有没有一种方法可以不使用继承(子类化),而是使用一个模块来覆盖attr_accessor,然后将该模块用作其他类的mixin?我尝试将MyBaseClass中的内容转换为Module AttributeAccessors。然后在我想要使用它的类中,我说extend AttributeAccessors,但是当我尝试访问类实例中的属性时,它会失败并显示:“NoMethodError:undefined method 'attributes' for Class:Class” - Robert J Berger

7
这里提供一种使用mixin而非继承的替代方案:
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]

有没有一种使用mixins来实现MyClass.attr_accessors并获得相同结果的方法?@sepp2k提出的继承方法可以同时实现。我可以考虑使用extend和include,但那似乎是冗余的。有更好的方法吗? - EricC

2

类定义

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

作为类方法调用

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

作为实例方法调用

a = SubClass.new
b = MyBaseClass.new

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

两者输出结果相同

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

如何区分writer、reader和accessor?

attr_accessor同时包含attr_writer和attr_reader

attr_reader方法名后面不会输出等于号(=)

attr_writer方法名后面会输出等于号(=)

您可以使用正则表达式过滤该输出。


2

在跟进Christian的回答时,我们可以使用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

1

可以使用等号匹配逻辑进行过滤:

methods = (SubClass.instance_methods - SubClass.superclass.instance_methods).map(&:to_s)
methods.select { |method| !method.end_with?('=') && methods.include?(method + '=') }
#> ["id", "title", "body"]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接