Ruby on Rails ツイートにユーザー情報を追加する。Active Recordによるアソシエーションとn + 1問題の解消

# ツイートにユーザー情報を追加する
### 作業内容

+ tweetsテーブルにカラムを追加する
+ ツイート保存時にユーザー情報を追加する処理を記述する
+ tweetsテーブルにカラムを追加
+ tweetsテーブルに```user_id```カラムを```integer```型で追加する。

```:ターミナル
$ rails g migration AddUserIdToTweets user_id:integer
# tweetsテーブルにuser_idカラムをinteger型で追加するマイグレーションファイルの作成

$ rake db:migrate
# マイグレーションファイルの実行
```
#1. ツイート保存時にユーザー情報を追加する処理をする
● ```current_user```
deviseでログイン機能を実装すると、```current_user```というヘルパーメソッドを使用することができる。これは、現在ログイン中のユーザーのレコードを、userクラスのインスタンスとして取得することができるメソッドである。
例を上げると```usersテーブル```での```id```カラムの値が```1```のアカウントでログインしている場合、```current_user```は```User.find(1)```と同じ意味を持つ。
この時、以下の例のように```current_user.usersテーブルのカラム名```とすることで、ログイン中のユーザーの情報として登録されている各カラムの値を取得することができる。

【例】

```
# current_userを利用して、ログインしているユーザーのデータを取得する
current_user.name
=> "nakamura"

current_user.email
=> "hoge@sample.com"
```
+ ```tweetsテーブルのuser_idカラムに保存すべきなのはcurrent_userのidカラムの値```になる。
+ ツイートを保存する際に、```image```,```text```というビューから送られてくる情報に加えて、```user_id```カラムにログイン中のユーザーのidを保存しなければならない。

```ruby:tweets_controller.rb
class TweetsController < ApplicationController

  before_action :move_to_index, except: :index

  def index
    @tweets = Tweet.page(params[:page]).per(5).order("created_at DESC")
  end

  def new
  end
  
  def create
    # tweet_paramsというストロングパラメーターを使用
    Tweet.create(image: tweet_params[:image], text: tweet_params[:text], user_id: current_user.id)

  private
  def tweet_params
    params.permit(:image, :text)
  end

  def move_to_index
    redirect_to action: :index unless user_signed_in?
  end
end
```
+ createアクションの__user_id: current_user.id__は現在ログイン中のユーザーの```id```を保存するための記述になる。

# 2. マイページの作成
+ マイページのルーティングを記述
+ コントローラとアクションを作成
+ マイページ用のビューファイルを作成

### routes.rbの編集
```ruby:routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'tweets#index"
  get  'tweets'      => 'tweets#index'
  get  'tweets/new'  => 'tweets#new'
  post 'tweets'      => 'tweets#create'
  get  'users/:id'   => 'users#show'
end
```
### コントローラーとアクションを作成
__●　whereメソッド__
```where```メソッドはActiveRecordメソッドのうちの一つであり、```モデル.where(条件)```のように引数部分に条件を指定することで、テーブル内の__条件に一致したレコードのインスタンスを配列型で取得__できる。
また、```where```メソッドを連続して記述することで複数の条件に一致したレコードも取得できる。
【例】

```:コンソール
[1] pry(main)> Tweet.where('id < 3')
=> [#<Tweet id: 1, image: "test1.jpg", text: "ナイス！", created_at: "2019-09-27 00:00:00", updated_at: "2019-09-27 00:00:00", user_id: 1>,＃<Tweet id: 2, image: "test2.jpg", text: "Thank you!", created_at: "2014-12-07 00:00:00", updated_at: "2014-12-07 00:00:00", user_id: 2>]
# idが3未満のtweetsテーブルのインスタンスを配列で取得

[2] pry(main)> Tweet.where('id < 3').where(user_id: 1)
=> [＃<Tweet id: 1, image: "test1.jpg", text: "いい景色だ。", created_at: "2014-12-06 00:00:00", updated_at: "2014-12-06 00:00:00", user_id: 1>]
# idが3未満かつuser_idが1のtweetsテーブルのインスタンスを配列で取得
```

