4

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.

Promise Preston
  • 24,334
  • 12
  • 145
  • 143
Afolabi Olaoluwa
  • 1,898
  • 3
  • 16
  • 37

1 Answers1

8

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.

DiegoSalazar
  • 13,361
  • 2
  • 38
  • 55
  • 2
    Perfect. Nice & clean +1 – Anuj Sep 02 '16 at 07:37
  • @diego.greyrobot 1. To implement creating Category using form, how will my form look like? Because I don't know how I can factor cat.id to my parent_id column on categories table using form. Also subcat.lawyer on my form. 2. I will have to remove or subcategory_id on my Lawyers Table right? – Afolabi Olaoluwa Sep 02 '16 at 10:48
  • I get `NoMethodError: undefined method `lawyer=' for #` when I run `subcat.lawyer = Lawyer.find_by_name "Sabina Mexis"` – Afolabi Olaoluwa Sep 02 '16 at 11:49
  • 2
    @AfolabiOlaoluwaAkinwumi updated the answer with another example. – DiegoSalazar Sep 02 '16 at 15:42
  • @diego.greyrobot I still get `NoMethodError: undefined method 'lawyer' for #`. The error is actually thrown by erb <%= subcat.lawyer.name %> – Afolabi Olaoluwa Sep 02 '16 at 22:41
  • @AfolabiOlaoluwaAkinwumi Correction: `lawyers` – DiegoSalazar Sep 03 '16 at 23:49
  • Thanks @diego.greyrobot. I have learned a lot from you. On the Table of Content example where we have `<%= subcat.lawyers.name %>` from your anwer, does not show the names of the attached lawyer's name. Instead it show the word **Lawyer** instead of lawyers names under those Categories and Subcategories. But when I do: `<%= subcat.lawyers.inspect %>`, I see the attributes. How do I fix to see the names of the lawyers under each category--subcategories? – Afolabi Olaoluwa Sep 04 '16 at 14:31
  • 1
    @AfolabiOlaoluwaAkinwumi Since `lawyers` is an array, you have to iterate over them and print out each name. See the updated example. – DiegoSalazar Sep 06 '16 at 16:54
  • @DiegoSalazar Hey, I was wondering if `parent_id` had to be an integer or a string? Would either work or is there a best practice? – Jake May 23 '18 at 21:14
  • 1
    @Jake id columns are typically integers. – DiegoSalazar May 24 '18 at 04:21
  • I was using this way of categorization for my `posts` section replacing `lawyers` with `posts` Would I handle all CRUD for posts in the `posts view` or the `categories view`? – Jake May 25 '18 at 23:13
  • 1
    Handle posts CRUD in the posts controller – DiegoSalazar May 26 '18 at 04:58