Understanding Rails Associations and Their Helper Methods — Ruby Deep Dive [22]

Bhavyansh @ DiversePixel
6 min readSep 4, 2024

--

Associations in Ruby on Rails are powerful features that allow you to define relationships between different models in your application. By defining associations, you can streamline your code and make it more intuitive by using a range of automatically generated helper methods. These methods allow for easy creation, access, and management of related objects. In this article, we’ll take a deep dive into Rails associations, exploring the different types of associations, the helper methods they provide, and how these methods simplify querying and modifying related objects.

1. Types of Associations in Rails

Rails provides several types of associations to define relationships between models:

  • belongs_to: Indicates that a model has a foreign key that points to another model.
  • has_one: Indicates a one-to-one connection with another model.
  • has_many: Indicates a one-to-many connection with another model.
  • has_many :through: Sets up a many-to-many connection with another model through a third model.
  • has_and_belongs_to_many: Directly sets up a many-to-many connection with another model without a join model.
  • has_one :through: A one-to-one relationship through another model.

Each type of association generates a set of helper methods that allow for easy interaction with the related models.

2. Understanding Helper Methods Generated by Associations

When you define associations in your models, Rails automatically generates a variety of methods that help you interact with the associated records. Let’s explore these methods in detail.

belongs_to Association Helper Methods

When a model uses belongs_to, it signifies that it holds a reference to another model through a foreign key. For instance, consider the following models:

class Menu < ApplicationRecord
belongs_to :restaurant
end
class Restaurant < ApplicationRecord
has_many :menus
end

With the belongs_to :restaurant association, Rails provides several helper methods:

  • restaurant: Returns the associated Restaurant object for the Menu instance.
@menu = Menu.find(1)
@restaurant = @menu.restaurant
  • restaurant=: Assigns a Restaurant object to the menu instance.
@menu.restaurant = Restaurant.find(2)
  • build_restaurant: Instantiates a new Restaurant object and assigns it to the menu without saving either object to the database.
@menu.build_restaurant(name: "New Restaurant")
  • create_restaurant: Creates a new Restaurant object, assigns it to the menu, and saves both objects to the database.
@menu.create_restaurant(name: "New Restaurant")
  • create_restaurant!: Similar to create_restaurant, but raises an exception if validation fails.

has_many Association Helper Methods

The has_many association is used when a model can be associated with multiple instances of another model. For example:

class Restaurant < ApplicationRecord
has_many :menus
end

With the has_many :menus association, Rails provides the following methods:

  • menus: Returns a collection of Menu objects associated with the Restaurant.
@restaurant = Restaurant.find(1)
@menus = @restaurant.menus
  • menus<<: Adds one or more Menu objects to the association collection.
@restaurant.menus << Menu.new(name: "Dinner Menu")
  • menus.delete: Removes one or more objects from the collection by removing the association in the database, not deleting the objects themselves.
@restaurant.menus.delete(@menu)
  • menus.destroy: Similar to delete, but also destroys the associated objects.
@restaurant.menus.destroy(@menu)
  • menus.create: Creates and saves a new Menu object associated with the Restaurant.
@restaurant.menus.create(name: "Lunch Menu")
  • menus.create!: Similar to create, but raises an exception if validation fails.
  • menus.build: Instantiates a new Menu object associated with the Restaurant without saving it.
@menu = @restaurant.menus.build(name: "Brunch Menu")
  • menus.count, menus.size, menus.empty?, menus.exists?: Provides various ways to query the association for the number of related records, check if there are any records, or if a specific record exists.
@restaurant.menus.count
@restaurant.menus.exists?(name: "Dinner Menu")

has_one Association Helper Methods

The has_one association establishes a one-to-one relationship between models. For example:

class Supplier < ApplicationRecord
has_one :account
end
class Account < ApplicationRecord
belongs_to :supplier
end

With the has_one :account association, the following methods are available:

  • account: Returns the associated Account object.
@supplier.account
  • account=: Assigns an Account object to the supplier.
@supplier.account = Account.find(1)
  • build_account: Instantiates a new Account object and assigns it to the supplier without saving either object.
  • create_account: Creates and saves a new Account object associated with the supplier.
  • create_account!: Similar to create_account, but raises an exception if validation fails.

has_and_belongs_to_many Association Helper Methods

has_and_belongs_to_many is used to set up a direct many-to-many relationship without a join model:

class Article < ApplicationRecord
has_and_belongs_to_many :categories
end
class Category < ApplicationRecord
has_and_belongs_to_many :articles
end

