【问题标题】:rails model subclassing -> multi table inheritance or polymorphismrails模型子类化->多表继承或多态
【发布时间】:2012-11-08 20:11:00
【问题描述】:

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

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 模型设置为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。

【问题讨论】:

  • 我认为继承本身应该可以工作(也就是说,create 应该是ImageComponent 的一种方法)但是 AR 的东西不会,因为它对文件和表名以及就像基于类名一样。虽然我认为 Rails 的大部分魔法都可以被覆盖,但也许更好的方法是使用 mixin,而不是直接继承。
  • 到底是什么问题? rails 实现模型子类化。这一切与多态性与多表继承有什么关系?
  • 多态和多表继承是我想要做的两种方法,但对于我的情况,这两种方法都不是特别干燥。我想将多表继承与模型子类化结合起来。 AFAIK 你不能创建一个模型的子类来为子类提供一个新的表。我在上面示例中的子类无法实现自己的字段——它们被绑定到超类的字段。

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


【解决方案1】:

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

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

【讨论】:

  • 谢谢。这是我最初的实现,但感觉很混乱,所有子类都可以使用子类特定的字段。即,如果我只想将一个字段添加到 ImageComponent,它也可用于 VideoComponent。叹息。
  • 虽然可以访问字段,但每个子类可以有不同的验证。因此,对于VideoComponent,您可以明确指定某些字段应为空白。但我可以想象ImageComponentVideComponent 之间可能存在很多重叠,因此在这种情况下,STI 似乎是我的最佳解决方案。如果你真的想干掉你的数据模型,你可以使用Properties 表,其中每个组件has_many :properties。 HTH。
【解决方案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 的组件控制器。由于组件接受资源(即图像/视频)的属性,因此您可以保存组件以及资源并为每个资源添加常规验证。

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

= 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) 上述模型结构仅对 resource 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

我希望这会有所帮助。

【讨论】:

    【解决方案3】:

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

    但是,您可以使用一个技巧,即在表中拥有一个字符串列,每个模型都可以使用该列来存储自定义序列化数据。为了做到这一点,这些数据元素没有被索引是可以的,因为您将无法在 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-07-21
      • 1970-01-01
      • 1970-01-01
      • 2015-03-10
      • 1970-01-01
      • 2014-06-23
      • 1970-01-01
      • 2013-06-10
      • 1970-01-01
      相关资源
      最近更新 更多