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

railsで毎回データベースに問い合わせる判定処理を書いてしまったとき、データベースにpluckで一覧データを作成しよう

rails

1つのSQLの発行をなくすと、5ms早くなります(自分の運営サイト)

それが一覧ページだと20回以上発行されていて、無駄に重くなっていたのですが、解決しました。

毎回1SQLが発行されると処理が重くなるのは、前回の記事を見て頂いたら、わかるかと思います。

まずはモデル関係の整理です。

User

class User < ActiveRecord::Base
  has_many :grumbles, dependent: :destroy
  has_many :cheers
end

Post

class Post < ActiveRecord::Base
  acts_as_taggable

  belongs_to :user
  has_many :cheers
end

Cheer

class Cheer < ActiveRecord::Base
  belongs_to :user
  belongs_to :grumble, counter_cache: true
end

では、どういう式かと言いますと、helperにこう書いてありました。

内容は、userが持っているpostに対して、応援をしているかどうかを判定している式です。

  def cheer_attrs(user, post)
    if !user
      'signin_required'
    elsif post.user == user
      'is-owner'
    elsif Cheer.where(user_id: user.id, post_id: post.id).exists? ←問題の箇所
      'on'
    else
      'off'
    end
  end

これの何が原因かと言いますと、判定処理に毎回SQLが発行されてしまいます。

これを一覧ページ(index)で出していたので、毎回SQLが発行されてしまい、無駄に重くなってしまいました。

最初にuserが持っているpostに対して、cheerをしたデータを作っておけば、そこにあるかの判定をするだけでよくなります。

そこで、userが持っているpostに対して、cheerをした一覧を作成します。

controller

private

def user_set_cheers
  @user_cheers = current_user.cheers.pluck(:post_id)  ←ここのpluckが重要
end

pluckは任意のカラムに対して、配列を作成してくれます。

つまり、最初にモデルから配列を作成しておけば、あとは配列があるかないかだけの判定になります。

これであとはページを開いたときに、一覧データを作成するようにすればいいですね。

helper

  def cheer_attrs(user, post)
    if !user
      'signin_required'
    elsif post.user == user
      'is-owner'
    elsif @user_cheers.include?(post.id)←  truefalseだけになる
      'on'
    else
      'off'
    end
  end

これで配列に含まれているかだけのtrue or falseのデータ処理に変わります。

最初に全体のsqlを発行するだけになります。

約1回分のsqlで、20回分のSQLがなくなります。

これを書くだけで約100ms(0.1秒)早くなりました。

SQLって重要ですね。