LoginSignup
0
7

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-09-29

ツイートにユーザー情報を追加する

作業内容

  • 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_userUser.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を保存しなければならない。
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の編集

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アクションにマイページに表示したい情報を定義

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

マイページ用のビューファイルを作成

/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_manybelogns_toなどの定義がされている。
  • 所属する側のテーブルに所属するクラス名_idというカラムがある。(user_idというカラムをtweetsテーブルに追加してあるため)

userの作成したtweetが複数個(has_many :tweets)

/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :validatable
  has_many :tweets
end

tweetはいずれかのuserに属する(belongs_to :user)

/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を定義する

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アクションで全ツイートを取得する一回に加えて、アソシエーションを利用してツイートの数だけユーザー情報をその度に呼び出してしまう。 よって、その状態だとツイート数+1回SQLが発行されてしまう。この状態のことをn+1問題と言う。

● includesメソッド

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

includesメソッドを追記

tweets_controller.rb
class TweetsController < ApplicationController

  def index
    @tweets = Tweet.includes(:user).page(params[:page]).per(5).order("created_at DESC")
  end
  #以下省略
end

まとめ

久しぶりに上記関係の復習をやってみましたが以前はなんのこっちゃ?みたいな部分があったのですがゆっくり読み返したことによって理解できたのではないかと思っています。
やはりこうしたアウトプットは重要だと痛感しました。
説明不足で端折ってる部分もあるかと思いますが、自分の備忘録としてこの記事を残させていただきます。

0
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
7