Making Music With Active Record Associations
Tags: rails, beginners, tutorial, ruby
Published: 2023-03-26 01:06:49

My latest project involves creating an _Eclectic Music_ database.  The purpose is to allow the user to store data about all types of music.  Eclectic means deriving ideas, style, or taste from a broad and diverse range of sources.  This is why I gave this music database this name.  It will allow the user to fully express their musical tastes.  

All excitement aside, this is a database for which Active Record was made to handle.  Recall that Active Record is a object relational mapper (ORM) which maps classes to tables.  Each instance of a class is mapped to a row (aka record).  These records can be linked to other records through associations.  Here are the common association macros below.

```
belongs_to
has_many
has_many :through
```
Here are also some the more esoteric association macros which I will not discuss in any detail except to list these for completeness sake.  These include `has_one`, `has_one :through`, and `has_and_belongs_to_many`.

The common active record relationships include one-to-many and many-to-many relationships.  One should also be familiar with foreign keys.  Foreign keys are columns that refer to the primary key of another table.  This is how data in one table is linked to data in another table.

## **One-to-many relationships:**

These are the most common relationships used.  Active Record gives us the `has_many` and `belongs_to` macros for creating instance methods to access data across models in a one-to-many relationship.  What in the world does creating instance methods to access data across models mean?  The key here is the word macro.  In nutrition, a macro includes macronutrients such as carbohydrates, proteins, and fats which are essential for our bodies to function optimally.  Macros in programming are code generators (basically code that writes more code).  Basically they allow our 'body' of code to run optimally and cut down on the code that we need to write.  Let us go through the sample code below.

The models:

```
class Song < ApplicationRecord
    belongs_to :artist
end

class Artist < ApplicationRecord
    has_many :songs
end
```
Corresponding Rails migrations: 
```
class CreateSongs < ActiveRecord::Migration[6.1]
  def change
    create_table :songs do |t|
      t.string :name
      t.integer :artist_id

      t.timestamps
    end
  end
end

class CreateArtists < ActiveRecord::Migration[6.1]
  def change
    create_table :artists do |t|
      t.string :name
      t.string :genre
      t.string :date_established
      t.text :interesting_fact
      t.string :artist_image_url

      t.timestamps
    end
  end
end
```
In this example `Artist` has many `Song`s, and a `Song` belongs to `Artist`.  This allows us to do some really cool things.  The `artist` method is made available to `Song`.  This is _singular_ because a `Song` can only belong to one `Artist`.  Likewise, the `songs` method is made available to `Artist`.  Note that this method is _pluralized_ because an `Artist` has many `Song`s.  This method gives access to a collection of the `Song`s belonging to a particular `Artist` instance.

```
first_song=Song.first
 => #<Song id: 31, name: "Likey", artist_id: 6, created_at: "2023-03-15 18:36:15.029983000 +0000", updated_at: "2023-03-15 18:36:15.029983... 

first_song.artist
 => #<Artist id: 6, name: "Twice", genre: "K-pop", date_established: "2015", interesting_fact: "Twice is the first female Korean act to simultaneo...", artist_image_url: "https://i.pinimg.com/originals/63/19/f5/6319f51f79...", user_id: 1, created_at: "2023-03-15 18:36:14.984921000 +0000", updated_at: "2023-03-22 01:17:56.926138000 +0000"> 
2.7.4 :004 > 
```
Here I have assigned the first record of `Song`s to `first_song`.  I can now access the `Artist` record for `first_song` using the `artist` method.  In plain English, I found the first song named "Likey" by the K-pop artist known as Twice.

![Likey by Twice](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/suj4vhsd5om37rafu9o3.jpg)

```
class Model < ApplicationRecord
    belongs_to :association
end
```
The `belongs_to` association macro gives this class access to a method defined by the symbol passed to the `belongs_to` method. The `association` method (`model_instance.association`) returns the associated object, if any. If no associated object is found, it returns `nil`.

There are eight methods granted by the `belongs_to` macro which include:

- association
- association=(associate)
- build_association(attributes = {})
- create_association(attributes = {})
- create_association!(attributes = {})
- reload_association
- association_changed?
- association_previously_changed?

In all of these methods, association is replaced with the symbol passed as the first argument to belongs_to. For example, if a `Song` record has no existing `Artist`, one could be created using `song.create_artist({artist_params})`.

