モデルのリレーションについてです。
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">]>