I have four models:
Each of these can have multiple Categories. The only logic rule is that the Categories are completely separate for each Class, so that a Post cannot have an ArticleCategory, a Location cannot have a ProductCatgory and so on.
Option 1: Multiple Category Models
Models:
Models for has_many through:
This works, but it doesn't adhere to the DRY (don't repeat yourself) philosophy. So, how about using STI?
Option 2: Single Category Model with STI
Models:
SubModels:
This seems fine, but I don't know if I need to even use STI if the columns are the same, and the logic is the same. The only difference between them is the associations.
Option 3: Single Category Model without STI?
Would it be better to have a "category_class" column, and do something like this:
class Post < ApplicationRecord
has_many :categories, -> { where category_class: "Post" }
end
To save on the number of classes and subclasses, and simplify the entire solution. I have used this before but not on something with polymorphic associations, would this work?
Perhaps I'm misunderstanding. But, it seems to me...
You could use an enum
to specify what each Category
record categorizes. Something like:
# == 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
Then, you can use your polymorphic join model, Categorization
Something like:
# == 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
And then you can associate your categorizations
and categories
using a has :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
I added that validation since you stated "Categories are completely separate for each Class". You may need to fiddle with that a bit, but hopefully it gives you an idea of how it might work.