# Associations
> Rails associations establish connections between Active Record models. With them, we can define how our models interact and save time an duplication while allowing better model interactions.

In Rails, associations are a powerful way to define relationships between models in an intuitive and expressive manner.

Establishing these connections, makes working with the framework easier by reducing duplication or tedious operations with data that's naturally associated.

Let's explore what can associations do for us and why do they exist:
## Why associations
The main reason associations exist in Rails is that, without them, we would have to write a lot of code to handle data that's associated with our records.

We would also have to worry about data integrity issues like orphaned records, that arise because of deleting a given record and forgetting or failing to delete associated data.

In Rails associations are declared using macros, which are methods that can generate or modify methods at runtime:

```ruby
class User < ApplicationRecord
  has_many :articles
end
```

This macro adds many methods to the user [model](https://avohq.io/glossary/models) that are useful to query and manipulate associated records.

These methods know how to query the database to return the associated `Article` collection that belongs to the user and avoid us the need to query using Active Record:

```ruby
user = User.first
articles = user.articles
articles = Article.where(user_id: user.id) ## Alternative to using associations
```

Moreover, we can pass the `dependent: :destroy` argument to the association:

```ruby
class User < ApplicationRecord
  has_many :articles, dependent: :destroy
end
```

Which will destroy associated articles if we delete a given user. This saves us from having to write repetitive code every time we need to destroy records:

```ruby
user = User.first
articles = Article.where(user_id: user.id)
articles.each do |article|
  article.destroy
end
user.destroy
```
## Types of associations in Rails
Rails provides six types of basic associations and some advanced associations to help us model our data layer to fit our needs.

Note that these associations are not exclusively related to Rails, but they are a convenient time saver and can also avoid 

Let's start by understanding the `belongs_to` association:
### Belongs to

![Belongs to association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MjgyLCJwdXIiOiJibG9iX2lkIn19--de36ff622839b0e08b2fe63e0e7891f1e119f7b2/rails-associations-belongs-to.png)


The belongs-to association establishes a relationship with another model, given that the model that establishes the `belongs_to` relates to one instance of the associated model.

```ruby
class Article < ApplicationRecord
  belongs_to :user
end
```

In this case, we know that an article belongs to one user, which is the author.

By convention, and unless we define it otherwise, this association expects a `user_id` foreign_key in the articles table.

It also makes the `Article` instances “know” about the user that it belongs to, but it doesn't create the inverse relation unless we declare it in the `User` class.

This means that, to allow users to be associated with multiple articles, we would need to declare a `has_many` or `has_one` association in the `User` class depending on how we want to model our data.
### Has one
![Has one association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MjgzLCJwdXIiOiJibG9iX2lkIn19--458d8216de0d769419ff6aa3d46e4713ff71cf9a/rails-associations-has-one.png)

The `has_one` association is useful when we intend to declare that two models can only be related with one instance of each other.

For example, we might want to store the user's profile information in a separate `profiles` table with a corresponding `Profile` model.

However, a given user normally has only one profile, so we can use the `has_one` in this case.

```ruby
class User < ApplicationRecord
  has_one :profile
end

class Profile < ApplicationRecord
  belongs_to :user
end
```

Setting both associations would give the `User` access to the `.profile` method, which would return a single `Profile` instance that corresponds to the first profile associated with the user.

Note that declaring the association doesn't validate that a user can only create one profile, so we might want to add a validation to make sure that we cannot add multiple profiles for a user:

```ruby
class Profile < ApplicationRecord
  belongs_to :user
  validates :user_id, uniqueness: true
end
```

This is a good start but enforcing uniqueness at the application level isn't enough to guarantee that no duplicate profile can be created at the database level.

To solve this, we can add an index with a migration:

```ruby
class AddUniqueIndexToProfiles < ActiveRecord::Migration[8.0]
  def change 
    add_index :profiles, :user_id, unique: true
  end
end
```

### Has Many
![Has many association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6Mjg0LCJwdXIiOiJibG9iX2lkIn19--f0e441e2ef7e7a61ed37e790a679e0268ae511ef/rails-assocations-has-many.png)

The `has_many` association creates a one-to-many relationship with other models, meaning that a model can have zero or multiple instances of another model.

It's declared like this:

```ruby
class Author < ApplicationRecord
  has_many :books
end
```

It's implied that the `Book` model's table will have a foreign key that references the author. By default, this key would match the model in singular: `article_id` but we can customize that if it suits us.

Like stated before, many book related methods are defined by the `has_many :books` macro. These methods allow us to build, create, query and manipulate the related records.

Just like before, if we would like to add referential integrity at the database level, we would have to add the `foreign_key: true` when creating the `books` table:

```ruby
create_table :books do |t|
  t.references :author, foreign_key: true
end
```

This will make sure that no book can be created without a valid author: if we try to associate a book with an `author_id` that doesn't exist, we would meet with an error at the db level.
### Has many through
![Has many through association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6Mjg1LCJwdXIiOiJibG9iX2lkIn19--f990e41d5522b36f41e8930a404dd08b1c038299/has-many-through.png)

The `has_many :through` association sets up a many-to-many relationship using a join model, which relates two models via an intermediary.

For this example, we will model a user that can place orders within our application. At some point, we might want to retrieve every purchased item for a given user.

The `has_many :through` association is helpful for a case like this. The association setup looks like this:

```ruby
class User < ApplicationRecord
  has_many :orders
end
```

Then, we set the associations in the `Order` model:

```ruby
class Order < ApplicationRecord
  belongs_to :user
  has_many :order_items
end
```

Conversely, the `OrderItem` model would declare a `belongs_to`:

```ruby
class OrderItem < ApplicationRecord
  belongs_to :order
end
```

Finally, we associate the users with the order items:

```ruby
class User < ApplicationRecord
  has_many :orders
  has_many :order_items, through: :orders
end
```

If we find that the join model—the order item in this case—doesn't do anything beyond connecting the `User` and `Order` models, we can use a `has_and_belongs_to_many` association, even though, in the long term, that's rarely the case, and we are usually better of using this type of association.
### Has one through
![Has one through association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6Mjg2LCJwdXIiOiJibG9iX2lkIn19--c0a7d61a1d7f1aeadf15a652d1d7ff9613f4f14b/has-one-through.png)

The `has_one :through` association establishes a one-to-one relationship with another model through an intermediary model.

It's very similar to the `has_many :through` except we get access to one instance of other models through a third one.

In this example, each order item represents a product that has been purchased. And, because each product only has one brand, we can access the brand for it from the `OrderItem` model.

To achieve this, we would have to set the associations like this:

```ruby
class OrderItem < ApplicationRecord
  belongs_to :product
  has_one :brand, through: :product
end
```

Then, we would have to set the association between the `Product` and the `Brand`:

```ruby
class Product < ApplicationRecord
  belongs_to :brand
  has_many :order_items
end
```

We would then be able to access the brand of order items using `order_item.brand` which 
### Has and belongs to many
![Has and belongs to many association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6Mjg3LCJwdXIiOiJibG9iX2lkIn19--8d03162a537c8936afca5fba36b18727604b4e7e/has-and-belongs-to-many.png)

The `has_and_belongs_to_many` association is very similar to the `has_many :through` with one difference: it requires a join table but not a corresponding model and the join table is not required to have a primary key.

In the example shown above, we're modeling a learning platform where users can enroll in courses.

To define the `has_and_belongs_to_many` associations, we don't need to define a `Enrollment` model, just to have a join table that follows a convention: it should be the result of joining both model names in plural and alphabetical order.

Hence, we get the `course_users` table.

Now, to declare the association, we need to add the `has_and_belongs_to_many` macro to both models:

```ruby
class User < ApplicationRecord
  has_and_belongs_to_many :courses
end
```

```ruby
class Course < ApplicationRecord
  has_and_belongs_to_many :users
end
```

You might feel that the `course_users` table is not very expressive, and you would probably be right, so we can rename the table to something like `enrollments`:

![[has-and-belongs-to-many-custom-table-name.png]]

Now, we just need to specify the table name to the association using the `join_table` argument:

```ruby
class User < ApplicationRecord
  has_and_belongs_to_many :courses, join_table: "enrollments"
end
```

```ruby
class Course < ApplicationRecord
  has_and_belongs_to_many :users, join_table: "enrollments"
end
```

Now, everything should work the same as before, but now we have a more meaningful join table.

As a note, if we want to create a join table without a primary key, we would have to use the `create_join_table` method in the migration.

Consider that prescinding of a PK for this join table can give us minor performance gains and make the table more simple, but we have to put some thought before deciding to avoid regretting it in the future.

{{warning}}
The `has_and_belongs_to_many` association is a practical solution when we're sure that our join table won't need extra attributes or behavior in the long term. However, it's not uncommon to add them with time. If we're not sure whether the table will need extra attributes or behavior eventually, we are better suited using a `has_many :through` association.
{{/warning}}
## Customizing Associations
Rails associations can be customized if your database tables or model names do not correspond directly with the defaults.

This means that we can declare associations with models whose names don't exactly match the name of the association or that have table names that differ from the model's name.

For example, we could have a `User` that acts as an author for articles without having to create a separate Active Record model:

```ruby
class Article < ApplicationRecord
  belongs_to :author, class_name: "User", foreign_key: :user_id
end
```

Using the `class_name` we can tell the `Article` which class we expect to relate to, and the `foreign_key` lets the association know that we're expecting a `user_id` on the articles table.

We can also customize the `foreign_key` to match our association name by having an `author_id` column that acts as a foreign key in the articles table and making that explicit in the association:

```ruby
class Article < ApplicationRecord
  belongs_to :author, class_name: "User", foreign_key: :author_id
end
```

Of course, these changes only make sense if the semantical gains are worth more to our codebase than deviating from Rails conventions.

Because of the presence of the `foreign_key` in this customization, Active Record will not recognize the bidirectional association between the author and the article, which will lose us access to some association goodies.

That's why we can make the relation explicit using the `inverse_of` to customize the association without losing any features:

```ruby
class Article < ApplicationRecord
  belongs_to :author, class_name: "User", foreign_key: :author_id, inverse_of: :article
end
```
## Advanced associations
Rails provides us with three advanced associations besides the six basic ones we already covered.

These allow us to relate models in ways that are less intuitive but help us avoid duplication and let us better work with data when some specific scenarios arise:
### Polymorphic associations
![Polymorphic association diagram](/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6Mjg4LCJwdXIiOiJibG9iX2lkIn19--0e6d7ec0d0b12dc0d326d7fe86e1ee485042b305/polymorphic-association.png)

A polymorphic association, which gets its name from the OOP programming principle, establishes a relation between models that don't necessarily know about each other via their foreign keys.

It allows us to relate one model to multiple other models without explicitly defining the relation.

In the example above, a `Bookmark` is a model that can belong to posts or products or, really, an indefinite number of things. 

The `Bookmark` model constructs the relation via the `bookmarkable` association, which is an arbitrary name we give to things that can be bookmarked.

This means that, for example, a user can bookmark posts or products using a single `bookmarks` table without the need to add hypothetical `post_bookmarks` and `product_bookmarks` tables and associating them with users.

They are very useful when we are confident that we will need our model to relate to other models in a generic way. Some good use cases for this type of association are:

- Comments
- Bookmarks
- Reactions
- Images
- Votes
- Tags
- Reviews

To declare this association in the example shown above, we first need to start with the polymorphic model:

```ruby
class Bookmark < ApplicationRecord
  belongs_to :user
  belongs_to :bookmarkable, polymorphic: true
end
```

Then, we declare the inverse association for posts and products:

```ruby
class Post < ApplicationRecord
  has_many :bookmarks, as: :bookmarkable
end

class Product < ApplicationRecord
  has_many :bookmarks, as: :bookmarkable
end
```

Here, the `bookmarkable` object would be the one declaring the `has_many` so, calling `@bookmark.bookmarkable` would return a `Post` instance when it's a post and a `Product` otherwise.

And, products and posts would have access to the `bookmarks` method which would return the bookmarks associated with them, which also contain the user for every instance.

As our application grows, we can add the `has_many :bookmarks, as: :bookmarkable` to as many models as we require and things should work without many issues.

When adding a polymorphic association like this, it's a good idea to add an index to tie the `type` and `id` attributes of the polymorphic table:

```ruby
class CreateBookmarks < ActiveRecord::Migration[8.0]
  def change
    create_table :bookmarks do |t|
      t.bigint :bookmarkable_id
      t.string :bookmarkable_type
      t.timestamps
    end

    add_index :bookmarks, [:bookmarkable_id, :bookmarkable_type]
  end
end
```

One last bit of advice: if you decide to change the class names of the bookmarked items, say you want to change from `Post` to `Article` you have to update the corresponding `bookmarks` table to keep everything working as expected.
### Composite primary keys
A composite primary is a primary key that's composed of multiple columns.

It means that, instead of being able to find a record with a single key like `Product.find(4)` we would need to pass various attributes to retrieve a given product.

We can use these composite keys when we want to keep record integrity to a new level:

```ruby
product = Product.find([1, "RCMM-1025"]) # => Find by id and SKU
```

If we wish to set a composite key when creating a migration, we pass the desired attributes to the `primary_key` argument:

```ruby
class CreateEnrollments < ActiveRecord::Migration[8.0]
  def change
    create_table :enrollments, primary_key: [:course_id, :user_id] do |t|
      t.integer :course_id
      t.integer :user_id
    end
  end
end
```

Now, we need to specify our model to use a composite PK:

```ruby
class Enrollment < ApplicationRecord
  self.primary_key = [:course_id, :user_id]

  belongs_to :course
  belongs_to :user
end
```

However, before deciding whether using a composite PK is adequate for your use case, consider that they can result in performance degradation and increased application logic.
### Self Joins
The self-join association is one where the `foreign_key` is located within the same table of the model that declares it.

They are useful to model relationships between different instances of a same class, especially when we need some sort of hierarchy

For example, if we wanted to model a category hierarchy where items belong to categories or subcategories, we could achieve it using a single `Category` table with self-joins:

```ruby
class Category < ApplicationRecord
  belongs_to :parent, class_name: "Category", optional: true
  has_many :subcategories, class_name: "Category", foreign_key: "parent_id"
end
```

The migration would look something like this:

```ruby
class CreateCategories < ActiveRecord::Migration[8.0]
  def change
    create_table :categories do |t|
      t.string :name, null: false
      t.references :parent, foreign_key: { to_table: :categories }, index: true, null: true
      
      t.timestamps
    end
    
    add_index :categories, [:name, :parent_id], unique: true
  end
end
```

By declaring an optional `parent_id` field, we could consider categories with no `parent_id` as root categories, and we could model a tree structure where categories could have children, grandchildren, fathers, grandfathers, and so on.

```ruby
class Category < ApplicationRecord
  belongs_to :parent, class_name: "Category", optional: true
  has_many :subcategories, class_name: "Category", foreign_key: "parent_id"

  scope :root_categories, -> { where(parent_id: nil) }

  def root?
    parent_id.nil?
  end
  
  def ancestors
    return [] if root?

    ## Recursive traversal of ancestors returning
    ## an array with the ancestor list
    parent.ancestors + [parent]
  end

  def descendants
    # Recursive traversal of descendants that returns a
    # flattened array with the Category instances
    subcategories.map { |subcat| [subcat] + subcat.descendants }.flatten
  end
end
````

Now, items can declare a `belongs_to :category_id` and be related to a category without knowing specifically about the hierarchy.
## Other associations
Rails offers access to other associations like Single Table Inheritance and Delegated Types.

Even though these associations are very useful for some use cases, they escape the scope of this article. We will talk about them in future articles so you can learn more about them and how to use them in your applications.

Meanwhile, you can learn about [delegated types](https://avohq.io/blog/delegated-types-and-and-building-the-ultimate-kanban-board-in-rails) in the article we wrote about building the [kanban board](https://avohq.io/addons/kanban) feature for Avo.

Stay in touch to learn more about advanced associations in Rails.
## Summary

Rails associations transform how you work with related data, making your code more expressive and maintainable. By understanding the different types of associations and their options, you can model complex relationships with ease. Whether you're building a simple blog or a complex application with intricate data relationships, mastering associations is essential for effective Rails development.

The true power of associations lies not just in the syntax they provide, but in how they encourage you to think about your data in terms of relationships. This mental model aligns closely with how we naturally think about information, making your code not only more efficient, but also more intuitive to write and understand.