```
blackpink=Artist.first
 => #<Artist id: 5, name: "Blackpink", genre: "K-pop", date_established: "2016", interesting_fact: "Referred to as the biggest girl group in the world..... 
2.7.4 :002 > 

blackpink.songs
 => #<ActiveRecord::Associations::CollectionProxy [#<Song id: 65, name: "Boombayah", artist_id: 5, created_at: "2023-03-16 08:08:36.097321000 +0000", updated_at: "2023-03-16 08:08:36.097321000 +0000">, #<Song id: 66, name: "Ddu-Du Ddu-Du", artist_id: 5, created_at: "2023-03-16 08:08:36.105075000 +0000", updated_at: "2023-03-16 08:08:36.105075000 +0000">, #<Song id: 67, name: "Whistle", artist_id: 5, created_at: "2023-03-16 08:08:36.112071000 +0000", updated_at: "2023-03-16 08:08:36.112071000 +0000">, #<Song id: 68, name: "Playing with Fire", artist_id: 5, created_at: "2023-03-16 08:08:36.118742000 +0000", updated_at: "2023-03-16 08:08:36.118742000 +0000">, #<Song id: 69, name: "Stay", artist_id: 5, created_at: "2023-03-16 08:08:36.126371000 +0000", updated_at: "2023-03-16 08:08:36.126371000 +0000">, #<Song id: 70, name: "As If It's Your Last", artist_id: 5, created_at: "2023-03-16 08:08:36.132867000 +0000", updated_at: "2023-03-16 08:08:36.132867000 +0000">, #<Song id: 71, name: "Forever Young", artist_id: 5, created_at: "2023-03-16 08:08:36.139672000 +0000", updated_at: "2023-03-16 08:08:36.139672000 +0000">, #<Song id: 72, name: "How You Like That", artist_id: 5, created_at: "2023-03-16 08:08:36.146085000 +0000", updated_at: "2023-03-16 08:08:36.146085000 +0000">, #<Song id: 73, name: "Ice Cream", artist_id: 5, created_at: "2023-03-16 08:08:36.152874000 +0000", updated_at: "2023-03-16 08:08:36.152874000 +0000">, #<Song id: 74, name: "Pretty Savage", artist_id: 5, created_at: "2023-03-16 08:08:36.159772000 +0000", updated_at: "2023-03-16 08:08:36.159772000 +0000">, ...]>
```
For this case, I have taken the first `Artist` record and assigned it to the variable `blackpink`.  Then the `songs` method was used to gain access to the collection of `Song`s by `blackpink`.


![Blackpink](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aubajiwqj02p2m90pygc.jpg)


```
class Model < ApplicationRecord
    has_many :collection
end
```
The `has_many` association macro gives this class access to a method defined by the symbol passed to the `has_many` method. The `collection` method (`model_instance.collection`) returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.

```
smooth_operator=Artist.find_by(name: "Smooth Operator")
 => #<Artist id: 32, name: "Smooth Operator", genre: "hip-hop", date_established: "2020", interesting_fact: "He is smooth", artist_image_url: "https://e... 

smooth_operator.songs.create(name: "Smooth Coding using Active Record Associations", artist_id: 32)
 => #<Song id: 10, name: "Smooth Coding using Active Record Associations", artist_id: 32, created_at: "2023-03-16 08:08:36.139672000 +0000", updated_at: "2023-03-16 08:08:36.159772000 +0000"> 
```
In this case I have taken a record from a fictional artist "Smooth Operator" and assigned it to the variable `smooth_operator`.  Then, I used the create method on the collection of Songs from `smooth_operator` and created a new `Song` called "Smooth Coding using Active Record Associations". 

When you declare a `has_many` association, the declaring class automatically gains 17 methods related to the association.

- collection
- collection<<(object, ...)
- collection.delete(object, ...)
- collection.destroy(object, ...)
- collection=(objects)
- collection_singular_ids
- collection_singular_ids=(ids)
- collection.clear
- collection.empty?
- collection.size
- collection.find(...)
- collection.where(...)
- collection.exists?(...)
- collection.build(attributes = {})
- collection.create(attributes = {})
- collection.create!(attributes = {})
- collection.reload

In all of these methods, collection is replaced with the symbol passed as the first argument to `has_many`, and collection_singular is replaced with the singularized version of that symbol.  An example is `smooth_operator.songs.create(name: "Smooth Coding using Active Record Associations", artist_id: 32)` that was just illustrated. 

Further, one can still use the multitude of Active Record Query Methods on these collections and associations.