With has_and_belongs_to_many :categories, Rails provides the following methods:

  • categories: Returns all associated Category objects.
  • categories<<: Adds one or more categories to the association.
  • categories.delete: Removes one or more categories from the association.
  • categories.destroy: Destroys the association and deletes the categories.
  • categories.create: Creates a new associated Category.
  • categories.create!: Creates a new associated Category, raising an exception if validation fails.
  • categories.empty?, categories.size, categories.count: Query methods for checking the status of the association.

has_many :through and has_one :through Association Helper Methods

has_many :through and has_one :through associations allow for more sophisticated relationships, utilizing a join model:

class Physician < ApplicationRecord
has_many :appointments
has_many :patients, through: :appointments
end
class Patient < ApplicationRecord
has_many :appointments
has_many :physicians, through: :appointments
end
class Appointment < ApplicationRecord
belongs_to :physician
belongs_to :patient
end

With has_many :through, you get all the same helper methods as a regular has_many association (patients, patients<<, patients.create, etc.), but the association goes through the join model (appointments in this case).

3. How Reverse Associations Work

Understanding how to retrieve associated records in the opposite direction is equally important. When you define an association, Rails provides methods to access and manipulate the reverse relationship.

For example, with the following belongs_to and has_many association:

class Menu < ApplicationRecord
belongs_to :restaurant
end
class Restaurant < ApplicationRecord
has_many :menus
end

When you call @menu.restaurant, Rails uses the foreign key (restaurant_id) in the menus table to look up the corresponding Restaurant record. This lookup is handled by Active Record and is done efficiently using SQL queries.

Similarly, when you call @restaurant.menus, Rails retrieves all Menu records with the restaurant_id matching the Restaurant’s ID.

4. Polymorphic Associations and Their Methods

Polymorphic associations allow a model to belong to more than one other model using a single association.

For example:

class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
class User < ApplicationRecord
has_many :pictures, as: :imageable
end

With polymorphic associations, Rails provides:

  • imageable: Returns the object the Picture belongs to, whether it’s a Product or a User.
  • imageable=: Assigns an object to the Picture polymorphic association.

Polymorphic methods behave similarly to other Active Record methods but are flexible enough to handle associations across different models.

5. Delegated Types and Their Methods

Delegated types offer an alternative to Single Table Inheritance (STI) by solving the issue of attribute bloat when a single table holds attributes for multiple subclasses. Instead of creating a single table for different subclasses with potentially unused attributes, delegated types let you split attributes across multiple tables.

In this setup, a base class, such as Entry, holds common attributes, while each subclass, like Message or Comment, has its own table for specific attributes.

Example:

class Entry < ApplicationRecord
delegated_type :entryable, types: %w[Message Comment], dependent: :destroy
end
class Message < ApplicationRecord
include Entryable
def title
subject
end
end
class Comment < ApplicationRecord
include Entryable
def title
content.truncate(20)
end
end

With delegated types, Rails automatically provides the following helper methods:

  • entryable: Returns the delegated object (either Message or Comment).
  • entryable_class: Returns the class of the delegated object.
  • entryable_name: Returns the name of the subclass, like "message" or "comment".
  • Entry.messages: Retrieves all entries where entryable_type is "Message".
  • Entry#message?: Returns true if the entryable_type is "Message", otherwise false.
  • Entry#message: Returns the Message record, if applicable.

Delegated types allow you to organize related models without overloading a single table, providing cleaner, more flexible associations while still benefiting from Active Record’s delegation features.

The Difference Between STI and Delegated Types

  • STI: Multiple models (subclasses) use one table, which leads to storing a lot of unnecessary columns (For example, if only Message has a subject and only Comment has content, both fields would still have to be in the entries table. This results in attribute bloat).
  • Delegated Types: Each model gets its own table for specific attributes, but they share a base class with only common attributes, reducing complexity and bloat.

Conclusion

Understanding the helper methods generated by Rails associations is crucial for efficient Rails development. These methods provide powerful ways to interact with related data without writing complex queries or handling SQL directly. By leveraging associations and their methods, you can write more readable, maintainable, and robust Rails applications. Whether you’re setting up simple one-to-many relationships or complex many-to-many relationships with join models, Rails associations offer a powerful toolkit to manage your data relationships effectively.

--

--

Bhavyansh @ DiversePixel

Hey I write about Tech. Join me as I share my tech learnings and insights. 🚀