1

我有一个相当独特的类,它允许其子类声明虚拟字段。子类可以通过调用父类的方法来声明存储为 XML 的虚拟字段,如下所示:

class Child1 < Parent
  create_xml_field ["readings", "usage"]
end

我设法通过一个讨厌的工作让它工作。create_xml_field方法将字段名称存储在 Class 变量中(见下文)。从after_initialize方法内部调用init_xml_fields方法。

class Parent < ActiveRecord::Base

  def self.create_xml_field(fields)
    @@xml_fields[self.name] = fields
  end

  def init_xml_fields(xml_fields)
    xml_fields.each do |f|
      f=f.to_sym
      self.class_eval do
        define_method(f) { ... } # define getter
        define_method(f) { ... } # define setter
        attr_accessible(f)       # add to mass assign OK list, does not seem to work!
      end
    end
  end

  protected
    def after_initialize
      init_xml_fields
    end
end

够恶心吧?我并不自豪,但我很难让它发挥作用。此外,解决方法不适用于表单参数的大规模分配。

有没有人有经验动态调用 attr_accessible 以允许在子类中进行大规模分配?先感谢您!

为了清楚起见,对这篇文章进行了编辑!

4

2 回答 2

1

下面是我如何实现创建访问器方法并将它们设置为 attr_accessibles 的元编程部分。

我使用 YAML 而不是 XML,只是作为个人圣战。我什至继续实施了不需要的序列化部分,只是为了将您推向 YAML。

require 'yaml'
require 'rubygems'
require 'active_support'
require 'active_record'

module Yamlable
  def self.included m
    m.extend ClassMethods
  end

  module ClassMethods
    def add_yaml_fields *args
      write_inheritable_array(:yaml_fields, args)
      attr_accessor(*args)
      attr_accessible(*args)
      before_save :serialize_yaml_fields
    end
  end

  def serialize_yaml_fields
    self.yamlable_column = read_inheritable_attribute(:yaml_fields)\
      .inject({}) { |h, a| h[a] = send(a); h }.to_yaml
  end

  def initialize(*args)
    super
    YAML::load(yamlable_column).each { |k, v| send("#{k}=", v) }
  end
end

class ParentModel < ActiveRecord::Base
  include Yamlable
  add_yaml_fields :foo, :bar
end

class ChildModel < ParentModel
end

# look, they're there:
y ChildModel.read_inheritable_attribute(:yaml_fields)

现在,如果您想知道为什么您的特定代码不起作用,您将不得不发布更多内容。


我可能应该对类可继承属性进行一些扩展。它们有点像类变量,有点像类实例变量。

如果你在一个类中定义了一个可继承的属性,它的所有子类都会共享它。但是,如果您在子类中更新所述属性,则该子类会复制原始属性并对其进行更新,因此更新是专有的,不会影响继承链中它周围的其他类。

使用普通write_inheritable_attribute方法,在子类上设置它只会覆盖父类的值。使用可继承的数组和哈希,write_inheritable_*将连接/合并到父类的值。


所以,在实践中,我的add_yaml_fields作品是这样的:

class Parent
  add_yaml_attributes :foo

class Child1 < Parent
  add_yaml_attributes :bar

class Child2 < Parent
  add_yaml_attributes :baz

这样,每个类的 yaml 属性将是:

  • 家长: foo
  • Child1:foo,bar
  • Child2:foo,baz
于 2009-05-02T11:13:46.803 回答
0

@kch 是正确的,但是我使用initialize(*args)发现了一个问题。ActiveRecord 并不总是使用new()实例化模型对象,因此并不总是调用initialize()方法。

而是使用after_initialize(*args),如下所示。

def self.included m
    m.extend ClassMethods
  end

  module ClassMethods
    def add_yaml_fields *args
      write_inheritable_array(:yaml_fields, args)
      attr_accessor(*args)
      attr_accessible(*args)
      before_save :serialize_yaml_fields
    end
  end

  def serialize_yaml_fields
    self.yamlable_column = read_inheritable_attribute(:yaml_fields)\
      .inject({}) { |h, a| h[a] = send(a); h }.to_yaml
  end

  def after_initialize(*args)
    super
    YAML::load(yamlable_column).each { |k, v| send("#{k}=", v) }
  end
end

class ParentModel < ActiveRecord::Base
  include Yamlable
  add_yaml_fields :foo, :bar
end

class ChildModel < ParentModel
end
于 2009-05-09T12:50:38.457 回答