12

这是我的设置,然后解释了我要完成的工作。

class Layer < ActiveRecord::Base
  has_and_belongs_to_many :components
end

class Component < ActiveRecord::Base
  has_and_belongs_to_many :layers 
end

class ImageComponent < Component
  # I want this table to inherit from the Component table
  # I should be able to add image-specific fields to this table
end

class VideoComponent < Component
  # I want this table to inherit from the Component table
  # I should be able to add video-specific fields to this table
end

我想能够做什么:

layer.components << ImageComponent.create
layer.components << VideoComponent.create

在实践中,我意识到ImageComponent并且VideoComponent实际上必须继承自ActiveRecord::Base. 有什么方法可以很好地在 Rails 中实现模型子类化?

现在我的Component模型设置是polymorphic这样的ImageComponentVideoComponent每个has_one :component, as: :componentable. 这给我的代码增加了一层烦恼和丑陋:

image_component = ImageComponent.create
component = Component.create
component.componentable = image_component
layer.components << component

我想一个简单的解释方法是我想在层和组件之间实现一个 habtm 关系。我有多种类型的组件(即 ImageComponent、VideoComponent),它们各自具有相同的基本结构,但与它们相关联的字段不同。关于如何实现这一点的任何建议?我觉得我错过了一些东西,因为我的代码感觉很hackish。

4

3 回答 3

10

在 Rails 中实现这一点的“官方”方法是使用单表继承。ActiveRecord 内置了对 STI 的支持:http ://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance

如果你想使用多表继承,你必须自己实现它......

于 2012-11-13T09:21:17.320 回答
2

这里的主要问题是组件及其类型之间,而不是层和组件之间。我有一个类似的问题。将解释针对您的问题的解决方案。

将类型(图像/视频)存储为组件的资源,并为组件提供一个控制器,而不是所有类型()

让模型结构为

Component < ActiveRecord::Base
  accepts_nested_attributes_for :resource
  belongs_to :resource, :polymorphic => true, :dependent => :destroy

  def resource_attributes=(params = {})
    self.resource = spec_type.constantize.new unless self.resource
    self.resource.attributes = params.select{|k| self.resource.attribute_names.include?(k) || self.resource.class::ACCESSOR.include?(k.to_sym)}
  end

#component will be either image or video and not both

Image < ActiveRecord::Base
  has_one :component, as :resource 

Video < ActiveRecord::Base
  has_one :component, as :resource

以及单个控制器作为组件 CRUD 的 ComponentsController。由于组件接受资源(即图像/视频)的属性,因此您可以保存组件以及资源并为每个资源添加正常验证。

添加组件的基本视图可以是

= form_for(@component, :url => components_path, :method => :post) do |f|
  = fields of Component
  = f.fields_for :resource, build_resource('image') do |image|
    = fields of resource Image
  = f.fields_for :resource, build_resource('video') do |video|
    = fields of resource Video

可以使用辅助方法添加图像/视频的字段

module ComponentsHelper
  def build_resource(klass)
    klass  = "{klass.capitalize}"
    object = eval("#{klass}.new")
    if @component.resource.class.name == klass
      object = @component.resource
    end
    return object
  end
end

由于组件只能有一个相关资源(图像/视频),您需要在视图上选择资源类型(在我的情况下,它是一个下拉列表)并根据所选资源显示其字段并隐藏/删除所有其他资源字段(如果选择了图像,请使用 javascript 删除视频字段)。提交表单时,来自组件模型的方法过滤掉预期资源的所有键值对,并创建组件及其相关资源。

1)在提交表单时保持每个资源的字段名称唯一原因,隐藏的资源(不需要的资源)字段被提交,覆盖预期的资源字段。

2) 上述模型结构仅对资源 attr_accessor产生问题(在 rails 控制台上无法访问它们)。它可以解决为

ACCESSOR = ['accessor1', 'accessor2'] #needed accessors
has_one :component, :as => :resource
attr_accessor *ACCESSOR

了解如何实现具有 3 个固定类别的职位发布功能

我希望这有帮助。

于 2012-11-19T08:10:25.450 回答
1

使用 STI,您将与多个模型类共享同一个表,因此如果您希望子类模型具有唯一字段(数据库列),那么它们需要在该公用表中表示。从您示例中的评论来看,这似乎是您想要的。

但是,您可以使用一个技巧,即在表中拥有一个字符串列,每个模型都可以使用该列来存储自定义序列化数据。为了做到这一点,这些数据元素没有被索引是可以的,因为您将无法在 SQL 中轻松地搜索它们。假设您将此字段称为aux。把它放在父模型中:

  require 'ostruct'
  serialize :aux, OpenStruct

现在假设您希望在子类模型中调用字段managerexperience但其他 STI 模型都不需要此字段,您不需要根据这些属性进行搜索。所以你可以在子类模型中做到这一点:

# gets the value
def manager
  return self.aux.manager
end

# sets the value
def manager=(value)
  self.aux.manager = value
end

 # gets the value
def experience
  return self.aux.experience
end

# sets the value
def experience=(value)
  self.aux.experience = value
end

在此示例中,单表继承仍然可以正常工作,并且您还可以获得子类模型的自定义持久属性。这为您提供了在多个模型之间共享代码和数据库资源的好处,而且还允许每个模型具有独特的属性。

于 2012-11-19T15:37:56.560 回答