大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【無職に転生 ~ 就職するまで毎日ブログ出す_19】【Rails】たまに使えるクエリメソッド

はじめに

こんにちは、大ちゃんの駆け出し技術ブログです。

タイトルにあるとおり【無職に転生 ~ 就職するまで毎日ブログ出す】というチャレンジをしています!!!!昨日までは就活するまで本気出すでしたが、これだとまるで就活後は頑張らないのかと思われてしまいそうで、、、大人気アニメのタイトルのまるパクリチャレンジです。

https://i.gyazo.com/3a02b7aae4e5e538130d4bb199b55dc7.png

RailsやらRubyやらSQLやらその他Webの知識やらが色々と抜け落ちているのを感じており、知識の定着のためにもアウトプットする機会を増やすためです。加えて、退職して文字通り無職に転生しましてプロニートになり、毎日時間に余裕ができたので引き締めるためにも毎日投稿を思い至りました!

【投稿内容】

  • SQLの難しい処理 (副問合せ、JOINとか複雑な処理が書けない)
  • Rails全般 (純粋に必要な知識が多すぎる、網羅的な理解が足りない)
  • Rubyのあまり使わないメソッドや記述方法 (あまり重要ではないけど特に)
  • Web知識全般 (クッキーやら、セッションやらなんとなくで理解しているものの自分の言葉で説明できない)
  • 書籍 (スタートアップ企業に勤めるので、自分が会社に与える影響やパフォーマンスを高めるためビジネス書を読んでいきます。)

本日やること

本日はRailsのActive Recordにおいて頻繁には使用しないけどケースによっては使うことがあるメソッドを紹介したいと思います。RailsガイドのActive Recorddのメソッドには本当にたくさんのメソッドがありますが、よく使うメソッドはせいぜい10個程度でしょうか。しかし、他のも様々な便利なメソッドがあるので、使い方を理解しておきましょう。

Active Record クエリインターフェイス - Railsガイド

group

取得メソッドにGROUP BY句を追加したい場合に使います。つまり項目が同じであるレコードをまとめます。

例えば下記のように難易度のクラスが定義されていたとします。enumで難易度が3段階で定義されています。

class Difficulty < ApplicationRecord
  belongs_to :user
  belongs_to :hoge
  enum level: {easy: 0, middle: 1, hard: 2}
end
Difficulty.levels
=> {"easy"=>0, "middle"=>1, "hard"=>2}

この時、難易度ごとにグループに分けて取得したい場合、groupメソッドで引数にグループ化する項目を指定することでGROUP BY句を使うことができます。

Difficulty.group(:level)
  Difficulty Load (1.3ms)  SELECT `difficulties`.* FROM `difficulties` GROUP BY `difficulties`.`level`
(Object doesn't support #inspect)
=>

しかし返り値は返ってきていないことがわかります。これはGROUP BYはSQLでは集計関数と併用することが必須条件であるためです。そのため、集計関数を使うことで取得できます。例えば集計関数COUNTを呼び出すcountを使用すれば以下のように出力することができます。

Difficulty.group(:level).count
   (0.5ms)  SELECT COUNT(*) AS count_all, `difficulties`.`level` AS difficulties_level FROM `difficulties` GROUP BY `difficulties`.`level`
=> {"easy"=>60, "middle"=>58, "hard"=>66}

GROUPごとにキーが難易度、バリューがその難易度の投票数を格納したハッシュが返却されます。


よく使われる方法として集計した中から最大のバリューのキーを取得したい場合があります。上記でいうと難易度が"hard"を取得したいと思います。その場合、下記のようにHashクラスのインスタンスメソッドであるmaxを使うことで配列として出力することができます。

Difficulty.group(:level).count.max { |x, y| x[1] <=> y[1] }
   (1.2ms)  SELECT COUNT(*) AS count_all, `difficulties`.`level` AS difficulties_level FROM `difficulties` GROUP BY `difficulties`.`level`
=> ["hard", 66]

注意点としてgroupを使う場合はActiveRecordの便利なメソッドを使うことができなくなります。これは、groupの返却値はハッシュオブジェクトであるためです。

=> {"easy"=>60, "middle"=>58, "hard"=>66}

Active Recordは基本的にレシーバーがDBと連携したモデルの値がある方がチェーンメソッドをつなげていくことができるため便利ですが、groupの場合はクエリメソッドのチェーンが途切れてしまうので注意しましょう。

having

GROUP BY句で取得した結果に対してさらに制約を加えるHAVING句を使うことができるメソッドです。groupメソッドにチェーンすることで使用することができます。

Difficulty.group(:level).having("count(*) > ?", 59).count
   (1.0ms)  SELECT COUNT(*) AS count_all, `difficulties`.`level` AS difficulties_level FROM `difficulties` GROUP BY `difficulties`.`level` HAVING (count(*) > 59)
=> {"easy"=>60, "hard"=>66}

order

ORDER BY句を使うことができるメソッドです。こちらの利用頻度はgroupに比べればたくさんあるかなと思います。よくある使われ方としてはscopeと組み合わせて使われる方法です。

class Post < ApplicationRecord
    scope :recent, -> { order(:created_at).first(5) }
  # Ex:- scope :active, -> {where(:active => true)}
end

Post.recent # => 最近作成された投稿を取得

逆に古いものを取得するにはorderの第二引数に降順を表すシンボル:descを指定します。

class Post < ApplicationRecord
    scope :recent, -> { order(:created_at).first(5) }
  scope :old, -> { order(:created_at, :desc).first(5) }
  # Ex:- scope :active, -> {where(:active => true)}
end

Post.old # => 最初あたりに作成された投稿を取得

複数のフィールドを指定して並べることもできます。

Post.**order**(updated: :asc, created_at: :desc)

limit

上から順にいくつ取得するかを指定します。

class Post < ApplicationRecord
    scope :recent, -> { order(:created_at).limit(5) }
  # Ex:- scope :active, -> {where(:active => true)}
end

firstでも同じ方法で取得できました。

offset

limitはひきすうをつかえあfirstと差別させる方法としてoffsetメソッドがあります。これはlimitと併用し、上から何行飛ばしてからlimitでデータを取得するかを指します。例えば、11番目から取得したい場合は10個飛ばすことで取得できます。

User.order(:created_at).limit(5).offset(10)
  User Load (2.9ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`created_at` ASC LIMIT 5 OFFSET 10

pluck

カラムを引数として指定して指定したカラムの値を配列として取得することができるメソッドです。

User.pluck(:name)
   (0.5ms)  SELECT `users`.`name` FROM `users`
[
 "Kaido",
 "Ussop",
 "Bellamy",
 ]

こちらも返り値が配列であるためクエリメソッドをつなげて呼び出すことができなくなります。そのため、whereなどで先に制約を加えておいて特定の配列を返すようにすることができるようになります。