Ruby on rails 将Rails属性转换为属性值对

Ruby on rails 将Rails属性转换为属性值对,ruby-on-rails,model,attributes,virtual-attribute,Ruby On Rails,Model,Attributes,Virtual Attribute,我有一个媒体模型,它有一堆标准的元数据属性,并像平常一样保存在数据库中。我现在想做的是在现有属性的基础上,向这个模型添加一些可配置的元数据属性。这些属性的列表将在配置文件中定义,并在运行时加载。它们将作为一系列属性值对存储在数据库中的另一个表中,并与主模型关联 那么我现在的代码是, class Media < ActiveRecord::Base has_many :custom_metadata attr_accessible :title, :language, :copyr

我有一个媒体模型,它有一堆标准的元数据属性,并像平常一样保存在数据库中。我现在想做的是在现有属性的基础上,向这个模型添加一些可配置的元数据属性。这些属性的列表将在配置文件中定义,并在运行时加载。它们将作为一系列属性值对存储在数据库中的另一个表中,并与主模型关联

那么我现在的代码是,

class Media < ActiveRecord::Base
  has_many :custom_metadata

  attr_accessible :title, :language, :copyright, :description
end
class媒体

class CustomMetadata
我想做的是能够以与标准元数据属性相同的方式访问和更新媒体模型上的自定义元数据属性。例如,如果自定义元数据属性称为publisher和contributor,那么我希望在媒体模型中以
@Media.publisher
@Media.contributor
的形式访问它们,即使它们将位于关联
@Media.custom_metadata
中,其值类似于
[{:name=>'publisher',:value=>'Fred'},{:name=>'contributor',:value=>'Bill'}]

虚拟属性似乎是实现这一点的最佳方式,但我能找到的所有使用虚拟属性的人的例子都是,属性的名称是静态的,从运行时配置来看是已知的,而不是动态的,因此它们可以定义方法,例如
publisher
publisher=
wh然后ich将包含写入相关属性值记录的代码

我可以使用
attr\u accessor*设置在类上定义属性。自定义元数据字段
(假设
设置。自定义元数据字段
返回
[:publisher,:contributor]
),还可以使用类似的
attr\u accessible
技术进行批量分配

我一直关注的部分是如何在从记录加载数据时从关联中填充虚拟属性,然后反过来,如何在保存记录之前将虚拟属性中的数据传递回关联中

我目前认为这两种方法都是使用
method\u missing
attribute\u missing
,或者可能通过
initialize
save
回调之前的
实现的。在这两种情况下,鉴于我的模型混合了普通属性和虚拟属性,我不确定如何定义它


有什么建议吗?

使用回调听起来很合理。 您使用的是什么数据库?如果是PostgreSQL,也许您应该看看HStore扩展()
它的性能会更好,而且有一些优点使它更易于使用。

在查看回调之后,我又发现了初始化后的
回调,这比我最初计划的使用
初始化
方法要好得多

最后,这是媒体模型的最终代码,我没有改变CustomMetadata模型中我在问题中定义的任何内容

class Media < ActiveRecord::Base
  has_many :custom_metadata

  attr_accessor *Settings.custom_metadata_fields

  attr_accessible :title, :language, :copyright, :description
  attr_accessible *Settings.custom_metadata_fields

  validates_presence_of *Settings.required_custom_fields

  before_save :save_custom_metadata
  after_initialize :load_custom_metadata

  def load_custom_metadata
    MediaMetadata.custom_all_fields.each do |field|
      custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
      send("#{field}=", custom_record.value)
    end
  end

  def save_custom_metadata
    MediaMetadata.custom_all_fields.each do |field|
      custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
      custom_record.value = send(field)
      if custom_record.value.blank?
        custom_record.destroy
      else
        custom_record.save
      end
    end
  end
end
class媒体field.to\u s)。第一个\u或\u初始化()
发送(“#{field}=”,custom_record.value)
结束
结束
def保存自定义元数据
MediaMetadata.custom_所有_字段。每个do|字段|
自定义\u记录=自定义\u元数据。其中(:name=>field.to\u s)。第一个\u或\u初始化()
custom_record.value=发送(字段)
如果自定义_record.value.blank?
自定义记录
其他的
自定义记录。保存
结束
结束
结束
结束

此解决方案有几个好处。首先,它不会影响媒体模型上的任何常规属性。其次,只有具有实际值的自定义元数据存储在自定义元数据表中。如果值为空,则记录将被完全删除。最后,我可以对模型属性使用标准验证,如所示我所需的自定义元数据属性。

感谢您的建议。不幸的是,我们在MySQL上作为数据库进行了标准化,因此改为PostgreSQL并不是一个真正的选项。考虑到普通属性和虚拟属性的混合,您将如何编写回调?回调建议很好。我发布了关于如何实现的代码下面是答案中的回调。还有一个想法-您打算对CustomMetadata记录执行SQL查询吗?如果不是,也许您可以使用#serialize()将属性哈希序列化到单个列中。关键是,使用一对多关系可能会导致性能问题。当您使用媒体#查找-查看后查找挂钩查询多个记录时,不会触发使用后查找初始化。
class Media < ActiveRecord::Base
  has_many :custom_metadata

  attr_accessor *Settings.custom_metadata_fields

  attr_accessible :title, :language, :copyright, :description
  attr_accessible *Settings.custom_metadata_fields

  validates_presence_of *Settings.required_custom_fields

  before_save :save_custom_metadata
  after_initialize :load_custom_metadata

  def load_custom_metadata
    MediaMetadata.custom_all_fields.each do |field|
      custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
      send("#{field}=", custom_record.value)
    end
  end

  def save_custom_metadata
    MediaMetadata.custom_all_fields.each do |field|
      custom_record = custom_metadata.where(:name => field.to_s).first_or_initialize()
      custom_record.value = send(field)
      if custom_record.value.blank?
        custom_record.destroy
      else
        custom_record.save
      end
    end
  end
end