Rails6〜7で追加された便利メソッド12選
はじめに
こんにちは。 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〜)
特定条件のレコードを一括で削除する場合、これまでは where
と destroy_all
や delete_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_at
と updated_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
が追加されました。
これまでも preload
と collect
を使えば同じことができましたが、よりシンプルで意図がわかるように書けるようになりました。
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クエリにならないケースでも関連を取得する際に preload
や includes
が強制されてしまうので、現時点では全体適用して bullet のgemを置き換えるのは厳しいんじゃないかなと思っています。Rails7.0で strict_loading! に対して追加された n_plus_one_only
モードが全体に対して設定できるようになると実用的になる気がします。
invert_where
の危険性については以下の記事で述べられていて、個人的にはよほど有用なケースを除いては使用しない方がいいんじゃないかなと思います。
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〜)
配列やハッシュから空のオブジェクトを取り除く場合、これまでは reject
と blank?
を組み合わせる必要がありましたが、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にアップグレードして便利メソッドの恩恵を受けていきましょう!
この他にももし便利なメソッドがあれば教えてください。
株式会社マイベストでは一緒にサービスを盛り上げてくれるメンバーを募集しています!
ご興味を持った方はぜひコーポレートサイトをご覧いただけると嬉しいです。
また、カジュアル面談も募集していますので、Rails、GraphQL、海外展開、プロダクトについてなど気軽にお話ししましょう!
株式会社マイベストのテックブログです! 採用情報はこちら > notion.so/mybestcom/mybest-information-for-Engineers-8beadd9c91ef4dc2b21171d48a4b0c49
Discussion
compact_blankは便利かつ分かりやすくて、お気に入りメソッドです!
コメントありがとうございます!
compact_blank
便利ですよね!in_order_of
やextract_associated
なんかも重宝しています!