4.8. has_many – 1:n Association

In order to explain has_many, let's create a book database. In this database, there is a model with books and a model with authors. As a book can have multiple authors, we need a 1:n association (one-to-many association) to represent it. As you will see, this is very easy with ActiveRecord.

Note

Associations are also sometimes referred to as relations or relationships.
First, we create a Rails application:
$ rails new bookshelf
  [...]
$ cd bookshelf 
$ 
Now we create the model for the books:
$ rails generate model book title
  [...]
$
And finally, we create the database table for the authors. In this, we need an assignment field to the books table. This foreign key is always set by default as name of the referenced object (here: book) with an attached _id:
$ rails generate model author book_id:integer first_name last_name
  [...]
$
Then execute a rake db:migrate so that the database tables are actually created:
$  rake db:migrate
  [...]
$
Let's have a look at this on the console:
$ rails console
Loading development environment (Rails 3.2.9)
>> Book
=> Book(id: integer, title: string, created_at: datetime, updated_at: datetime)
>> Author
=> Author(id: integer, book_id: integer, first_name: string, last_name: string, created_at: datetime, updated_at: datetime)
>> exit
$
The two database tables are set up and can be used with ActiveRecord. But ActiveRecord does not yet know anything of the 1:n relation between them. But this can be easily done in two steps:
  • We insert the option has_many in the model file app/models/book.rb:
    class Book < ActiveRecord::Base
      attr_accessible :title
    
      has_many :authors
    end
  • And we insert the option belongs_to in the model file app/models/author.rb:
    class Author < ActiveRecord::Base
      attr_accessible :book_id, :first_name, :last_name
    
      belongs_to :book
    end
    
These two simple definitions form the basis for a good deal of Rails magic. ActiveRecord will generate a bunch of cool new methods for us to link both models.

Creating Records

In this example, we want to save a record for the book "Homo faber" by Max Frisch.

Manually

We drop the database with rake db:reset
$ rake db:reset
  [...]
$  
Then we first create an object with the book data. Next, we remember the book ID and then create the author record with this ID in the field book_id:
$ rails console
Loading development environment (Rails 3.2.9)
>> book = Book.create(title: 'Homo faber')
   (0.1ms)  begin transaction
  SQL (24.8ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 11:31:01 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 11:31:01 UTC +00:00]]
   (3.1ms)  commit transaction
=> #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:31:01", updated_at: "2012-11-18 11:31:01">
>> author = Author.create(book_id: 1, first_name: 'Max', last_name: 'Frisch') 
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:31:25 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 11:31:25 UTC +00:00]]
   (3.0ms)  commit transaction
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:31:25", updated_at: "2012-11-18 11:31:25">
>> Book.all
  Book Load (0.3ms)  SELECT "books".* FROM "books" 
=> [#<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:31:01", updated_at: "2012-11-18 11:31:01">]
>> Author.all
  Author Load (0.4ms)  SELECT "authors".* FROM "authors" 
=> [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:31:25", updated_at: "2012-11-18 11:31:25">]
>> exit
$
Entering the book_id manually in this way is of course not very practical and susceptible to errors. That's why there is the method the section called “create”.

create

Now we try doing the same as in the section called “Manually”, but this time we use a bit of ActiveRecord magic. We can use the method create of authors to add new authors to each Book object. These automatically get the correct book_id:
$ rake db:reset
  [...]
$ rails console
Loading development environment (Rails 3.2.9)
>> book = Book.create(title: 'Homo faber')
   (0.1ms)  begin transaction
  SQL (24.8ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 11:32:39 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 11:32:39 UTC +00:00]]
   (2.5ms)  commit transaction
=> #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:32:39", updated_at: "2012-11-18 11:32:39">
>> author = book.authors.create(first_name: 'Max', last_name: 'Frisch')
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:32:56 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 11:32:56 UTC +00:00]]
   (2.8ms)  commit transaction
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:32:56", updated_at: "2012-11-18 11:32:56">
>> Book.all
  Book Load (0.3ms)  SELECT "books".* FROM "books" 
