Как вы вызываете attr_accessible динамически в Rails? - PullRequest
1 голос
/ 02 мая 2009

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

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

Мне удалось заставить его работать через неприятную работу вокруг. Метод create_xml_field сохраняет имена полей в переменной Class (см. Ниже). Метод 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, чтобы разрешить массовое назначение в дочернем классе? Заранее спасибо!

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

Ответы [ 2 ]

1 голос
/ 02 мая 2009

Вот как я реализую часть метапрограммирования, которая создает методы доступа и устанавливает их как attr_accessibles.

Я использую YAML intead 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
0 голосов
/ 09 мая 2009

@ 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
...