你如何在Rails中动态调用attr_accessible?

时间:2009-05-02 10:10:47

标签: ruby-on-rails ruby metaprogramming

我有一个相当独特的类,允许其子类声明虚拟字段。子进程可以通过调用父类的方法声明存储为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_acessible以允许在子类中进行批量分配的经验?提前谢谢!

为了清晰起见,编辑了这篇文章!

2 个答案:

答案 0 :(得分:1)

以下是我如何实现创建存取方法并将它们设置为attr_accessibles的元编程部分。

我正在使用YAML intead of 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

答案 1 :(得分: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