🚂

Rails6〜7で追加された便利メソッド12選

2022/10/11に公開
2

はじめに

こんにちは。 mybest でBackendエンジニアをしている rince です。

現在、弊社ではRails6.1から7.0へのアップグレードを進めています。
→ 2023/3/2にRails7.0にアップグレード完了しました!

アップグレードを進める中で新たに追加された便利なメソッドを使用する機会があったので、今回はそんなRailsの最新便利メソッドをまとめました。

また、Rails7.0だけでなく、6.0や6.1で追加されたメソッドの中にもまだ割と知られていない便利なメソッドがあったりするので、それらについても合わせてご紹介します。

よりシンプルにわかりやすくコードを書けるメソッドがたくさん追加されていますので、ぜひ読んでみていただいて、皆さんの開発の手助けになれば嬉しいです。

ActiveRecord

destroy_by / delete_by (6.0〜)

特定条件のレコードを一括で削除する場合、これまでは wheredestroy_alldelete_all を組み合わせる必要がありましたが、Rails6.0で destroy_by , delete_by が追加されたことで短く書けるようになりました。

Before

User.where(name: 'David').destroy_all
User.where(name: 'David').delete_all

After

User.destroy_by(name: 'David')
User.delete_by(name: 'David')

insert_all / upsert_all (6.0〜)

レコードを一括登録する場合、これまでは activerecord-import というgemを使う方が多かったかと思いますが、Rails6.0で insert_all , upsert_all が追加されたため、Rails標準の機能でレコードの一括登録が行えるようになりました。

Book.insert_all([
  { title: "Rework", author: "David", created_at: Time.current, updated_at: Time.current },
  { title: "Eloquent Ruby", author: "Russ", created_at: Time.current, updated_at: Time.current }
 ])

また、Rails6.1までは created_atupdated_at を明示的に指定する必要があったのですが、Rails7.0からは自動でタイムスタンプが追加されるようになり、より便利になりました。
(※正確にはモデルの record_timestamps の設定に従います)

# Rails7からは自動でタイムスタンプが追加されるため、created_atやupdated_atは不要
Book.insert_all([
  { title: "Rework", author: "David" },
  { title: "Eloquent Ruby", author: "Russ" }
 ])

enum negative scope (6.0〜)

Rails6.0から enum を宣言した際に、否定のスコープも自動で追加されるようになりました。
特定の値でない場合のレコードを取得するケースは結構あるので地味に嬉しい機能です。

class Post < ActiveRecord::Base
  enum status: [:drafted, :active, :trashed]
end

Post.not_drafted # => where.not(status: :drafted)
Post.not_active  # => where.not(status: :active)
Post.not_trashed # => where.not(status: :trashed)

pick (6.0〜)

Rails6.0から絞り込みや並べ替えを指定した際に、最初のレコードの特定のカラムの値を取得するためのメソッドである ActiveRecord::Relation#pick が追加されました。
今まで同様の処理をするにはパフォーマンスを考えると limit , pluck , first の3つを組み合わせる必要がありましたが、 pick だけで簡潔に書けるようになりました。

Before

Person.where(id: 1).limit(1).pluck(:name).first

After

Person.where(id: 1).pick(:name)
# SELECT people.name FROM people WHERE id = 1 LIMIT 1
# => 'David'

# 複数カラムも指定可能
Person.where(id: 1).pick(:name, :email_address)
# SELECT people.name, people.email_address FROM people WHERE id = 1 LIMIT 1
# => [ 'David', 'david@loudthinking.com' ]

extract_associated (6.0〜)

Rails6.0でリレーションから関連レコードを抽出する extract_associated が追加されました。
これまでも preloadcollect を使えば同じことができましたが、よりシンプルで意図がわかるように書けるようになりました。

Before

category.posts.preload(:author).collect(&:author)

After

category.posts.extract_associated(:author)
# => Returns collection of Author records

where.missing (6.1〜)

Rails6.1で関連先が存在しない孤立したレコードを取得できる ActiveRecord::Relation#missing というクエリメソッドが追加されました。

例えば、著者の紐付いていない投稿を探したい場合に以下のように簡単に書けるようになります。

Before

Post.left_joins(:author).where(authors: { id: nil })

After

Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL

where.associated (7.0〜)

Rails7.0で ActiveRecord::Relation#associated というクエリメソッドが追加されました。
これは先ほど紹介した ActiveRecord::Relation#missing の逆のパターンで、関連先の存在するレコードのみを取得できます。

