Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Rails 7.1: ActiveRecord::Baseにnormalizesが追加された(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

Rails 7.1: ActiveRecord::Basenormalizesが追加された(翻訳)

Rails 7.1で、Active Recordの属性値の正規化(normalization)を宣言するメソッドが新たに追加されました。これは、ユーザー入力のサニタイズ、書式の統一、外部由来のデータのクリーンアップで特に有用です。

Rails 7.1より前は、以下のようにbefore_saveコールバックで属性を正規化できます。

model User < ApplicationRecord
  before_save :downcase_email, if :email_present?

  private

    def email_present?
      email.present?
    end

    def downcase_email
      email.downcase!
    end
end

Rails 7.1では、同じコードを以下のような形にリファクタリングできます。

model User < ApplicationRecord
  normalizes :email, with: -> email { email.downcase }
end

この正規化は、属性への代入や属性の更新時に適用され、正規化済みの値はデータベースで永続化されます。この正規化は、finderメソッドで対応するキーワード引数にも適用されます。これにより、作成されたレコードについて、正規化されていない値を用いて後からクエリをかけられるようになります。

デフォルトでは、nil値に対して正規化は適用されません。nil値を正規化するには、以下のようにapply_to_nil:オプションでnilの正規化を有効にできます。

model User < ApplicationRecord
  normalizes :user_name, with:
    -> user_name { user_name.parameterize.underscore }

  normalizes :email, with: -> { _1.strip.downcase }

  normalizes :profile_image, with:
    -> profile_image {
      profile_image.present? ? URI.parse(profile_image).to_s :
      "https://source.boringavatars.com/beam" },
    apply_to_nil: true
end
# rails console
>> User.create!(user_name: "Eve Smith", email: "eveSmith@EXAMPLE.com")

#<User:0x000000010b757090 id: 1, user_name: "eve_smith", profile_image:"https://source.boringavatars.com/beam", email: "evesmith@example.com", created_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00, updated_at: Wed, 03 May 2023 07:49:20.067765000 UTC +00:00>

>> user = User.find_by!(email: "EVESMITH@example.COM")
>> user.email # => "evesmith@example.com"

>> User.exists?(email: "EveSmith@Example.COM")          # => true

ここでユーザーのメールアドレスが、正規化ステートメントがモデルに追加される前にデータベースに保存されていた場合、そのメールアドレスは正規化済みの形では取得されません。

その理由は、データベース内でメールの大文字小文字が混在している、つまり正規化されずに保存されているためです。そのようなレガシーデータがある場合は、Normalization#normalize_attributeメソッドを明示的に利用することで正規化できます。

# rails console
>> legacy_user = User.find(1)
>> legacy_user.email  # => "adamSmith@EXAMPLE.com"
>> legacy_user.normalize_attribute(:email)
>> legacy_user.email  # => "adamsmith@example.com"
>> legacy_user.save

詳しくは#43945をご覧ください。

関連記事

Rails 7.1: 複数ジョブを一度にエンキューするperform_all_laterが追加(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。