2009-05-02 5 views
1

У меня есть довольно уникальный класс, который позволяет его дочерним классам объявлять виртуальные поля. Ребенок может объявить виртуальные поля хранятся в виде XML с помощью вызова метода родительского класса, как это:Как вы называете attr_accessible динамически в Rails?

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

мне удалось получить его работу через неприятную работу вокруг. Метод create_xml_field сохраняет имена полей в переменной класса (см. Ниже). Метод init_xml_fields вызывается из метода after_initialize.

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 динамически, чтобы разрешить массовое присвоение в дочернем классе? Заранее спасибо!

Это сообщение было отредактировано для четкости!

+0

Это все очень запутанно, но мне кажется, что вы вызываете метод экземпляра для изменения класса, который создавал бы eigenclass для этого конкретного экземпляра. Это поможет, если вы разместите минимальный пример с вашими фактическими классами. То, что вы пытаетесь сделать, очень просто, но вы, кажется, приближаетесь к нему с немного неправильного угла. – kch

+0

Согласен, это сбивает с толку. Извините, я не мог объяснить это лучше! Спасибо за ваш ответ, я отправлю еще несколько кода, чтобы лучше описать сценарий. – pchap10k

+0

Хорошо, я уже туша по вашему примеру. Понятно, что у меня были круглые колышки в квадратных отверстиях. Принимая ваш пример в качестве отправной точки, как я могу объявить объявление ** add_yaml_fields: foo,: bar ** в дочернем классе вместо родительского класса? Кроме того, у моего родителя есть несколько дочерних элементов, так что заявление ** write_inheritable_array (: yaml_fields, ...) ** вызывает проблемы между Child1, Child2 и т. Д. ...? Спасибо заранее, и да YAML прекрасен. – pchap10k

ответ

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_* будет concat/merge к значениям родительского класса.


Таким образом, на практике, мои 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
+0

Кажется, что это на правильном пути, но я не уверен, как интегрировать это в мой родительский класс, а ваш пример вызывает «add_yaml_fields» от родителя, а не от дочернего. Спасибо за любые дополнительные советы. – pchap10k

+0

модель Yamlable может быть включена в любой класс, и она будет предоставлять add_yaml_fields для нее и всех ее потомков. Итак, чтобы вызвать его в ChildModel, просто назовите его там, а не в родительском. Никаких трюков не требуется. – kch

+0

Большое спасибо за попытку снова! Я получил его работу после смены «yamlable_column» на «content», который специфичен для моей таблицы DB. Хорошая работа, я дал вам правильный ответ (и было так много соревнований!) Cheers mate. – pchap10k

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 
Смежные вопросы