Rails active record relationship category and subcategory

Viewed 72

Basically, my teacher only created one model which is "categories" but then when we run the seed file:

Category.create([
{ name: "community", url: "community" }
]);

Category.create([
{ name: "activities", url: "activities", parent_id: 1 },
{ name: "artists", url: "artists", parent_id: 1 },
{ name: "childcare", url: "childcare", parent_id: 1 }
]);

As you can see there is a reference on the second Category.create.

And then on the Category model, I saw this:

class Category < ApplicationRecord
    belongs_to :parent, class_name: "Category", optional: true
    has_many :subcategories, class_name: "Category", foreign_key: :parent_id, dependent: :destroy
end

As you can see, my teacher did not create two different models to do the relation which looks confusing to me.

And then on the public_controller.rb file he puts:

def home
    @categories = Category.where(parent_id: nil)
end 

Which also a bit confusing to me.

Can anyone explain the model association made above line by line in layman's term?

Sorry I was so confuse and a newbie here.

1 Answers

That's a self join relationship, it's pure SQL, and Active Record has its way to make it work.

The line

belongs_to :parent, class_name: "Category", optional: true

is making a relationship between the categories table to itself, where the class_name is needed, because by convention Rails will use the first argument to infer the table name. The third argument optional states that there's no constraint for new records to be created with a parent_id. That strategy is what makes a category and a subcategory; categories don't have a parent_id, while subcategories must have one.

The foreign key is also inferred from the first argument parent, so the relationship is based on the column parent_id.

The second line

has_many :subcategories, class_name: "Category", foreign_key: :parent_id, dependent: :destroy

makes another relationship from categories to categories as well, but for you to have an easy way to access to subcategories having the category or vice-versa. The class_name remains the same, as it's the same table, but this time the foreign_key can not be inferred, so you must be explicit, the fourth argument dependent: :destroy is what makes every subcategory associated to a category to be destroyed when their category is destroyed, leaving no "orphan" records.

How do you get only categories and not subcategories?

Category.where(parent_id: nil)

By querying the categories table and filtering those rows with no parent_id.

Related