Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Association between Category, Subcategory, and Lawyer

I have a vast list of Lawyers, Categories, and Subcategories.

Hint (so you could have a clue if my associations are okay)

  1. On Categories Table, I do not want to see a column on Categories Table referencing Subcategories.
  2. On Subcategories Table, I do not want to see a column on Subcategories Table referencing Categories.
  3. Not all Categories has Subcategories. i.e. some don't have subcategories as seen in the picture.
  4. I have 2 separate forms creating category and subcategory.
  5. I added category_id and subcategory_id as foreign keys to my lawyers table. Such that I can choose from lawyers form upon create, the category or subcategory a lawyer will belong to as soon in the image.
  6. Also note: A Subcategory could be created at any time, any day, for Categories not having Subcategory, as well as new Subcategories under Categories already having some Subcategories, and lawyers will be placed under them.
  7. The image is a replica of my index/homepage I am having at the moment, at least before number 6 above takes effect any time any day, and I hope to use loop to make this view happen.

Pictorial understanding of what I am trying to do:

image description of my homepage

Here are my relationships between 3 models

class Lawyer < ActiveRecord::Base
  belongs_to :category
  belongs_to :subcategory
end

class Category < ActiveRecord::Base
  has_many :lawyers
end

class Subcategory < ActiveRecord::Base
  #belongs_to :category #Do I want "category_id" in Subcategories Table?
  has_many :lawyers
end

Question

Is my association on those 3 models okay for the Hint I gave? This is pretty confusing.

like image 535
Afolabi Olaoluwa Akinwumi Avatar asked Mar 11 '23 06:03

Afolabi Olaoluwa Akinwumi


1 Answers

You don't need a Subcategory model/table, specially if they have the same columns. Your categories table should have a parent_id column. A category is a subcategory when it has a parent_id value pointing to another category record. Categories with a NULL parent_id are the top level categories.

Example

class Lawyer < ActiveRecord::Base
  belongs_to :category
end

class Category < ActiveRecord::Base
  has_many :lawyers

  # This is called a self referential relation. This is where records in a 
  # table may point to other records in the same table.
  has_many :sub_categories, class_name: "Category", foreign_key: :parent_id

  # This is a scope to load the top level categories and eager-load their 
  # lawyers, subcategories, and the subcategories' lawyers too.
  scope :top_level, -> { where(parent_id: nil).include :lawyers, sub_categories: :lawyers }
end

Note: you should create a migration to add the parent_id column to the categories table. And you can drop the subcategories table.

Now create some of your categories (I'm assuming there's a name column):

cat = Category.create name: "Corporate and Commercial Law"
subcat = Category.new name: "Corporate Tax", parent_id: cat.id
subcat.lawyers << Lawyer.find_by_name("Sabina Mexis")
subcat.save

Example table of contents:

<% Category.top_level.each do |cat| %>
  <%= cat.name %>
  <% cat.sub_categories.each do |subcat| %>
    <%= subcat.name %>
    <%= subcat.lawyers.each do |laywer| %> 
      <%= lawyer.name %>
    <% end %>
  <% end %>
<% end %>

The above is a simplified example. Hope that helps.

Update

To enhance your form to allow you to create a subcategory and assign its parent category, use a select menu filled with top_level category IDs:

<%= form_for Category.new do |f| %>
  <%= f.text_field :name %>
  <%= f.select :parent_id, options_from_collection_for_select(Category.top_level, :id, :name) %>
  <%= f.submit %>
<% end %>

Check out the docs for options_from_collection_for_select if it's unfamiliar. What it does is build a select menu with the category:id as values and their :name as the text in the menu. Make sure you add :parent_id to your strong parameters to allow mass assignment via params[:category].

The laywer error was just a typo in my example code, it's fixed now.

like image 94
DiegoSalazar Avatar answered Mar 24 '23 07:03

DiegoSalazar