```
blackpink.songs.count
 => 15 

blackpink.songs.pluck(:name)
 => ["Boombayah", "Ddu-Du Ddu-Du", "Whistle", "Playing with Fire", "Stay", "As If It's Your Last", "Forever Young", "How You Like That", "Ice Cream", "Pretty Savage", "Love Sick Girls", "Pink Venom", "Shut Down", "Typa Girl", "Ready for Love"] 

blackpink.songs.first
 => #<Song id: 65, name: "Boombayah", artist_id: 5, album_id: 34, created_at: "2023-03-16 08:08:36.097321000 +0000", updated_at: "2023-03-16 08:08:36.097321000 +0000"> 

blackpink.songs.last
 => #<Song id: 79, name: "Ready for Love", artist_id: 5, album_id: 36, created_at: "2023-03-16 08:08:36.198539000 +0000", updated_at: "2023-03-16 08:08:36.198

smooth_operator.songs.order(:name)
 => #<ActiveRecord::AssociationRelation [#<Song id: 137, name: "finally made it smooth", artist_id: 32, created_at: "2023-03-20 10:56:51.733042000 +0000", updated_at: "2023-03-20 10:56:51.733042000 +0000">, #<Song id: 133, name: "make it smooth", artist_id: 32, created_at: "2023-03-20 10:49:08.178298000 +0000", updated_at: "2023-03-20 10:49:08.178298000 +0000">, #<Song id: 134, name: "make it smooth and silky", artist_id: 32, created_at: "2023-03-20 10:51:16.889179000 +0000", updated_at: "2023-03-20 10:51:16.889179000 +0000">, #<Song id: 131, name: "sexy smooth", artist_id: 32, created_at: "2023-03-20 10:38:23.173949000 +0000", updated_at: "2023-03-20 10:38:23.173949000 +0000">, #<Song id: 136, name: "silky and smooth", artist_id: 32, created_at: "2023-03-20 10:54:00.360430000 +0000", updated_at: "2023-03-20 10:54:00.360430000 +0000">, #<Song id: 135, name: "smooth and silky", artist_id: 32, created_at: "2023-03-20 10:52:41.305959000 +0000", updated_at: "2023-03-20 10:52:41.305959000 +0000">, #<Song id: 132, name: "smooth things over", artist_id: 32, created_at: "2023-03-20 10:46:05.463048000 +0000", updated_at: "2023-03-20 12:17:46.602416000 +0000">]> 
```
One last note on foreign keys:  The foreign key should be in the belonging table.  Please refer to my migrations above in which the foreign key `artist_id` is located in the `songs` table.

## **Many-to-many relationships:**

Active Record gives us the `has_many :through`, in addition to the `has_many` and `belongs_to` macros for creating instance methods to access data across models in a many-to-many relationship.  The concepts described above are still applicable here.  However, there are some differences from a one-to-many relationship.

First is the location of the foreign keys.  One, should think of a many-to-many relationship as two one-to-many relationships joined together because conceptually that is exactly what it is.  Since these relationships are _joined_ together it makes sense to have a _join_ table.  This join table contains the foreign keys needed to for a record from each table to reference the other table.

Second is the use of the `has_many :through` macro. A `has_many :through` association is often used to set up a many-to-many connection with another model. This association indicates that the declaring model can be matched with zero or more instances of another model by proceeding through a third model.  This third model is the join table.

Here is some sample code.

The models:

```
class Artist < ApplicationRecord
    has_many :songs
    has_many :albums, through: :songs
end

class Song < ApplicationRecord
    belongs_to :artist
    belongs_to :album
end

class Album < ApplicationRecord
    has_many :songs
    has_many :artists, through: :songs
end
```

Corresponding Rails migrations:

```
class CreateArtists < ActiveRecord::Migration[6.1]
  def change
    create_table :artists do |t|
      t.string :name
      t.string :genre
      t.string :date_established
      t.text :interesting_fact
      t.string :artist_image_url
      t.integer :user_id

      t.timestamps
    end
  end
end

class CreateSongs < ActiveRecord::Migration[6.1]
  def change
    create_table :songs do |t|
      t.string :name
      t.integer :artist_id
      t.integer :album_id

      t.timestamps
    end
  end
end

class CreateAlbums < ActiveRecord::Migration[6.1]
  def change
    create_table :albums do |t|
      t.string :name
      t.string :album_cover_url
      t.integer :year_released

      t.timestamps
    end
  end
end
```
In this case, `Artist` has many `Song`s and has many `Album`s through `Song`s.  Therefore, `Artist` has gained access to the `songs` method as well as the `albums` method through songs.  `Album` has many `Song`s and has many `Artist`s through `Song`s.  Hence, `Album` has gained access to the `songs` method as well as the `artists` method through songs.  `Song` belongs to `Album` and belongs to `Artist`.  Song is our join table which contain the foreign keys `artist_id` and `album_id`, one key to reference one table each.

