rails model subclassing - >多表继承或多态

时间:2012-11-08 20:11:01

标签: ruby-on-rails ruby-on-rails-3

这是我的设置,然后解释我想要完成的任务。

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

在实践中,我意识到ImageComponentVideoComponent实际上必须从ActiveRecord::Base继承。有没有办法在Rails中很好地实现模型子类化?

现在,我的Component模型设置为polymorphicImageComponentVideoComponent每个has_one :component, as: :componentable。这给我的代码增加了一层烦恼和丑陋:

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

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

3 个答案:

答案 0 :(得分:10)

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

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

答案 1 :(得分:2)

这里的主要问题是组件及其类型,而不是层和组件。我有类似的问题。将解释特定于你的问题的解决方案。

将类型(图像/视频)存储为Component的资源,并具有Component的控制器而不是所有类型()

让模型结构为

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。由于Component接受资源的属性(即图像/视频),因此您可以保存组件和资源,并为每个资源添加正常的验证。

添加Component的基本视图可以是

= 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

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

另外

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

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

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

请参阅how to implement jobpost functionality that has 3 fixed categoris

我希望这会有所帮助。

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

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