Rails make it possible to handle many to many associations with the
has_many through. Before going into the details on them, its important to be sure if a many to many association is what you really need. A many to many relationship is created when a TableA can have multiple occurrences of TableB and TableB can have multiple occurrences of table A. A good example is how projects can be distributed across employees in a workplace. An employee can have multiple projects and a project can be assigned to multiple employees. So if there's a project almanac in the company and Bob and Juliet are working on several other company projects, they could both be assigned to this project while still working on their other projects.
The HABTM (Has and Belongs To Many Table) is sufficient when you aren't storing any metadata on the relationships. It uses a JOIN table to store the foreign keys between both tables. For these examples I'll use an employee model and a product model. The first step will be to create the migrations.
$rails g migration create_employees name:string position:string
which should generate this:
class CreateEmployees < ActiveRecord::Migration def change create_table :employees do |t| t.string :name t.string :position t.timestamps null: false end end end
For product, generate the migration and you should have this:
class CreateProducts < ActiveRecord::Migration def change create_table :products do |t| t.string :name t.string :description t.timestamps null: false end end end
Then the JOIN table
$rails g migration create_employees_products
Pay attention to the table name. It has to have both tables pluralized and in an alphabetic order separated with underscore. Now modify your JOIN table migration as follows
class CreateEmployeesProducts < ActiveRecord::Migration def change create_table :employees_products, id: false do |t| t.integer :employee_id t.integer :product_id end add_index :employees_products, :employee_id add_index :employees_products, :product_id end end
:id => false on
create_table is to make sure we don't have any primary keys on the table. It's simply just a JOIN table that takes ids of both associated models. I also added index to speed up queries.
If you think it's a lot of work to go through this you can use the HABTM generator gem
The next step will be to create the association in the employee and product model
class Employee < ActiveRecord::Base has_and_belongs_to_many :products end
class Product < ActiveRecord::Base has_and_belongs_to_many :employees end
With all that set you can now fetch records within each of them with simple AR queries like:
# Fetching products that belong to an employee e = Employee.find(1) e.products # Creating products for an employee e.products.create(name: 'project almanac')
When creating the views for our associated tables. A form for adding employees may require that you select the number of products that will be attached to each employee. We need to have checkboxes to select from the existing products and if you're wondering how to have that get properly updated in your join table, it's easy with the rails power. For a simple form object for employee you can have a new controller method:
def new @employee = Employee.new @products = Product.all end
and in your view you would have:
<%= form_for @employee do |f| %> <%= f.collection_check_boxes :product_ids, @products, :id, :name %> <% end %>
and that produces check boxes for all the products. There are more methods that can be used with the
has_many_and_belongs_to be sure to check out the guides for more.
The HMT (Has Many Through) uses a join table that can store extra data about the relationship between the two models. Imagine we need to keep tasks info for the employees on each product. We will have a JOIN table for tasks with a migration like this:
class CreateTasks < ActiveRecord::Migration def change create_table :tasks do |t| t.belongs_to :employee, index: true t.belongs_to :product, index: true t.string :task_description t.datetime :task_assign_date t.timestamps null: false end end end
Then models for employee, product, and task can be written as:
class Employee < ActiveRecord::Base has_many :tasks has_many :products, through: :tasks end
class Product < ActiveRecord::Base has_many :tasks has_may :employees, through: :tasks end
class Task < ActiveRecord::Base belongs_to :employee belongs_to :product end
has_many_and_belongs_to useful for most of my use-cases but I understand when it's important to use
has_many through. It's common to see developers that choose to always use HMT when HABTM is what's really meant for the job.