A.3 Association Helper Methods

A.3.1 belongs_to

belongs_to(Symbol, Hash) ⇒ ActiveRecord

Let’s say I have a movie in a variable m. It is annoying and error prone to, whenever I want the director associated with a movie, have to type

d = Director.where({ :id => m.director_id }).at(0)

Wouldn’t it be great if I could just type

d = m.director

and it would know how to go look up the corresponding row in the directors table based on the movie’s director_id?

Unfortunately, I can’t, because .director isn’t a method that Movie objects automatically know how to perform — the method would be undefined. (Movie objects know how to perform .director_id because we get a method for every column in the table.)

We could define such an instance method in the model ourselves without too much trouble. But, fortunately, since domain modeling and associations are at the heart of every application’s power, Rails gives us a shortcut. Go into the Movie model and add a line like this:

belongs_to(:director, { :class_name => "Director", :foreign_key => "director_id" })

This line tells Rails:

  • belongs_to: We only one result (not an array of results).
  • :director: Define a method called .director for all movie objects.
  • :class_name => "Director": When someone invokes .director on a movie, go fetch a result from the directors table.
  • :foreign_key => "director_id": Use the value in the director_id column of the movie to query the directors table for a row.

This is exactly what we would do if we defined the instance method by hand:

def director
  return Director.where({ :id => m.director_id }).at(0)
end

Either way, we now can utilize this handy shortcut anywhere in our application when we have a movie m and we want to get the record in the directors table associated with it:

m.director

Even better, if you’ve named your method and foreign key column conventionally (exactly matching the name of the other table), you can use the super-shorthand version:

belongs_to(:director)
  • If you omit specifying the :class_name, Rails assumes that the table that you want to query is named the same thing as the method you are defining.

  • If you omit specifying the :foreign_key, Rails assumes that the foreign key column is named the same thing as the method plus _id.

  • If either of those things happens to not be true, then just include the Hash as the second argument to belongs_to and spell it all out:

    belongs_to(:owner, { :class_name => "User", :foreign_key => "poster_id" })

    This would give us a method called .owner that returns a User, even though the foreign key column is called poster_id. We still have complete control, if we need to depart from conventional method/foreign key names for some reason.

A.3.2 has_many

has_many(Symbol, Hash) ⇒ ActiveRecord_Relation

Let’s say I have a director in a variable d. It is annoying and error prone to, whenever I want the films associated with the director, have to type

f = Movie.where({ :director_id => d.id })

Wouldn’t it be great if I could just type

f = d.movies

and it would know how to go look up the corresponding rows in the movies table based on the director’s id?

Unfortunately, I can’t, because .movies isn’t a method that Director objects automatically know how to perform — the method would be undefined.

We could define such an instance method in the model ourselves without too much trouble. But, fortunately, since domain modeling and associations are at the heart of every application’s power, Rails gives us a shortcut. Go into the Director model and add a line like this:

has_many(:movies, { :class_name => "Movie", :foreign_key => "director_id" })

This line tells Rails:

  • has_many: We want many results in an array.
  • :movies: Define a method called .movies for all director objects.
  • :class_name => "Movie": When someone invokes .movies on a director, go fetch results from the movies table.
  • :foreign_key => "director_id": Use the director_id column of the movies table to filter using the id of the director.

This is exactly what we would do if we defined the instance method by hand:

def movies
  return Movie.where({ :director_id => self.id })
end

Either way, we now can utilize this handy shortcut anywhere in our application when we have a director d and we want to get the records in the movies table associated with it:

d.movies

Even better, if you’ve named your method and foreign key column conventionally (exactly matching the name of the other table), you can use the super-shorthand version:

has_many(:movies)
  • If you omit specifying the :class_name, Rails assumes that the table that you want to query is named the same thing as the method you are defining.

  • If you omit specifying the :foreign_key, Rails assumes that the foreign key column is named the same thing as this model plus _id.

  • If either of those things happens to not be true, then just include the Hash as the second argument to belongs_to and spell it all out:

    has_many(:filmography, { :class_name => "Movie", :foreign_key => "director_id" })

    This would give us a method called .filmography that returns Movies. We still have complete control, if we need to depart from conventional method/foreign key names for some reason.

A.3.3 has_many/through

has_many(Symbol, Hash) ⇒ ActiveRecord_Relation

After you have established all of your one-to-many association helper methods, you can also add many-to-many helper methods with the :through option on has_many:

class Movie < ApplicationRecord
   has_many(:characters)
   has_many(:actors, { :through => :characters, :source => :actor })
end

class Character < ApplicationRecord
  belongs_to(:movie)
  belongs_to(:actor)
end

class Actor < ApplicationRecord
   has_many(:characters)
   has_many(:movies, { :through => :characters, :source => :movie })
end