アソシエーションをマスターしよう ~データベース設計達人への道~ [Rails]

#**はじめに**
開発が進むにしたがって、最初のテーブル間の設計ミスに苦悶する瞬間に出くわします。ロジックとモデルは密接に繋がっているので、是非ともモデルについての理解を（アソシエーションを含め）深めることにより、開発力を高めたいと思うこの頃です。

##**1:1アソシエーション**

###**has_one**

```user.rb
class User < ApplicationRecord
  has_one :author
end
```

```author.rb
class Author < ApplicationRecord
  belongs_to :user
end
```
このようなリレーションがあった場合、一つのUserオブジェクトに対して最大一つのAuthorオブジェクトが存在することになります。

ですので、下記のように条件に合致したレコードをコントローラで取得した場合、テンプレートファイルでの使用例は次のようになります。

```assoc_controller.rb
def hasone
  @user = User.find_by(name: 'takuyanin')
end
```

```assoc/hasone.html.erb
.
.
<% unless @user.author.nil? %>
  <%= @user.author.name %>
  .
  .
<% end %>
.
```
has_oneアソシエーションの場合、AuthorオブジェクトはUserオブジェクトに必ず所属しておりますが、UserオブジェクトはAuthorオブジェクトを持っているとは限りません。
そのため、`nil?`メソッドで、存在を確認する必要があります。

アソシエーションを利用しない場合は下記のようなコードとなり、直感的に理解しにくくなります。

```assoc_controller.rb
def hasone
  @user = find_by(name: 'takuyanin')
  @author = Author.find_by(user_id: @user.id)
end
```

```assoc/hasone.html.erb
<% unless @author.nil? %>
<% end %>
```

##**m:n(多対多)アソシエーション**

###**1. has_and_belongs_to_many**
中間テーブルを作成してモデル間を関連づけます。

```book.rb
class Book < ApplicationRecord
  has_and_belongs_to_many :authors
end
```

```author.rb
class Author < ApplicationRecord
  has_and_belongs_to_many :books
end
```
中間デーブルの名前は、参照先テーブル名を**アルファベット順に「_」で連結したもの**である必要があるため、今回の場合は、`authors_books`テーブルを作成し、`author_id`と`book_id`カラムを持たせます。関連名は、いずれも複数形として指定します。また、多対多の関係では、中間テーブルは外部キー以外のカラムを持っていないため、モデルを作成する必要はありません。

```assoc_controller.rb
def has_and_belongs_to_many
  @book = Book.find_by(title: 'プロを目指す人のためのRuby入門')
end
```

```assoc/has_and_belongs_to_many.html.erb
<% @book.authors.each do |author| %>
  <%= author.name %>
<% end %>
```

###**2. has_many through**
こちらも多対多の関係を表現しますが、先ほどの`has_and_belongs_to_many`アソシエーションと違って、　**中間テーブルに外部キー（関連付け情報）以外にも情報を持たせることが可能**です。

モデル間の関係は以下の通りです。

```book.rb
class Book < ApplicationRecord
  has_many :reviews
  has_many :users, through: :reviews
end
```

```review.rb
class Review < ApplicationRecord
  belongs_to :books
  belongs_to :users
end
```

```user.rb
class User < ApplicationRecord
  has_many :reviews
  has_many :books, through: :reviews
end
```
上記のように関連付けされた中間テーブル`reviews`は、外部キー以外の情報を持つことができます。
また、`has_many　through`アソシエーションにより、`books/users`テーブルを、この中間テーブルを介して関連づけています。

```assoc_controller.rb
def has_many_through
 @user = User.find_by(name: 'takuyanin')
end
```

```assoc/has_many_through.html.erb
<% unless @user.books.empty? %>
  <% @user.books.each do |book| %>
    <%= book.title %>
  <% end %>
<% end %>
```
これは、ユーザーがレビューを書いたことのある書籍を取得しています。
`empty`メソッドでUserモデルに関連付いたBookモデルが存在することを確認した上で、Bookオブジェクトのプロパティにアクセスしています。

また、ここでのポイントは、`empty?`メソッドを利用する点です。
先ほどは`nil?`メソッドを利用してましたがここでは利用できません。理由は、**`nil?`メソッドは、下記のように配列の中身があってもなくても`false`が返るから**です。

