带有STI的多类别模型与单类别模型? -滑轨

时间:2019-05-16 15:48:04

标签: ruby-on-rails polymorphism ruby-on-rails-5 has-many-through single-table-inheritance

我有四个型号:

  • 发布
  • 产品
  • 文章
  • 位置

其中每个可以具有多个类别。唯一的逻辑规则是,每个类的类别完全分开,因此,帖子不能具有ArticleCategory,位置不能具有ProductCatgory,等等。

选项1:多个类别模型

型号:

  • PostCategory
  • ProductCategory
  • ArticleCategory
  • LocationCategory

has_many的模型通过:

  • PostCategorization
  • ProductCategorization
  • ArticleCategorization
  • LocationCategorization

这可行,但是不遵循DRY(不要重复自己)的哲学。那么,如何使用STI?

选项2:具有STI的单一类别模型

型号:

  • 类别
  • 分类

子模型:

  • PostCategory <<类别
  • ProductCategory <<类别
  • ArticleCategory <<类别
  • LocationCategory <<类别

这似乎很好,但是如果列相同并且逻辑相同,我不知道是否需要使用STI。它们之间的唯一区别是关联。

选项3:没有STI的单一类别模型?

最好有一个“ category_class”列,并执行以下操作:

class Post < ApplicationRecord

  has_many :categories, -> { where category_class: "Post" }

end

节省类和子类的数量,并简化整个解决方案。我以前使用过此功能,但未在具有多态关联的功能上使用过,这行得通吗?

2 个答案:

答案 0 :(得分:2)

也许我误会了。但是,在我看来...

您可以使用enum来指定每个Category记录的分类。像这样:

# == Schema Information
#
# Table name: categories
#
#  id                :integer          not null, primary key
#  name              :string           not null
#  categorizes       :integer          not null
#  created_at        :datetime         not null
#  updated_at        :datetime         not null
#
class Category < ApplicationRecord
  has_many :categorizations
  has_many :categorizeables, through: :categorizations

  enum categorizes: {
    post:         0,
    product:      1,
    article:      2,
    location:     3
  }

  class << self 

    def not_for(categorizeable_type)
      where.not(categorizes: categorizeable_type)
    end

  end

end

然后,您可以使用多态联接模型Categorization,例如:

# == Schema Information
#
# Table name: categorizations
#
#  id                   :integer          not null, primary key
#  category_id          :integer          not null
#  categorizeable_id    :integer          not null
#  categorizeable_type  :string           not null
#  created_at           :datetime         not null
#  updated_at           :datetime         not null
#
class Categorization < ApplicationRecord
  belongs_to :category
  belongs_to :categorizeable, polymorphic: true
end

然后您可以使用categorizationscategorieshas :many, through关联起来:

# == Schema Information
#
# Table name: posts
#
#  id                   :integer          not null, primary key
#  created_at           :datetime         not null
#  updated_at           :datetime         not null
#
class Post < ApplicationRecord
  has_many :categorizations, as: :categorizeable
  has_many :categories, through: :categorizations

  validate :correct_categorization

  def correct_categorization
    if categories.not_for(:post).any?
      errors.add(:categorization, "is incorrect")
    end
  end

end

我添加了验证,因为您说“每个类的类别完全独立”。您可能需要对此稍作摆弄,但是希望它可以使您了解其工作方式。

答案 1 :(得分:1)

我认为@jvillian建议在这种情况下使用枚举。但是,我并不特别喜欢验证规则……用户不应看到不属于他尝试分类的对象的类别。在这种情况下,我将创建一个有范围的关系,因此我将像这样定义Post模型:

class Post < ApplicationRecord
  has_many :categorizations, as: :categorizeable
  has_many :categories, -> { where(categorizes: 0)}, through: :categorizations
end

并通过关系has_many建立关联:

f.association :categories