We should test these methods right?  Let's move on from K-pop artists.  Where are the rappers?

I heard there was a rapper who resembled "Hammering" Hank Aaron.  I am going to find him, his songs, and his albums.  I heard he could dance.

![MC Hammer dance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsoa1fj4rkg7v3evivds.gif)

Alright, here we go.

```
mc_hammer=Artist.find_by(name:"MC Hammer")
 => #<Artist id: 19, name: "MC Hammer", genre: "hip-hop", date_established: "1985", interesting_fact: "He was named for his resemblance to \"Hammering\"... 

mc_hammer.songs
 => #<ActiveRecord::Associations::CollectionProxy [#<Song id: 138, name: "U Can't Touch This", artist_id: 19, album_id: 56, created_at: "2023-03-20 11:13:12.315773000 +0000", updated_at: "2023-03-20 11:16:08.290758000 +0000">, #<Song id: 113, name: "Too Legit Too Quit", artist_id: 19, album_id: 49, created_at: "2023-03-20 00:56:03.164558000 +0000", updated_at: "2023-03-20 00:56:03.164558000 +0000">]>
 
mc_hammer.albums
 => #<ActiveRecord::Associations::CollectionProxy [#<Album id: 56, name: "Please Hammer Don't Hurt Em", album_cover_url: "https://upload.wikimedia.org/wikipedia/en/d/d3/Ple...", year_released: 1990, created_at: "2023-03-20 11:13:10.656256000 +0000", updated_at: "2023-03-20 11:14:26.094936000 +0000">, #<Album id: 49, name: "Too Legit to Quit ", album_cover_url: "https://upload.wikimedia.org/wikipedia/en/a/a0/Too...", year_released: 1991, created_at: "2023-03-19 23:31:08.410185000 +0000", updated_at: "2023-03-20 02:06:42.688468000 +0000">]>
```
Now let's test this in reverse.  I am taking the MC Hammer album "Too Legit to Quit " and finding the songs and the artist who released the album.  I predict it will come back to the Hammer man.

```
too_legit_to_quit=Album.find(49)
 => #<Album id: 49, name: "Too Legit to Quit ", album_cover_url: "https://upload.wikimedia.org/wikipedia/en/a/a0/Too...", year_released: 1991, created_a... 

too_legit_to_quit.songs
 => #<ActiveRecord::Associations::CollectionProxy [#<Song id: 113, name: "Too Legit Too Quit", artist_id: 19, album_id: 49, created_at: "2023-03-20 00:56:03.164558000 +0000", updated_at: "2023-03-20 00:56:03.164558000 +0000">]> 

too_legit_to_quit.artists
 => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 19, name: "MC Hammer", genre: "hip-hop", date_established: "1985", interesting_fact: "He was named for his resemblance to \"Hammering\" Ha...", artist_image_url: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...", user_id: 1, created_at: "2023-03-19 19:34:56.268802000 +0000", updated_at: "2023-03-19 23:02:13.796906000 +0000">]> 
```
It's all good when you know how to use Active Record Associations to make music but let's get something on the page.  Our search for an artist using `mc_hammer=Artist.find_by(name:"MC Hammer")`.

![MC Hammer Search](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0vwagwqdqq1923llq67l.png)

Now we can see his albums using `mc_hammer.albums`.

![MC Hammer Albums](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0arxfhp5ss96zqm4c1e.png)

Check out these songs by MC Hammer using `mc_hammer.songs`. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ghata0rrxy2nc2m9rwf.png)
 
We just made music together using the power of Active Record Associations.

## Recap:

- The common relationships are one-to-many and many-to-many.
- One-to-many relationships use `has_many` and `belong_to` macros.
- Many-to-many relationships use `has_many :through` in addition to `has_many` and `belong_to` macros.
- The use of foreign keys are integral to these relationships.  In a one-to-many relationship the foreign key is located in the belonging table.  In a many-to-many relationship it is located in the join table.
- The macros `belong_to`, `has_many` and `has_many :through` add additional methods to your models.

In short Active Record associations with their macros allow us work with complex networks of related models to create rich and dynamic applications.  Now that is music to my ears. 


Source: Ruby on Rails Guides {% embed https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association %}