```
pry(main)> [1, "文字列だよ"].nil?
=> false
pry(main)> [].nil?
=> false

pry(main)> [1, "文字列だよ"].empty?
=> false
pry(main)> [].empty?
=> true
```

###**m:nの関係(has_many/ has_and_belongs_to_many)で追加されるメソッド**
上記のアソシエーションを定義することによって、追加されるメソッドで僕がよく利用するものについていくつか載せておきます。

####**<<**
関連するモデルを追加します。

```
[1] pry(main)> @book = Book.find(3)
=> #<Book:0x00007f8c4dccad90 id: 3, title: "プログラミング言語Ruby", price: 4104, created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00, updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[2] pry(main)> @review = Review.find(5)
=> #<Review:0x00007f8c4ae0ba40 id: 5, text: "e", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>

[3] pry(main)> @book.reviews << @review
=> [#<Review:0x00007f8c4db9a6a0 id: 1, text: "a", book_id: 3, user_id: nil, created_at: nil, updated_at: nil>,
.
.,
 #<Review:0x00007f8c4db5a618 id: 26, text: "z", book_id: 3, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4ae0ba40 id: 5, text: "e", book_id: 3, user_id: nil, created_at: nil, updated_at: Sun, 23 Dec 2018 16:44:53 UTC +00:00>]

[4] pry(main)> @review
=> #<Review:0x00007f8c4ae0ba40 id: 5, text: "e", book_id: 3, user_id: nil, created_at: nil, updated_at: Sun, 23 Dec 2018 16:44:53 UTC +00:00>

```
[1]で、idが3のBookオブジェクトを取得し、
[2]では、idが5のReviewオブジェクトを取得しています。book_idは2となっております。
[3]では、`<<`メソッドを利用することによって、book_idが2であるReviewオブジェクトをbook_idが3である（Bookモデルに関連づいた）Reviewオブジェクトのまとまりに追加しています。
[4]では、Reviewオブジェクトを確認しています。book_idが2から3に変更されているのが確認できます。

####**destroy**
関連づいたモデルを削除

```
[1] pry(main)> @review = Review.find(3)
=> #<Review:0x00007f8c4dac62b0 id: 3, text: "c", book_id: 1, user_id: nil, created_at: nil, updated_at: nil>

[2] pry(main)> @book = Book.find(1)
=> #<Book:0x00007f8c4d900a48 id: 1, title: "3ステップでしっかり学ぶRuby入門", price: 2570, created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00, updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[3] pry(main)> @book.reviews
=> [#<Review:0x00007f8c49fe3788 id: 3, text: "c", book_id: 1, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4b105598 id: 24, text: "x", book_id: 1, user_id: nil, created_at: nil, updated_at: nil>]

[4] pry(main)> @book.reviews.destroy(@review)
=> [#<Review:0x00007f8c4dac62b0 id: 3, text: "c", book_id: 1, user_id: nil, created_at: nil, updated_at: nil>]

[5] pry(main)> @book.reviews
=> [#<Review:0x00007f8c4b105598 id: 24, text: "x", book_id: 1, user_id: nil, created_at: nil, updated_at: nil>]
```

[4]では[1]で取得したReviewオブジェクトを`@book`に紐づいたReviewオブジェクト群から削除しています。

####**review_ids**
このメソッドは利用モデルによって名前は適宜変更していただく必要がありますが、
このメソッドにより**関連モデルのid値を配列として取得**できます。

```
[1] pry(main)> @book = Book.find(2)
=> #<Book:0x00007f8c49ed9748
 id: 2,
 title: "プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで",
 price: 3218,
 created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00,
 updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[2] pry(main)> @review_ids = @book.review_ids
=> [6, 12, 16]
```
開発をする中で、id値のみを取得したい場面に多々遭遇しました。
こちらのメソッドは割と重宝しますね。

####**clear**
関連モデルを破棄します。