例えば、著者の紐付いている投稿のみを探したい場合に以下のように簡単に書けるようになります。

Before

Post.joins(:author).where.not(authors: { id: nil })

After

Post.where.associated(:author)
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NOT NULL

in_order_of (7.0〜)

Rails7.0でレコードを任意の順序で取得できる in_order_of というメソッドが追加されました。
これまでMySQLではFIELD関数を使えば同様のことは実現可能でしたが、PostgreSQLだとまた書き方が違ったりしてツラみがあったので、使用するDBによらずシンプルに書けるようになって嬉しいです。

例えば、KVSから人気ランキングの記事IDをランキング順に取得してその順でDBからレコードを取得したいといった場合などに使えます。

Article.in_order_of(:id, [1, 5, 3])
# SELECT "articles".* FROM "articles"
# ORDER BY FIELD("articles"."id", 1, 5, 3)
# WHERE "articles"."id" IN (1, 5, 3)

その他

他にもActiveRecordには、Rails6.1でN+1クエリを防ぐための strict_loading 、Rails7.0でwhere句の条件を反転する invert_where などのメソッドも追加されていますが、まだ実運用では使いづらかったり、使い方によっては危険だったりするため、今回は紹介を省きました。

strict_loading はN+1クエリにならないケースでも関連を取得する際に preloadincludes が強制されてしまうので、現時点では全体適用して bullet のgemを置き換えるのは厳しいんじゃないかなと思っています。Rails7.0で strict_loading! に対して追加された n_plus_one_only モードが全体に対して設定できるようになると実用的になる気がします。

invert_where の危険性については以下の記事で述べられていて、個人的にはよほど有用なケースを除いては使用しない方がいいんじゃないかなと思います。

https://pocke.hatenablog.com/entry/2021/04/28/233930

ActiveModel

ComparisonValidator (7.0〜)

バリデーションで2つの値を比較する場合、これまでは自分でバリデーションのロジックを実装する必要がありましたが、Rails7.0でComparisonValidatorが追加され、Railsのデフォルトのバリデーションで検証できるようになりました。

Before

class Event < ApplicationRecord
  validate :end_date_is_after_start_date

  private

  def end_date_is_after_start_date
    if end_date < start_date
      errors.add(:end_date, 'cannot be before the start date')
    end
  end
end

After

class Event < ApplicationRecord
  validates :end_date, comparison: { greater_than: :start_date }
end

ActiveSupport

compact_blank (6.1〜)

配列やハッシュから空のオブジェクトを取り除く場合、これまでは rejectblank? を組み合わせる必要がありましたが、Rails6.1からは compact_blank を使ってシンプルに書けるようになりました。

Before

# Array
[1, "", nil, 2, " ", [], {}, false, true].reject(&:blank?)
# =>  [1, 2, true]

# Hash
{ a: "", b: 1, c: nil, d: [], e: false, f: true }.reject { |_k, v| v.blank? }
# => { b: 1, f: true }

After

# Array
[1, "", nil, 2, " ", [], {}, false, true].compact_blank
# =>  [1, 2, true]

# Hash
{ a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
# => { b: 1, f: true }

まとめ

Rails6〜7にかけて追加された便利メソッドを紹介してきましたがいかがでしたでしょうか。
意外と知らないメソッドや知っててもまだ使ったことのないメソッドもあったのではないでしょうか?

よりシンプルにわかりやすくコードを書けるメソッドがいろいろと追加されているので、ぜひ最新のRailsにアップグレードして便利メソッドの恩恵を受けていきましょう!

この他にももし便利なメソッドがあれば教えてください。


株式会社マイベストでは一緒にサービスを盛り上げてくれるメンバーを募集しています!

ご興味を持った方はぜひコーポレートサイトをご覧いただけると嬉しいです。
https://my-best.com/company

また、カジュアル面談も募集していますので、Rails、GraphQL、海外展開、プロダクトについてなど気軽にお話ししましょう!
https://youtrust.jp/recruitment_posts/d873ac9e14bc2bd8c18b78f6af6b02d9

Discussion

sontixyousontixyou

compact_blankは便利かつ分かりやすくて、お気に入りメソッドです!

mybestmybest

コメントありがとうございます!
compact_blank 便利ですよね!
in_order_ofextract_associated なんかも重宝しています!