読者です 読者をやめる 読者になる 読者になる

【Rails】モデルのリレーションシップ

Ruby on Rails
スポンサーリンク

モデルのリレーションについてです。

1対多の関係

使用するテーブルの準備をします。

$ ./bin/rails g model Publisher name:string address:text
$ ./bin/rails g model Book name:string publisher:references published:date price:integer

publishers (参照先テーブル) と books (参照元テーブル) の「1対多」となる関係を表すには Publisher モデルにhas_manyメソッドを使用します。 引数にはモデル名(参照元のBookモデルは復数なので複数形)を指定します。これは参照元の books テーブルの一覧を取得するためのアクセサメソッドにもなります。

class Publisher < ApplicationRecord
  has_many :books
end

使用例は以下の通りです。

irb(main):001:0> publisher = Publisher.create(name: 'Gihyo inc.', address: 'Ichigaya')
irb(main):002:0> Book.create(name: 'Book1', publisher_id: 1, published: '2016-10-13', price: 1000)
irb(main):003:0> Book.create(name: 'Book2', publisher_id: 1, published: '2016-09-13', price: 2000)
irb(main):004:0> Book.create(name: 'Book3', publisher_id: 1, published: '2016-08-13', price: 3000)
irb(main):005:0> publisher.books
  Book Load (6.1ms)  SELECT `books`.* FROM `books` WHERE `books`.`publisher_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [
#<Book id: 1, name: "Book1", publisher_id: 1, published: "2016-10-13", price: 1000, created_at: "2016-11-13 15:12:37", updated_at: "2016-11-13 15:12:37">, 
#<Book id: 2, name: "Book2", publisher_id: 1, published: "2016-09-13", price: 2000, created_at: "2016-11-13 15:12:46", updated_at: "2016-11-13 15:12:46">],
#<Book id: 3, name: "Book3", publisher_id: 1, published: "2016-08-13", price: 3000, created_at: "2016-11-13 15:12:56", updated_at: "2016-11-13 15:12:56">]>

books (参照元テーブル) から publisher_id を外部キーにして publishers (参照先テーブル) を参照している関係を表すには Book モデルにbelongs_toメソッドを使用します。 引数にはモデル名(参照先の Publisher モデルはひとつなので単数形)を指定します。これは参照先 の publishers テーブルを取得するためのアクセサメソッドにもなります。

class Book < ApplicationRecord
  belongs_to :publisher
end

使用例は以下の通りです。

irb(main):001:0> book = Book.find(1)
  Book Load (0.4ms)  SELECT  `books`.* FROM `books` WHERE `books`.`id` = 1 LIMIT 1
irb(main):002:0> book.publisher
  Publisher Load (0.8ms)  SELECT  `publishers`.* FROM `publishers` WHERE `publishers`.`id` = 2 LIMIT 1
=> #<Publisher id: 1, name: "Gihyo inc.", address: "Ichigaya", created_at: "2016-11-13 08:44:10", updated_at: "2016-11-13 08:44:10">

1対1の関係

使用するテーブルの準備をします。

$ ./bin/rails g model Supplier name:string
$ ./bin/rails g model Account supplier:references account_number:string

「1対1」となる関係を表すにはhas_manyメソッドとbelongs_toメソッドを使用します。 引数にはモデル名(1対1なのでどちらのモデルも単数形)を指定してください。 外部キーを持つモデルがbelongs_toを使用することになりますが、モデルの主従関係を判断して従となるモデルに外部キーを持たせます。

class Supplier < ApplicationRecord
  has_one :account
end

class Account < ApplicationRecord
  belongs_to :supplier
end

使用例は以下の通りです。

irb(main):001:0> supplier = Supplier.create(name: 'Test')
  SQL (0.3ms)  INSERT INTO `suppliers` (`name`, `created_at`, `updated_at`) VALUES ('Test', '2016-11-23 09:02:06', '2016-11-23 09:02:06')
=> #<Supplier id: 1, name: "Test", created_at: "2016-11-23 09:02:06", updated_at: "2016-11-23 09:02:06">
irb(main):002:0> account = Account.create(supplier_id: 1, account_number: 'A001')
irb(main):003:0> supplier.reload
irb(main):004:0> supplier.account
  Account Load (0.4ms)  SELECT  `accounts`.* FROM `accounts` WHERE `accounts`.`supplier_id` = 1 LIMIT 1
=> #<Account id: 1, supplier_id: 1, account_number: "A001", created_at: "2016-11-23 09:04:35", updated_at: "2016-11-23 09:04:35">
irb(main):005:0> account.supplier
=> #<Supplier id: 1, name: "Test", created_at: "2016-11-23 09:02:06", updated_at: "2016-11-23 09:02:06">

 belongs_toとhas_oneのどちらを選ぶか | Rails ガイド

多対多の関係①

使用するテーブルの準備をします。

$ ./bin/rails g model author name:string email:string
$ ./bin/rails g model author_book author:references book:references

多対多の関係を表現するには中間テーブルを使用したhas_many throughの関連付けを使用します。authors テーブルと books テーブルが「多対多」の関係であるため、author_books テーブルを経由して両方のモデルを参照する必要があります。 まずは books と author_books、authors と author_books でそれぞれ「1対多」の関係を定義して、books と authors をthroughを使って author_books を中間テーブルとして関連付けます。

class Book < ApplicationRecord
  has_many :author_books
  has_many :authors, through: :author_books
end

class Author < ApplicationRecord
  has_many :author_books
  has_many :books, through: :author_books
end

中間テーブルの AuthorBook モデルにはbelongs_toメソッドを使用します。ただしrails g model author_book ...で作成されたモデルを見ると自動で設定されます。

class AuthorBook < ApplicationRecord
  belongs_to :author
  belongs_to :book
end

booksメソッドと<<を使用して関連するモデルの追加をすると中間テーブルの author_books が作成されます。また、booksメソッドを使用した時も中間テーブルの情報をキーにしてデータが取得されているのがわかりますね。

irb(main):001:0> author = Author.create(name: 'tasukujp', email: 'tasukujp@example.com')
=> #<Author id: 1, name: "tasukujp", email: "tasukujp@example.com", created_at: "2016-11-15 15:22:19", updated_at: "2016-11-15 15:22:19">

irb(main):002:0> author.books << Book.find(1)
  SQL (0.3ms)  INSERT INTO `author_books` (`author_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 1, '2016-11-23 06:16:07', '2016-11-23 06:16:07')
  Book Load (1.8ms)  SELECT `books`.* FROM `books` INNER JOIN `author_books` ON `books`.`id` = `author_books`.`book_id` WHERE `author_books`.`author_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [
#<Book id: 1, name: "Book1", publisher_id: 1, published: "2016-10-13", price: 1000, created_at: "2016-11-23 06:10:37", updated_at: "2016-11-23 06:10:37">]>

irb(main):003:0> author.books
=> #<ActiveRecord::Associations::CollectionProxy [
#<Book id: 1, name: "Book1", publisher_id: 1, published: "2016-10-13", price: 1000, created_at: "2016-11-23 06:10:37", updated_at: "2016-11-23 06:10:37">]>

irb(main):004:0> AuthorBook.find_by(author_id: 1)
  AuthorBook Load (0.4ms)  SELECT  `author_books`.* FROM `author_books` WHERE `author_books`.`author_id` = 1 LIMIT 1
=> #<AuthorBook id: 1, author_id: 1, book_id: 1, created_at: "2016-11-23 06:16:07", updated_at: "2016-11-23 06:16:07">

関連するモデルの追加ではなくて直接中間テーブルを作成するためにbuildメソッドを使用します。中間テーブルに別の属性を持たせてる場合などはこちらの方法が使えます。

irb(main):005:0> author_books = author.author_books.build
=> #<AuthorBook id: nil, author_id: 1, book_id: nil, created_at: nil, updated_at: nil>

irb(main):020:0> author.author_books
  AuthorBook Load (2.8ms)  SELECT `author_books`.* FROM `author_books` WHERE `author_books`.`author_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [
#<AuthorBook id: 1, author_id: 1, book_id: 1, created_at: "2016-11-15 15:33:33", updated_at: "2016-11-15 15:33:33">, 
#<AuthorBook id: nil, author_id: 1, book_id: nil, created_at: nil, updated_at: nil>]>

irb(main):006:0> author_books.book_id = 2
irb(main):007:0> author_books.save
   (0.5ms)  BEGIN
  Book Load (0.7ms)  SELECT  `books`.* FROM `books` WHERE `books`.`id` = 2 LIMIT 1
  SQL (0.6ms)  INSERT INTO `author_books` (`author_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 2, '2016-11-23 07:03:52', '2016-11-23 07:03:52')
   (4.6ms)  COMMIT
=> true

irb(main):026:0> author.author_books
=> #<ActiveRecord::Associations::CollectionProxy [
#<AuthorBook id: 1, author_id: 1, book_id: 1, created_at: "2016-11-15 15:33:33", updated_at: "2016-11-15 15:33:33">, 
#<AuthorBook id: 2, author_id: 1, book_id: 2, created_at: "2016-11-21 14:27:08", updated_at: "2016-11-21 14:27:08">]>

irb(main):009:0> author.books
  Book Load (0.4ms)  SELECT `books`.* FROM `books` INNER JOIN `author_books` ON `books`.`id` = `author_books`.`book_id` WHERE `author_books`.`author_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [
#<Book id: 1, name: "Book1", publisher_id: 1, published: "2016-10-13", price: 1000, created_at: "2016-11-15 15:08:37", updated_at: "2016-11-15 15:08:37">, 
#<Book id: 2, name: "Book2", publisher_id: 1, published: "2016-09-13", price: 2000, created_at: "2016-11-15 15:08:51", updated_at: "2016-11-15 15:08:51">]>

createメソッドを使用すると中間テーブルが生成と同時に保存されます。

irb(main):009:0> author_books = author.author_books.create(book_id: 3)
   (0.2ms)  BEGIN
  Book Load (0.3ms)  SELECT  `books`.* FROM `books` WHERE `books`.`id` = 3 LIMIT 1
  SQL (0.3ms)  INSERT INTO `author_books` (`author_id`, `book_id`, `created_at`, `updated_at`) VALUES (1, 3, '2016-11-23 07:06:58', '2016-11-23 07:06:58')
   (1.0ms)  COMMIT
=> #<AuthorBook id: 3, author_id: 1, book_id: 3, created_at: "2016-11-23 07:06:58", updated_at: "2016-11-23 07:06:58">

 has_many :throughとhas_and_belongs_to_manyのどちらを選ぶか | Rails ガイド

多対多の関係②

使用するテーブルの準備をします。

$ ./bin/rails g migration CreateJoinTableAuthorBook author book

has_many throughを指定した場合と異なり、authors_books という中間テーブルは必要ですがモデルとして作成する必要はありません。Author モデルと Book モデルに対してhas_and_belongs_to_manyメソッドのみを追加してください。

class Author < ApplicationRecord
  has_and_belongs_to_many :books
end

class Book < ApplicationRecord
  has_and_belongs_to_many :authors
end

使用例は以下の通りです。中間モデルに関係を表すメソッドは定義していませんが、has_many throughと同様の操作をしても中間テーブルの authors_books に関連が保存されているのがわかります。中間テーブルは便宜的なものとして操作するため関連付け以上の情報を加えることはできません。

irb(main):001:0> author = Author.create(name: 'tasukujp', email: 'tasukujp@example.com')
irb(main):002:0> author.books
  Book Load (0.4ms)  SELECT `books`.* FROM `books` INNER JOIN `authors_books` ON `books`.`id` = `authors_books`.`book_id` WHERE `authors_books`.`author_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy []>

irb(main):003:0> author.books << Book.find(2)
  SQL (11.3ms)  INSERT INTO `authors_books` (`author_id`, `book_id`) VALUES (1, 2)
irb(main):004:0> author.books << Book.find(3)
  SQL (0.4ms)  INSERT INTO `authors_books` (`author_id`, `book_id`) VALUES (1, 3)

irb(main):005:0> author.reload
irb(main):006:0> author.books
  Book Load (0.4ms)  SELECT `books`.* FROM `books` INNER JOIN `authors_books` ON `books`.`id` = `authors_books`.`book_id` WHERE `authors_books`.`author_id` = 1
=> #<ActiveRecord::Associations::CollectionProxy [
#<Book id: 2, name: "Book1", publisher_id: 1, published: "2016-10-13", price: 1000, created_at: "2016-11-23 10:00:45", updated_at: "2016-11-23 10:00:45">, 
#<Book id: 3, name: "Book2", publisher_id: 1, published: "2016-09-13", price: 2000, created_at: "2016-11-23 10:00:51", updated_at: "2016-11-23 10:00:51">]>