根据数据库/表列动态设置子类的attr

时间:2016-03-18 03:59:26

标签: ruby metaprogramming subclass attr superclass

我需要从数据库中的各个表中检索列名,以便为这些表对应的类动态设置attr_accessor方法。这需要从父类完成。

class Child < Parent
  attr_accessor :id, :column_a, :etc.
end

Parent&#39; #column_names方法确实按我打算的方式工作。它返回子类指定的任何表的列名数组。但是,在从#inherited方法调用时,它不会返回任何列名。当我尝试为子类设置attr_accessor方法时,返回一个空数组。

我正在尝试动态设置这些attr_accessor方法,使用#class_eval将它们插入子类。这是对#column_names的调用发生的地方以及返回空数组的位置。

module SomeModule
  class Parent

    def self.inherited(child)
      child.class_eval do
        self.column_names.each { |att| attr_accessor att.to_sym }
      end  
    end

    #...

    def self.table_name
      "#{self.to_s.downcase}s"
    end

    def self.column_names
      sql = "#... WHERE table_name = '#{table_name}';"

      columns = []
      exec(sql).each do |col-data|
        columns << col_data["column_name"]
      end

      columns
    end

    def self.exec(sql, args=[])
      #...
    end

    #...

  end
end

我的理解是,这应该基本上创建一个从ChildChild.column_names的来电,我认为这样做。我只是不知道为什么没有填充返回数组。我已尝试移动column_names方法,但行为的唯一变化(取决于放置的位置)是子类的NoMethodError

1 个答案:

答案 0 :(得分:0)

不幸的是,inherited上的文档并不详细,但我看到问题在这里 - 它是self class_eval的价值块。

NoMethodError是因为未在子类上定义column_names方法,而self引用class_eval块内的子类,

这是一个简单的修复,在self阻止之前将class_eval设置为变量。

希望以下示例清楚,我测试并运行。请注意,与您的代码唯一的真正区别在于inherited方法。所有其他的东西都只是测试它,因为我无法按原样运行你的代码。

require 'byebug'

class ParentClass
  def self.inherited(child)
    parent_class = self # Store value of self in a variable
                        # because it will change in class_eval
                        # Then call column_names on this variable
    child.class_eval do
        parent_class.column_names.each { |attr| attr_accessor attr.to_sym }
    end
  end
  def self.column_names
    [:foo, :bar]
  end
end

class ChildClass < ParentClass
  def initialize
  end
end

child = ChildClass.new
child.foo = "bar"
puts child.foo
# => "bar"

这个问题在javascript中更常见,javascript具有更严格的隔离范围级别。换句话说,self的值更改频繁。