### users_controllerの作成
```:ターミナル
$ rails g controller users
```
### usersコントローラーのshowアクションにマイページに表示したい情報を定義
```ruby:users_controller.rb
class UsersController < ApplicationController
  def show
    # 現在ログインしているユーザーのニックネーム
    @nickname = current_user.nickname
    # 現在ログインしているユーザーが投稿したツイート
    @tweets = Tweet.where(user_id: current_user.id).page(params[:page]).per(5).order("created_at DESC")
  end
end
```
### マイページ用のビューファイルを作成
```ruby:/users/show.html.erb
   #省略
   <header class="header">
    <div class="header__bar row">
      <h1 class="grid-6"><a href="/">Tweet</a></h1>
      <% if user_signed_in? %>
        <div class="user_nav grid-6">
          <span><%= current_user.nickname %>
            <ul class="user__info">
              <li>
                <a href="/users/<%= current_user.id %>">マイページ</a>
                <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
              </li>
            </ul>
          </span>
          <a class="post" href="/tweets/new">投稿する</a>
        </div>
      <% else %>
        <div class="grid-6">
          <%= link_to "ログイン", new_user_session_path, :class => 'post' %>
          <%= link_to "新規登録", new_user_registration_path, :class => 'post' %>
        </div>
      <% end %>
    </div>
  </header>
   #省略
```
次回からはaタグの記述はヘルパーを使うようにする。
# アソシエーションの利用
+ アソシエーションの定義
+ アソシエーションの実装

### アソシエーションを定義する
+ モデルクラスには__has_many__や__belogns_to__などの定義がされている。
+ 所属する側のテーブルに__所属するクラス名_id__というカラムがある。(```user_id```というカラムをtweetsテーブルに追加してあるため）

### userの作成したtweetが複数個(```has_many :tweets```)
```ruby:/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :validatable
  has_many :tweets
end
```
### tweetはいずれかのuserに属する(```belongs_to :user```)
```ruby:/models/tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user
end
```
以上でアソシエーションの定義をしたことになる。
# アソシエーションを実装
【例】 アソシエーションをしない場合

```
$ rails c
[1] pry(main)> user = User.find(1)
[2] pry(main)> Tweet.where(user_id: user.id)
=> [#<Tweet id: 1, image: "http://photo1.jpg", text: "ナイス！", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>,
  #<Tweet id: 1, image: "http://photo2.jpg", text: "beautiful!", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]
```
【例】 アソシエーションを使用

```
$ rails c
[1] pry(main)> user = User.find(1)
[2] pry(main)> user.tweets
=> [#<Tweet id: 1, image: "http://photo1.jpg", text: "ナイス！", created_at: "2014-12-06 09:00:00", updated_at: "2014-12-06 09:00:00", user_id: 1>,
  #<Tweet id: 1, image: "http://photo2.jpg", text: "beautiful!", created_at: "2014-12-06 10:00:00", updated_at: "2014-12-06 10:00:00", user_id: 1>]
```
### アソシエーションを利用した```@tweets```を定義する
```ruby:users_controller
class UsersController < ApplicationController
  def show
    @nickname = current_user.nickname
    @tweets = current_user.tweets.page(params[:page]).per(5).order("created_at DESC")
  end
end
```
上記のように__current_user.tweets__と続ければ、__現在ログインしているユーザーの投稿したツイート全て__を取得できる。
# ツイートからユーザー情報を先読み
### ● n+1問題
+ ```n+1問題```とは、データを呼び出す際に大量のSQLが発行されてしまう問題のことである。例にあげると
tweetsのindexアクションで全ツイートを取得する一回に加えて、アソシエーションを利用してツイートの数だけユーザー情報をその度に呼び出してしまう。
よって、その状態だとツイート数+１回SQLが発行されてしまう。この状態のことを```n+1```問題と言う。

### ● includesメソッド
+ ```n+1問題```は__includes__メソッドを使うことによって解消することができる。指定された関連モデルをまとめて取得することで、SQLの発行回数を減らすことができる。
+ 記述の仕方は__inludes(:モデル名)__のように引数で、関連モデルをシンボル型で指定する。

### includesメソッドを追記
```ruby:tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
  end
  #以下省略
end
```
# まとめ
久しぶりに上記関係の復習をやってみましたが以前はなんのこっちゃ？みたいな部分があったのですがゆっくり読み返したことによって理解できたのではないかと思っています。
やはりこうしたアウトプットは重要だと痛感しました。
説明不足で端折ってる部分もあるかと思いますが、自分の備忘録としてこの記事を残させていただきます。