=> [#<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:32:39", updated_at: "2012-11-18 11:32:39">]
>> Author.all
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" 
=> [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:32:56", updated_at: "2012-11-18 11:32:56">]
>> exit
$
You can also place the authors.create() directly behind the Book.create():
$ rake db:reset
  [...]
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.create(title: 'Homo faber').authors.create(first_name: 'Max', last_name: 'Frisch')
   (0.1ms)  begin transaction
  SQL (24.6ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 11:34:04 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 11:34:04 UTC +00:00]]
   (2.6ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:34:04 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 11:34:04 UTC +00:00]]
   (0.9ms)  commit transaction
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:34:04", updated_at: "2012-11-18 11:34:04">
>> Book.all
  Book Load (0.3ms)  SELECT "books".* FROM "books" 
=> [#<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:34:04", updated_at: "2012-11-18 11:34:04">]
>> Author.all
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" 
=> [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:34:04", updated_at: "2012-11-18 11:34:04">]
>> exit
$
As create also accepts an array of hashes as an alternative to a single hash, you can also create multiple authors for a book in one go:
$ rake db:reset
  [...]
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.create(title: 'Example').authors.create([{last_name: 'A'}, {last_name: 'B'}, {last_name: 'C'}])
   (0.1ms)  begin transaction
  SQL (24.5ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00], ["title", "Example"], ["updated_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00]]
   (3.0ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00], ["first_name", nil], ["last_name", "A"], ["updated_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00]]
   (0.8ms)  commit transaction
   (0.1ms)  begin transaction
  SQL (0.4ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00], ["first_name", nil], ["last_name", "B"], ["updated_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00]]
   (0.6ms)  commit transaction
   (0.0ms)  begin transaction
  SQL (0.3ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00], ["first_name", nil], ["last_name", "C"], ["updated_at", Sun, 18 Nov 2012 11:28:18 UTC +00:00]]
   (0.8ms)  commit transaction
=> [#<Author id: 1, book_id: 1, first_name: nil, last_name: "A", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">, #<Author id: 2, book_id: 1, first_name: nil, last_name: "B", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">, #<Author id: 3, book_id: 1, first_name: nil, last_name: "C", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">]
>> Book.all
  Book Load (0.3ms)  SELECT "books".* FROM "books" 
=> [#<Book id: 1, title: "Example", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">]
>> Author.all
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" 
=> [#<Author id: 1, book_id: 1, first_name: nil, last_name: "A", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">, #<Author id: 2, book_id: 1, first_name: nil, last_name: "B", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">, #<Author id: 3, book_id: 1, first_name: nil, last_name: "C", created_at: "2012-11-18 11:28:18", updated_at: "2012-11-18 11:28:18">]
>> exit
$ 

build

The method build resembles create. But the record is not saved. This only happens after a save:
$ rake db:reset
  [...]
$ rails console
Loading development environment (Rails 3.2.9)
>> book = Book.create(title: 'Homo faber')
   (0.1ms)  begin transaction
  SQL (24.5ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 11:35:35 UTC +00:00], ["title", "Homo faber"], ["updated_at", Sun, 18 Nov 2012 11:35:35 UTC +00:00]]
   (3.0ms)  commit transaction
=> #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:35:35", updated_at: "2012-11-18 11:35:35">
>> author = book.authors.build(first_name: 'Max', last_name: 'Frisch')
=> #<Author id: nil, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: nil, updated_at: nil>
>> author.new_record?
=> true
>> author.save
   (0.1ms)  begin transaction
  SQL (0.7ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 1], ["created_at", Sun, 18 Nov 2012 11:36:12 UTC +00:00], ["first_name", "Max"], ["last_name", "Frisch"], ["updated_at", Sun, 18 Nov 2012 11:36:12 UTC +00:00]]
   (2.5ms)  commit transaction
=> true
>> author.new_record?
=> false
>> exit
$ 

Warning

When using create and build, you of course have to observe logical dependencies, otherwise there will be an error. For example, you cannot chain two build methods. Example:
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.build(title: 'Example').authors.build(last_name: 'A')
NoMethodError: undefined method `build' for #<Class:0x007fb082f2c350>
 from /Users/xyz/.rvm/gems/ruby-1.9.3-p327/gems/activerecord-3.2.9/lib/active_record/dynamic_matchers.rb:50:in `method_missing'
 from (irb):1
 from /Users/xyz/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:47:in `start'
 from /Users/xyz/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands/console.rb:8:in `start'
 from /Users/xyz/.rvm/gems/ruby-1.9.3-p327/gems/railties-3.2.9/lib/rails/commands.rb:41:in `<top (required)>'
 from script/rails:6:in `require'
 from script/rails:6:in `<main>'
>> exit
$ 

Accessing Records

Now we need example data. Please populate the file db/seeds.rb with the following content:
# ruby encoding: utf-8

Book.create(title: 'Homo faber').authors.create(first_name: 'Max', last_name: 'Frisch')
Book.create(title: 'Der Besuch der alten Dame').authors.create(first_name: 'Friedrich', last_name: 'Dürrenmatt')
Book.create(title: 'Julius Shulman: The Last Decade').authors.create([
  {first_name: 'Thomas', last_name: 'Schirmbock'},
  {first_name: 'Julius', last_name: 'Shulman'},
  {first_name: 'Jürgen', last_name: 'Nogai'}
  ])
Book.create(title: 'Julius Shulman: Palm Springs').authors.create([
  {first_name: 'Michael', last_name: 'Stern'},
  {first_name: 'Alan', last_name: 'Hess'}
  ])
Book.create(title: 'Photographing Architecture and Interiors').authors.create([
  {first_name: 'Julius', last_name: 'Shulman'},
  {first_name: 'Richard', last_name: 'Neutra'}
  ])
Book.create(title: 'Der Zauberberg').authors.create(first_name: 'Thomas', last_name: 'Mann')
Book.create(title: 'In einer Familie').authors.create(first_name: 'Heinrich', last_name: 'Mann')
Now drop the database and refill it with the db/seeds.rb:
$ rake db:reset
  [...]
$
The convenient feature of the 1:n assignment in ActiveRecord is the particularly easy access to the n instances. Let's look at the first record:
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.first
  Book Load (0.1ms)  SELECT "books".* FROM "books" LIMIT 1
=> #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:42:22", updated_at: "2012-11-18 11:42:22">
>> Book.first.authors
  Book Load (0.3ms)  SELECT "books".* FROM "books" LIMIT 1
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1
=> [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:42:23", updated_at: "2012-11-18 11:42:23">]
>>
Isn't that cool?! You can access the records simply via the plural form of the n model. The result is returned as array. Hm, maybe it also works the other way round?
>> Author.first
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" LIMIT 1
=> #<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:42:23", updated_at: "2012-11-18 11:42:23">
>> Author.first.book
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" LIMIT 1
  Book Load (0.2ms)  SELECT "books".* FROM "books" WHERE "books"."id" = 1 LIMIT 1
=> #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:42:22", updated_at: "2012-11-18 11:42:22">
>> exit
$
Bingo! Accessing the associated Book class is also very easy. And as it's only a single record (belongs_to), the singular form is used in this case.

Note

If there was no author for this book, the result would be an empty array. If no book is associated with an author, then ActiveRecord outputs the value nil as Book.

Searching For Records

Before we can start searching, we again need defined example data. Please fill the file db/seeds.rb with the following content (its the same as we used in the section called “Accessing Records”):
# ruby encoding: utf-8

Book.create(title: 'Homo faber').authors.create(first_name: 'Max', last_name: 'Frisch')
Book.create(title: 'Der Besuch der alten Dame').authors.create(first_name: 'Friedrich', last_name: 'Dürrenmatt')
Book.create(title: 'Julius Shulman: The Last Decade').authors.create([
  {first_name: 'Thomas', last_name: 'Schirmbock'},
  {first_name: 'Julius', last_name: 'Shulman'},
  {first_name: 'Jürgen', last_name: 'Nogai'}
  ])
Book.create(title: 'Julius Shulman: Palm Springs').authors.create([
  {first_name: 'Michael', last_name: 'Stern'},
  {first_name: 'Alan', last_name: 'Hess'}
  ])
Book.create(title: 'Photographing Architecture and Interiors').authors.create([
  {first_name: 'Julius', last_name: 'Shulman'},
  {first_name: 'Richard', last_name: 'Neutra'}
  ])
Book.create(title: 'Der Zauberberg').authors.create(first_name: 'Thomas', last_name: 'Mann')
Book.create(title: 'In einer Familie').authors.create(first_name: 'Heinrich', last_name: 'Mann')
Now drop the database and refill it with the db/seeds.rb:
$ rake db:reset
  [...]
$
And off we go. First we check how many books are in the database:
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.count
   (0.1ms)  SELECT COUNT(*) FROM "books" 
=> 7
>>
And how many authors?
>> Author.count
   (0.1ms)  SELECT COUNT(*) FROM "authors" 
=> 11
>> exit
$ 

joins

How can we find out all books that have at least one author with the surname 'Mann'? We use a join.
[24]We can use this to join the two models in the search as well:
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.joins(:authors).where(:authors => {last_name: 'Mann'})
  Book Load (0.1ms)  SELECT "books".* FROM "books" INNER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann'
=> [#<Book id: 6, title: "Der Zauberberg", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Book id: 7, title: "In einer Familie", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
>> Book.joins(:authors).where(:authors => {last_name: 'Mann'}).count
   (0.2ms)  SELECT COUNT(*) FROM "books" INNER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann'
=> 2
>>
The database contains two books with the author 'Mann'. In the SQL, you can see that the method joins executes an INNER JOIN.
Of course, we can also do it the other way round. We are looking for the author of the book 'Homo faber':
>> Author.joins(:book).where(:books => {title: 'Homo faber'})
  Author Load (0.2ms)  SELECT "authors".* FROM "authors" INNER JOIN "books" ON "books"."id" = "authors"."book_id" WHERE "books"."title" = 'Homo faber'
=> [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
>> exit
$

includes

includes is very similar to the method joins (see the section called “joins”). Again, you can use it to search within a 1:n association. Let's once more search for all books with an author whose surname is 'Mann':
$ rails console
Loading development environment (Rails 3.2.9)
>> Book.includes(:authors).where(:authors => {last_name: 'Mann'})
  SQL (0.3ms)  SELECT "books"."id" AS t0_r0, "books"."title" AS t0_r1, "books"."created_at" AS t0_r2, "books"."updated_at" AS t0_r3, "authors"."id" AS t1_r0, "authors"."book_id" AS t1_r1, "authors"."first_name" AS t1_r2, "authors"."last_name" AS t1_r3, "authors"."created_at" AS t1_r4, "authors"."updated_at" AS t1_r5 FROM "books" LEFT OUTER JOIN "authors" ON "authors"."book_id" = "books"."id" WHERE "authors"."last_name" = 'Mann'
=> [#<Book id: 6, title: "Der Zauberberg", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Book id: 7, title: "In einer Familie", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
>> exit
$ 
In the console output, you can see that the SQL code is different from the joins query.
joins only reads in the Book records and includes also reads the associated Authors. As you can see even in our little example, this obviously takes longer (0.2 ms vs. 0.3 ms).
So why would you want to use includes at all? Well, if you already know before the query that you will later need all author data, then it can make sense to use includes, because then you only need one database query. That is a lot faster than starten a seperate query for each n.
In that case, would it not be better to always work with includes? No, it depends on the specific case. When you are using includes, a lot more data is transported initially. This has to be cached and processed by Ruby, which takes longer and requires more resources.

delete and destroy

With the methods destroy, destroy_all, delete and delete_all you can delete records, as described in Section 4.12, “Removing a Record”. In the context of has_many, this means that you can delete the Author records associated with a Book in one go:
$ rails console
Loading development environment (Rails 3.2.9)
>> book = Book.where(title: 'Julius Shulman: The Last Decade').first
  Book Load (0.1ms)  SELECT "books".* FROM "books" WHERE "books"."title" = 'Julius Shulman: The Last Decade' LIMIT 1
=> #<Book id: 3, title: "Julius Shulman: The Last Decade", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">
>> book.authors
  Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 3
=> [#<Author id: 3, book_id: 3, first_name: "Thomas", last_name: "Schirmbock", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Author id: 4, book_id: 3, first_name: "Julius", last_name: "Shulman", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Author id: 5, book_id: 3, first_name: "Jürgen", last_name: "Nogai", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
>> book.authors.destroy_all
   (0.2ms)  begin transaction
  SQL (5.0ms)  DELETE FROM "authors" WHERE "authors"."id" = ?  [["id", 3]]
  SQL (0.0ms)  DELETE FROM "authors" WHERE "authors"."id" = ?  [["id", 4]]
  SQL (0.0ms)  DELETE FROM "authors" WHERE "authors"."id" = ?  [["id", 5]]
   (2.3ms)  commit transaction
=> [#<Author id: 3, book_id: 3, first_name: "Thomas", last_name: "Schirmbock", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Author id: 4, book_id: 3, first_name: "Julius", last_name: "Shulman", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">, #<Author id: 5, book_id: 3, first_name: "Jürgen", last_name: "Nogai", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
>> book.authors
=> []
>> exit
$

Options

I can't comment on all possible options at this point. But some of them are so useful and I use them in every project, so I would like to introduce them here. For all others, please refer to the Ruby on Rails documentation that you can find on the Internet at http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html or on your system on the shell via ri ActiveRecord::Associations::ClassMethods.

belongs_to

In my opinion, the most important option for belongs_to is:
  • :touch => :true
    This automatically sets the field updated_at of the entry in the table Book to the current time when an Author is edited. In the app/models/author.rb, it would look like this:
    class Author < ActiveRecord::Base
      attr_accessible :book_id, :first_name, :last_name
    
      belongs_to :book, :touch => true
    end
You should have a quick look at the list of all available options. You can access it in the shell via ri ActiveRecord::Associations::ClassMethods#belongs_to.

has_many

I find that the most important options for has_many are:
  • :order => :last_name
    If you want to sort the authors by surname, you can do this via the following app/models/book.rb:
    class Book < ActiveRecord::Base
      attr_accessible :title
    
      has_many :authors, :order => :last_name
    end
    As an example, let's create a new book with new authors and see how ActiveRecord sorts them:
    $ rails console
    Loading development environment (Rails 3.2.9)
    >> Book.create(title: 'Test').authors.create([{last_name: 'Z'}, {last_name: 'A'}]) 
       (0.1ms)  begin transaction
      SQL (23.5ms)  INSERT INTO "books" ("created_at", "title", "updated_at") VALUES (?, ?, ?)  [["created_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00], ["title", "Test"], ["updated_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00]]
       (2.6ms)  commit transaction
       (0.1ms)  begin transaction
      SQL (0.6ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 8], ["created_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00], ["first_name", nil], ["last_name", "Z"], ["updated_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00]]
       (0.8ms)  commit transaction
       (0.1ms)  begin transaction
      SQL (0.4ms)  INSERT INTO "authors" ("book_id", "created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?, ?)  [["book_id", 8], ["created_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00], ["first_name", nil], ["last_name", "A"], ["updated_at", Sun, 18 Nov 2012 12:04:31 UTC +00:00]]
       (0.8ms)  commit transaction
    => [#<Author id: 12, book_id: 8, first_name: nil, last_name: "Z", created_at: "2012-11-18 12:04:31", updated_at: "2012-11-18 12:04:31">, #<Author id: 13, book_id: 8, first_name: nil, last_name: "A", created_at: "2012-11-18 12:04:31", updated_at: "2012-11-18 12:04:31">]
    >> Book.last.authors
      Book Load (0.3ms)  SELECT "books".* FROM "books" ORDER BY "books"."id" DESC LIMIT 1
      Author Load (0.2ms)  SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 8 ORDER BY last_name
    => [#<Author id: 13, book_id: 8, first_name: nil, last_name: "A", created_at: "2012-11-18 12:04:31", updated_at: "2012-11-18 12:04:31">, #<Author id: 12, book_id: 8, first_name: nil, last_name: "Z", created_at: "2012-11-18 12:04:31", updated_at: "2012-11-18 12:04:31">]
    >> exit
    $ 
    And if we want to sort in descending order for a change:
    has_many :authors, :order => 'title DESC'
  • :dependent => :destroy
    If a book is removed, then it usually makes sense to also automatically remove all authors dependent on this book. This can be done via :dependent => :destroy in the app/models/book.rb:
    class Book < ActiveRecord::Base
      attr_accessible :title
    
      has_many :authors, :dependent => :destroy
    end
    In the following example, we destroy the first book in the database table. All authors of this book are also automatically destroyed:
    $ rails console
    Loading development environment (Rails 3.2.9)
    >> Book.first
      Book Load (0.1ms)  SELECT "books".* FROM "books" LIMIT 1
    => #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">
    >> Book.first.authors
      Book Load (0.3ms)  SELECT "books".* FROM "books" LIMIT 1
      Author Load (0.1ms)  SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1
    => [#<Author id: 1, book_id: 1, first_name: "Max", last_name: "Frisch", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">]
    >> Book.first.destroy
      Book Load (0.3ms)  SELECT "books".* FROM "books" LIMIT 1
       (0.1ms)  begin transaction
      Author Load (0.2ms)  SELECT "authors".* FROM "authors" WHERE "authors"."book_id" = 1
      SQL (4.8ms)  DELETE FROM "authors" WHERE "authors"."id" = ?  [["id", 1]]
      SQL (0.1ms)  DELETE FROM "books" WHERE "books"."id" = ?  [["id", 1]]
       (3.0ms)  commit transaction
    => #<Book id: 1, title: "Homo faber", created_at: "2012-11-18 11:46:29", updated_at: "2012-11-18 11:46:29">
    >> Author.exists?(1)
      Author Exists (0.2ms)  SELECT 1 AS one FROM "authors" WHERE "authors"."id" = 1 LIMIT 1
    => false
    >> exit
    $

    Important

    Please always remember the difference between the methods destroy (see the section called “destroy”) and delete (see the section called “delete”). This association only works with the method destroy.
  • :has_many .. :through
    Here I need to elaborate a bit: you will probably have noticed that in our book-author example we have sometimes been entering authors several times in the authors table. Normally, you would of course not do this. It would be better to enter each author only once in the authors table and take care of the association with the books via an intermediary table. For this purpose, there is has_many , :through => .
    This kind of association is called Many-to-Many (n:n) and we'll discuss it in detail in Section 4.9, “Many-to-Many – n:n Association”.


[24] If you are interested in the theoretical background on joins, you will find more information here: http://en.wikipedia.org/wiki/SQL#Queries, http://en.wikipedia.org/wiki/Join_(SQL), http://en.wikipedia.org/wiki/Relational_algebra#Joins_and_join-like_operators