```
[1] pry(main)> @book = Book.find(2)
=> #<Book:0x00007f8c4a9fb7e8
 id: 2,
 title: "プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで",
 price: 3218,
 created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00,
 updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[2] pry(main)> @book.reviews
=> [#<Review:0x00007f8c49fef510 id: 6, text: "f", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4d852560 id: 12, text: "l", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4d852290 id: 16, text: "p", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>]

[3] pry(main)> @book.reviews.clear
  Review Update All (3.2ms)  UPDATE `reviews` SET `reviews`.`book_id` = NULL WHERE `reviews`.`book_id` = 2
=> []

[4] pry(main)> @book.reviews
=> []
```
[3]では、関連づいたReviewオブジェクト群のbook_idがNULLにセットされています。
そのため、[4]では`@book`に関連づいたReviewオブジェクトがなくなっているのが確認しています。

####**empty?**
こちらは先ほど出てきました。
**関連するモデルが存在するか確認できます。**

```
[5] pry(main)> @book.reviews.empty?
=> true
```
上記は、先ほどの続きに対して、`empty?`メソッドを適用しています。

####**size**
関連づいたモデルの数を取得します。

```
[6] pry(main)> @book.reviews.size
=> 0
```

####**exists?**
関連モデル群のなかに特定のモデルが存在するかを確認します。

```
[7] pry(main)> @book
=> #<Book:0x00007f8c4d81b510
 id: 2,
 title: "プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで",
 price: 3218,
 created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00,
 updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[8] pry(main)> @book.reviews
=> [#<Review:0x00007f8c4db7f7d8 id: 6, text: "f", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4db7eef0 id: 12, text: "l", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4db7ea68 id: 16, text: "p", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>,
 #<Review:0x00007f8c4acd7228 id: 27, text: "great", book_id: 2, user_id: nil, created_at: Sun, 23 Dec 2018 17:38:33 UTC +00:00, updated_at: Sun, 23 Dec 2018 17:38:33 UTC +00:00>,
 #<Review:0x00007f8c4ac31670 id: nil, text: "good", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>]

[9] pry(main)> @book.reviews.exists?(12)
  Review Exists (0.8ms)  SELECT  1 AS one FROM `reviews` WHERE `reviews`.`book_id` = 2 AND `reviews`.`id` = 12 LIMIT 1
=> true

[10] pry(main)> @book.reviews.exists?(5)
  Review Exists (16.7ms)  SELECT  1 AS one FROM `reviews` WHERE `reviews`.`book_id` = 2 AND `reviews`.`id` = 5 LIMIT 1
=> false
```

####**build, create**
これらのメソッドは、関連先のモデルを新規に生成しますが、
違いは、**buildは保存はせず、createは保存を行う点です。**

```
[1] pry(main)> @book = Book.find(2)
=> #<Book:0x00007f8c4d81b510
 id: 2,
 title: "プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで",
 price: 3218,
 created_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00,
 updated_at: Wed, 12 Dec 2018 10:38:40 UTC +00:00>

[2] pry(main)> @book.reviews.size
=> 3

[3] pry(main)> @book.reviews.build(text: 'good')
=> #<Review:0x00007f8c4ac31670 id: nil, text: "good", book_id: 2, user_id: nil, created_at: nil, updated_at: nil>

[4] pry(main)> @book.reviews.create(text: 'great')
=> #<Review:0x00007f8c4acd7228 id: 27, text: "great", book_id: 2, user_id: nil, created_at: Sun, 23 Dec 2018 17:38:33 UTC +00:00, updated_at: Sun, 23 Dec 2018 17:38:33 UTC +00:00>
```
注目すべき点は、[3]（buildメソッド）では、保存を行わないため、created_at, updated_atカラムには値が挿入されずnilが登録されている点です。
対照的に、[4]（createメソッド）では、保存を行うため、created_at, updated_atカラムには、時刻が登録されています。

##**ポリモーフィック関連**
ポリモーフィック関連に関しては下記記事にまとめています。

[ポリモーフィック関連を定義する設定をまとめてみた[Rails]](https://qiita.com/takuyanin/items/d74de3b7d7807b9e4284)


#**おわりに**
アソシエーションに結びついたメソッドをそつなく使えるようになることで、直感的に理解できるコードを書けるように精進したいと思います。


少しでも役に立ったという方は、いいね、よろしくお願いします(^^)

また、[takuyaninのマイページ](https://qiita.com/takuyanin)にRails, Rubyに関していくつか記事をまとめてますので、よければご参考ください。