Ruby

新しいItemの作成

Items

最新コメント

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

Rails 8.1におけるRails.configuration.action_view.remove_hidden_field_autocomplete

config.action_view.remove_hidden_field_autocompleteは、hiddenフィールドからautocomplete="off"属性を除去するかどうかの設定です。 (この機能のPRこの機能のCommitbin/rails app:updateコマンドが生成する config/initializers/new_framework_defaults_8_1.rbには、 以下のようにこの設定を有効にするコードがあります。

+  # Highlight code that triggered redirect in logs.
+  config.action_dispatch.verbose_redirect_logs = true
+

この機能が導入された背景

この機能は、HTML標準へ より準拠したHTMLをRailsが生成するように追加されました。

具体的には、この機能を有効にしない場合、Railsは以下のようなautocomplete="off"属性を付けたhiddenフィールドを生成していました。

<input type="hidden" name="authenticity_token" value="abc123..." autocomplete="off">

このautocomplete属性付きのhiddenフィールドはHTML標準に沿っておらず、 Nu Html Checkerは 以下のメッセージとともに「エラー」として指摘していました。

An “input” element with a “type” attribute whose value is “hidden” must not have an “autocomplete” attribute whose value is “on” or “off”.

0
Raw
https://www.techtips.page/ja/comments/1120

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

Rails 8.1におけるconfig.action_dispatch.verbose_redirect_logs

config.action_dispatch.verbose_redirect_logsは、リダイレクトのソース位置を関連するログ行の下にログ出力するかどうかの設定です。

rails newで生成されるconfig/environments/development.rbにおいて、 8.1からは以下のようにこの設定を有効にするコードが追加されました(この変更のPRこの変更のCommit)。

+  # Highlight code that triggered redirect in logs.
+  config.action_dispatch.verbose_redirect_logs = true
+

development環境でデバッグする際に使える情報が増える形になると思いますので、既存アプリでもこの設定を追加しても良いかもしれません。

0
Raw
https://www.techtips.page/ja/comments/1119

Rails 8.0からRails 8.1への移行(アップデート、アップグレード)で必要な作業

wakairo @wakairo

基本的にはRailsガイドの手順に従えば良いと思います。

ガイドの手順にもありますが、ぜひbin/rails app:updateコマンドを活用しましょう。

Rails 8.1への移行で対応が必要そうな個別の作業について、以下のコメントでそれぞれ取り上げますので、ご参考になれば幸いです。

0
Raw
https://www.techtips.page/ja/comments/1118

Active Recordのgenerates_token_for:DBカラム不要で一時的なトークンを扱う機能

wakairo @wakairo

generates_token_forとは

generates_token_forは、特定の目的を持つトークンを生成し、そのトークンからレコードを検索・検証するための機能です。 Rails 7.1で標準機能として導入されました。

特長

  • DBへのデータ保存不要:トークンを保存するためのデータベースのカラムやテーブルを追加せずに利用できます。
  • 目的別に分離:目的(purpose)ごとにトークンを分離できるため、別目的で生成されたトークンを使い回すことができません。
  • 失効条件を設定可能:有効期限とモデルのデータ変化による失効条件を柔軟に設定できます。
  • 改ざん耐性:署名付きのトークンのため、改ざん耐性があります。

使いどころ:一時的に有効なURLの送付

以下のような「一時的で目的が限定されたトークン付きURL」を送付する場面に適しています。

  • メールアドレス確認リンク
  • 招待リンク
  • マジックリンク(パスワードなしログイン)
  • 一時アクセスリンク

なお、APIの認証トークンのような永続的な用途には向きません。 Railsで永続的なトークンを扱いたい場合は、has_secure_tokenなどの利用を検討してください。

注意:パスワードリセットには専用APIあり

Rails 8.0以降でhas_secure_passwordを利用している場合、パスワードリセット用トークンを扱う専用APIが自動的に提供されます。そのため、パスワードリセットの用途に限っては、自分でgenerates_token_forを定義する必要はありません。(詳しくは後述の補足を参照してください)

使い方(メールアドレス確認の例)

ここでは「メールアドレス変更時の確認リンク」を題材に、基本的な使い方を説明します。

基本の流れ(定義・生成・検証)

まず、モデルにgenerates_token_forを用いて、トークンの用途名(ここでは:email_verification)を定義します。 なお、用途名が異なっていれば複数定義することも可能です。

class User < ApplicationRecord
  generates_token_for :email_verification
end

トークンを生成する際は、対象のインスタンスに対してgenerate_token_forを呼び出します。

注意:モデルで定義する際は複数形のgenerates_token_forですが、 インスタンスから呼び出す際は単数形のgenerate_token_forになる点に注意してください。

user = User.first # 対象のレコードを取得(ここでは`first`で代替)
token = user.generate_token_for(:email_verification)
# => "BAhJIi..." (生成されたトークン文字列)

生成したトークンは、例えば以下のようにURLパラメータに載せてメールなどで送付するのが典型的な流れです。

# 例:メイラー等でのURL生成
email_verifications_url(token: token)
# => "https://example.com/email_verifications?token=BAhJIi..."

受け取ったトークンからレコードを検索・検証するには、クラスメソッドのfind_by_token_forを使います。 有効なトークンならUserインスタンスが返り、無効なトークンや期限切れのトークンの場合はnilが返ります。

user = User.find_by_token_for(:email_verification, token)

有効期限による失効

定義時にexpires_inオプションを付与することで、トークンに有効期限(例:15分間)を設けることができます。

class User < ApplicationRecord
  generates_token_for :email_verification, expires_in: 15.minutes
end

モデルのデータ変化による失効

generates_token_forは、「一度状態が変わったら、古いトークンを使わせない」挙動を簡単に実装できます。 例えば、新しいメールアドレス確認用に生成されたトークンを、メールアドレスの変更が完了した後に無効化すれば、万が一流出しても悪用を防げます。

無効化の仕組みは「定義時のブロックの戻り値をトークンに埋め込み、検証時にその値が変わっていれば無効とみなす」というものです。

class User < ApplicationRecord
  generates_token_for :email_verification, expires_in: 15.minutes do
    # トークン生成時と検証時で`email`の値が異なれば(=メールアドレスが変更完了していれば)、
    # 有効期限内であっても無効になる
    email
  end
end
注意:ブロックの戻り値に機密情報を含めない

ブロック内で返した値は、改ざん耐性はあるものの、暗号化されるわけではなく、デコードすれば読める形でトークンに埋め込まれます。 そのため、ブロックの戻り値には機密情報を含めてはなりません。

補足:パスワードリセット専用API

Rails 8.0以降では、モデルでhas_secure_passwordを利用している場合、パスワードリセットトークン用の以下のメソッドがデフォルトで追加されます。

  • password_reset_token:トークン生成(インスタンスメソッド)
  • find_by_password_reset_token:トークンからの検索と検証(クラスメソッド)
  • find_by_password_reset_token!:同上(無効時に例外を発生させるバージョン)

Rails 8.1で追加されたトークン有効期限の機能

さらにRails 8.1以降では、トークンの有効期限を柔軟に扱うための機能が追加されています。

has_secure_passwordの呼び出し時にreset_tokenオプションを通して有効期限を変更できるようになりました。

class User < ApplicationRecord
  has_secure_password reset_token: { expires_in: 1.hour }
end

また、以下のメソッドが追加されました。

  • password_reset_token_expires_in:設定されている有効期限の取得(インスタンスメソッド)

参考情報

0
Raw
https://www.techtips.page/ja/comments/1117

Railsモデルのクラスメソッドはメソッドチェーンでも呼べる ― 注意点とscopeへ整理する方針

wakairo @wakairo

Railsでモデルにクラスメソッドを定義すると、メソッドの内容に関わらず、メソッドチェーン(ActiveRecord::Relation)経由でもそのメソッドを呼び出せます。 これが便利な場合がある一方で、エラーにはならないものの、意味的に不正確なコードが書けてしまったり、気を付けないとバグの温床になったりすることもあります。 特に、リレーションを返す用途のクラスメソッドと、そうでないクラスメソッドが混在している場合に混乱が生じやすくなります。 本記事ではその仕組みと注意点、そして実務上の整理方針を解説します。

モデルのクラスメソッドがActiveRecord::Relation経由でも呼べる仕組み

以下のようにPenクラスにクラスメソッドを定義したとします。

class Pen < ApplicationRecord
  def self.with_color(color)
    if color.present?
      where(color: color)
    else
      all
    end
  end
end

定義したこのメソッドは以下のように呼べます。これはRubyにおける通常のクラスメソッド呼び出しです。

test(dev):000> Pen.with_color("red")
  Pen Load (0.1ms)  SELECT "pens".* FROM "pens" WHERE "pens"."color" = 'red' /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x00007b3619af2060
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">,
 #<Pen:0x00007b361a437f80
  id: 3,
  color: "red",
  price: 150,
  created_at: "2026-02-20 05:53:10.752331000 +0000",
  updated_at: "2026-02-20 05:53:10.752331000 +0000">]

では、メソッドチェーンでの呼び出しはどうなっているでしょうか。

まず、whereなどのスコープメソッドを呼び出した結果は、以下で確認できる通り、 PenクラスではなくPen::ActiveRecord_Relationのインスタンスになります。

test(dev):000> Pen.where(created_at: 1.year.ago..).class
=> Pen::ActiveRecord_Relation

このリレーションインスタンスに対しても、クラスメソッドを以下のように呼び出せます。 つまり、モデルに定義したクラスメソッドは、そのモデルに対応したActiveRecord::Relationクラスのインスタンスからも呼べる仕組みになっているのです。この仕組みが、クラスメソッドをメソッドチェーンから呼べるようにしています。

test(dev):000> Pen.where(created_at: 1.year.ago..).with_color("red").count
  Pen Count (0.5ms)  SELECT COUNT(*) FROM "pens" WHERE "pens"."created_at" >= '2025-02-21 07:00:00.454690' AND "pens"."color" = 'red' 
=> 2

メソッドチェーンで呼べてしまうからこそ、モデルのクラスメソッドは責務と名前を明確にする

with_colorのようにクエリを組み立てるメソッドであれば、メソッドチェーンで呼び出せても違和感がないと思います。 しかし、データベースクエリと関係のないメソッドでもクラスメソッドであれば同様にリレーション経由で呼び出せてしまいます。

その結果、クラスメソッドであれば、その処理内容に関わらず、クエリを組み立てるメソッドを間に挟んだメソッドチェーンにおいて、エラーにならず実行できてしまいます。 具体例を見てみましょう。export_to_csvがクラスメソッドだとすると以下のようにwhereを間に挟んでもエラーにならずに実行できます。

Pen.where(created_at: 1.year.ago..).export_to_csv(1, 2)

ではexport_to_csvメソッドは、countのように「条件に合ったレコードに対する処理をするメソッド」なのでしょうか? それとも、クエリ構築とは関係がない「ユーティリティメソッド」で、このメソッドチェーンで呼び出すコードは何かしらのミスの結果なのでしょうか?

この例から分かるのは、モデルクラスに定義するクラスメソッドの名前はより慎重に付けなくてはならないということです。 名前だけではありません。メソッドの責務についても、クエリ用途であるか否かなど、明確さがあることが望まれます。

そのため、モデルのクラスメソッドの責務と名前に一定の規則性を持たせることも1つのアイデアです。 例えば、「findcreateなど、Active Recordのクエリメソッドで使われる単語から名前が始まる場合は、必ずクエリ関連の責務とする」といったルールが考えられます。

また、名前だけでなくコードドキュメントにおいても、他のメソッド以上に使い方をはっきりと示すことが望まれます。

クエリ構築用クラスメソッドでelse節を省くとメソッドチェーンの途中でエラーを招く

クエリ構築用のクラスメソッドの場合には、条件分岐のelse節を省いてしまうと、メソッドチェーンの途中で予期せぬエラーを引き起こす原因になるという注意点もあります。

以下のように、先ほどのwith_colorメソッドからelse節を削除してみましょう。

class Pen < ApplicationRecord
  def self.with_color(color)
    if color.present?
      where(color: color)
    end
  end
end

引数が有効値のとき(color.present?が真になるとき)はエラーになりませんが、 以下のように引数が空文字列で条件を満たさない場合はNoMethodErrorが発生してしまいます。

test(dev):000> Pen.where(created_at: 1.year.ago..).with_color("").count
(test):0:in '<main>': undefined method 'count' for nil (NoMethodError)

これは、以下のように条件を満たさなかった場合の戻り値がnilになってしまうためです。 nilに対してcountメソッドを呼び出そうとしたため、エラーになったのです。

test(dev):000> Pen.where(created_at: 1.year.ago..).with_color("")
=> nil

クエリ構築用メソッドとしてクラスメソッドを設計するなら、 常にActiveRecord::Relationインスタンス(またはそれに準ずるオブジェクト)を返す設計にしておくことが求められます。

リレーションを返す場合はscopeに統一する

クエリ構築の一環としてリレーションを返すことを意図したメソッドは、 クラスメソッドではなくscopeでの定義に統一するのが有効です。 これは、前述したモデルのクラスメソッドに関する困難さを和らげる、シンプルなアプローチの1つです。

1つのプロジェクト内でクラスメソッドでの定義とscopeでの定義を混在させることは、 前述のクラスメソッドの注意点とscopeの注意点を足し合わせることになり、チーム開発における理解コストを上げてしまいます。 では、混在をやめてどちらに統一するのが良いかと言えば、scopeに統一することが有力な戦略です。以下にその理由を3つ述べます。

理由1: scopeとクラスメソッドでは挙動に違いがある

まず大きな違いは、nilまたはfalseを返したときの挙動です(この点については理由3で詳しく後述します)。 また、レシーバとしてモデル名を書いた際にも明確な挙動の違いが確認できます(下記「参考」を参照)。 これらに限らず、他の挙動の差異が潜んでいる可能性もあります。

このように仕様や挙動に差異がある場合、二通りのやり方が混在していると「片方で問題ない書き方がもう片方でバグになる」という厄介さを抱えることになります。また、両方の仕様・挙動に対する知識が求められるため、コードベースの理解コストも上がってしまいます。

(参考)モデルクラス名を明示的にレシーバとした場合の挙動の違い

クエリ構築を行うscopeとクラスメソッドでは、モデルクラス名を明示的にレシーバとして書いてしまうと、 前の条件を引き継ぐか否かに違いが出てしまいます。

具体的には、まず以下のように、Pen.where(color: color)とだけ記述したscopeとクラスメソッドをそれぞれ定義します。

class Pen < ApplicationRecord
  scope :with_color_s, ->(color) { Pen.where(color: color) }

  def self.with_color_m(color)
    Pen.where(color: color)
  end
end

次に、それぞれを実行してみると、以下のような違いが出ます。

test(dev):000> Pen.where(price: 80).with_color_m("red")
  Pen Load (0.2ms)  SELECT "pens".* FROM "pens" WHERE "pens"."price" = 80 AND "pens"."color" = 'red' /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x00007ad76b175648
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">]
test(dev):000> Pen.where(price: 80).with_color_s("red")
  Pen Load (0.2ms)  SELECT "pens".* FROM "pens" WHERE "pens"."color" = 'red' /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x00007ad76ae02d08
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">,
 #<Pen:0x00007ad76ae02bc8
  id: 3,
  color: "red",
  price: 150,
  created_at: "2026-02-20 05:53:10.752331000 +0000",
  updated_at: "2026-02-20 05:53:10.752331000 +0000">]

クラスメソッドのwith_color_mは前の条件where(price: 80)が適切にマージされているのに対し、 scopeのwith_color_sprice: 80の条件が無視されています(where(color: color)のようにレシーバPenを消せば正しくマージされます)。

なお、この違いの確認には執筆時の最新版であるRails 8.1.2を用いました。

理由2: scopeには「リレーションを返す」という共通認識がある

クラスメソッドは、用途が極めて広い汎用的な仕組みです。そのため、メソッド名だけでは「チェーン可能なクエリメソッドなのか」が判断しづらくなります。

一方、scopeは「クエリを組み立ててActiveRecord::Relationを返すためのもの」という開発者間の共通認識があります。 また、scopeは通常のクラスメソッド定義方法とは異なるため、メソッドチェーンで利用できることも自然に受け入れられます。

以上から、scopeの方が、コードの読み書きに必要な認知的負荷が低いと言えます。

理由3: scopeでは、else節を省いた簡潔な記載が可能

RailsのAPIドキュメントによれば、 nilまたはfalseが返された場合にその時点までに構築済みのクエリ条件(allに相当)を返すというのがscopeの仕様です。

そのため、以下のscope定義にはelse節がありませんが、メソッドチェーンの途中で呼び出されてもエラーを引き起こしません。

class Pen < ApplicationRecord
  scope :with_color, ->(color) do
    if color.present?
      where(color: color)
    end
  end
end

これにより、先ほど述べたクラスメソッドが持つ「nilを返してチェーンが壊れる」問題を回避しつつ、条件付きクエリを簡潔に表現できます。

さらにRubyでは、全く同じ処理を以下の一行に書き直せます。

class Pen < ApplicationRecord
  scope :with_color, ->(color) { where(color: color) if color.present? }
end

このようにscopeを使うことで、意図が明確かつ簡潔なコードを書くことが出来ます。

まとめ

  • クエリ用途ではないクラスメソッドを定義するときには、メソッドチェーンで呼び出されることが不自然であると伝わるように、責務や名前の明確さが重要です。
  • リレーションを返すメソッドをscopeでの定義に統一することで、nil返却エラーを防いだり、コードの読み書きを容易にしたり出来ます。
0
Raw
https://www.techtips.page/ja/comments/1115

Active Recordのallが持つ条件を引き継ぐ性質と動的クエリ構築への応用

wakairo @wakairo

RailsのActive Recordのallメソッドは、 その名前から「テーブルに存在する全レコードを取得するメソッド」と理解されがちです。 しかし実際には「その時点までに積み上げられた条件に該当するものを全て取得する」という実務上とても重要な性質を持っています。

本記事では、このallの性質の基本と、動的にデータベースクエリを組み立てる際の実践的な応用テクニックについて解説します。

allは「構築済みの条件」を引き継ぐ

データベースにPenモデルのレコードが3件保存されているとします。 以下のようにPenクラスに対して直接allを呼び出すと、テーブルに存在する全てのレコードが取得されます。

test(dev):000> Pen.all
  Pen Load (1.2ms)  SELECT "pens".* FROM "pens" /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x00007cdf641ba6f0
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">,
 #<Pen:0x00007cdf66b23208
  id: 2,
  color: "blue",
  price: 80,
  created_at: "2026-02-20 05:53:03.457025000 +0000",
  updated_at: "2026-02-20 05:53:03.457025000 +0000">,
 #<Pen:0x00007cdf66b230c8
  id: 3,
  color: "red",
  price: 150,
  created_at: "2026-02-20 05:53:10.752331000 +0000",
  updated_at: "2026-02-20 05:53:10.752331000 +0000">]

では、別の条件に続けてallを呼び出すとどうなるでしょうか。 以下の実行例を見ると分かるように、allは「全て」ではなく、「それまでの条件を満たすもの全て」を返します。 ここではcolor: "red"を満たすレコードだけが取得されています。

test(dev):000> Pen.where(color: "red").all
  Pen Load (0.1ms)  SELECT "pens".* FROM "pens" WHERE "pens"."color" = 'red' /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x00007cdf66b20648
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">,
 #<Pen:0x00007cdf66b20508
  id: 3,
  color: "red",
  price: 150,
  created_at: "2026-02-20 05:53:10.752331000 +0000",
  updated_at: "2026-02-20 05:53:10.752331000 +0000">]

このように、allは単に全件取得する機能だけでなく、 手前に条件がある場合には「そこまでに構築されたクエリ条件(Relationオブジェクト)を引き継ぎ、そのまま返す」という機能を担う側面があります。

応用例:動的クエリ構築においてallを起点にしてメソッドチェーン可能にする

この「そこまでに構築された条件を引き継ぐ」というallの性質は、 「URLのクエリパラメータが存在する場合だけ条件を追加する」といった動的なデータベースクエリ構築において役立ちます。

例えば、クエリパラメータを処理するfilter_byというスコープを定義することを考えてみましょう。 このスコープは、以下のように「1年以内に作成されたレコード」という前提条件(where)のあとにメソッドチェーンで呼び出される想定です。

test(dev):000> Pen.where(created_at: 1.year.ago..).filter_by(color: "red", max_price: 100)
  Pen Load (0.4ms)  SELECT "pens".* FROM "pens" WHERE "pens"."created_at" >= '2025-02-21 05:50:22.982591' AND "pens"."color" = 'red' AND "pens"."price" <= 100 /* loading for pp */ LIMIT 11 
=>
[#<Pen:0x000072d2c03393d8
  id: 1,
  color: "red",
  price: 80,
  created_at: "2026-02-20 05:52:53.460900000 +0000",
  updated_at: "2026-02-20 05:52:53.460900000 +0000">]

この挙動を実現するfilter_byスコープの実装は以下のようになります。 最初にallを呼び出して現在のRelation(既に構築済みのwhere条件など)を変数pensに格納し、 そのpensに対してパラメータの有無をチェックしながら条件を継ぎ足しています。

class Pen < ApplicationRecord
  scope :filter_by, ->(params) do
    pens = all
    pens = pens.where(color: params[:color]) if params[:color].present?
    pens = pens.where(price: ..params[:max_price]) if params[:max_price].present?
    pens
  end
end

ここで重要になるのが、filter_byの中で最初にallを呼んで変数に入れている点です。
allは「何も条件がない状態」ではなく、 「このスコープが呼ばれた時点のRelationをそのまま受け取る」役割を果たしています。 そのため、Pen.where(created_at: …)のように手前で条件を付けていても、 それをリセットすることなくさらに条件を積み上げられます。 つまり、allを変数に入れることで、 手前の条件を維持した上でさらに条件を積み上げていくための「起点」を作ることができるのです。 なお、クエリパラメータが全て空で条件追加がない場合でも、allが返した元のRelationがpensに入っているため、 メソッドチェーンをそのまま継続できます。

(参考)個別スコープへの切り出しとそのタイミング

先ほどの例ではfilter_byの内部で変数に再代入しながら条件を追加していましたが、 もしcolorpriceによる絞り込みをアプリケーション内の他の場所で単独で利用したい場合は、 それぞれを個別のスコープとして切り出すことでコードをよりキレイに書くことができます。

Active Recordのスコープは、ブロックの評価結果がnilまたはfalseになった場合、 自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。 そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。

class Pen < ApplicationRecord
  scope :with_color, ->(color) { where(color: color) if color.present? }
  scope :with_max_price, ->(max_price) { where(price: ..max_price) if max_price.present? }
  scope :filter_by, ->(params) do
    with_color(params[:color]).with_max_price(params[:max_price])
  end
end

なお、YAGNI原則の観点からは、 これらの個別スコープ(with_colorwith_max_price)が他の場所で「本当に必要になるまで」は、 無理に切り出さない方が良いでしょう。 つまり、最初から再利用性を過剰に意識してスコープを量産するよりは、 まずは前述のpens = allを使うアプローチで1箇所にまとめておき、 個別スコープが本当に必要になったタイミングで切り出す方が、 結果的には開発コストを減らせるはずです。

0
Raw
https://www.techtips.page/ja/comments/1114

RailsのAction MailerとAction Mailboxはmail gemを利用している

wakairo @wakairo

RailsでMailクラスに遭遇したら、それはmail gemのものです。ですので、そのAPI情報などは、Railsの情報源ではなく、以下のようなmail gemの情報源を見ると得られます。

0
Raw
https://www.techtips.page/ja/comments/1109

Active Recordマイグレーション:t.referencesとadd_referenceで外部キー制約を詳細設定する方法

wakairo @wakairo

要点

Active Recordのマイグレーションファイルにてt.referencesまたはadd_referenceを使う際、 foreign_key:オプションに対してtrueの代わりに ハッシュを渡すことで外部キー制約の詳細を設定 できます。

このハッシュで指定できるオプションはadd_foreign_keyのAPIドキュメントに記載されているオプションです。

具体例

t.references を使う場合(新規テーブル作成時)

create_table :books do |t|
  t.references :author, foreign_key: { on_delete: :cascade }
end

add_reference を使う場合(既存テーブルへの追加)

add_reference :books, :author, foreign_key: { on_delete: :cascade }

説明

Active Recordのマイグレーションファイルにてテーブル間に関連付けをする際には、 t.referencesまたはadd_referenceを使うと便利です。

この2つのメソッドは、呼び出すときにforeign_key:オプションを付けると、 外部キー制約も同時に付与することが出来ます。例えば、foreign_key: trueという記述をよく見かけます。

foreign_key: trueは外部キー制約をかけることだけの指定となりますが、 ここでのtrueの代わりにハッシュを渡すことで、外部キー制約の詳細が設定可能になります。

このハッシュの中に入れられるオプション、つまり外部キー制約の詳細設定項目は、add_foreign_keyに渡せるオプションと同じです。 このadd_foreign_keyに渡せるオプションの具体的な種類とそれぞれの意味については、 add_foreign_keyのAPIドキュメントをご覧ください。

0
Raw
https://www.techtips.page/ja/comments/1108

Active Recordマイグレーションでのadd_foreign_keyとadd_referenceの違い

wakairo @wakairo

Railsガイドを一読しただけでは、 add_foreign_keyとadd_referenceが、それぞれどのようなもので、どう違うのかがいまいちよく分かりませんでした。 そこで、add_foreign_keyとadd_referenceによってdb/schema.rbがどのように変化するかを基に、 それぞれの機能について確認してみました。

add_foreign_key

add_foreign_keyは外部キー制約の追加だけを行います。よって、カラムやインデックスの追加は行いません。

外部キー制約とは、子テーブルが参照している親のIDが親テーブルに存在することをデータベースレベルで保証する制約です。

例えば、以下の記述をマイグレーションファイルに行ったとします。

add_foreign_key :products, :users

すると以下の記述がdb/schema.rbに追加されます。

add_foreign_key "products", "users"

この記述は、products(子テーブル)のuser_idカラムに登場するIDが、users(親テーブル)のidカラムに必ず存在するように制約をかけています。

add_reference

add_referenceは、テーブル間の関連付けに関する複数の設定を一度に行える便利メソッドです。

add_referenceの基本機能は、カラムとインデックスの追加です。 例えば、以下の記述をマイグレーションファイルに行ったとします。

add_reference :products, :user

すると以下のように、users(親テーブル)に関連付ける2つの記述、具体的にはuser_idのカラムとインデックスをproducts(子テーブル)に追加する2つの記述が、db/schema.rbに追加されます。

t.integer "user_id"
t.index ["user_id"], name: "index_products_on_user_id"

またadd_referenceは、foreign_key:trueオプションを追加することで、前述の2つに加えて外部キー制約も同時に設定できます。 例えば、以下の記述をマイグレーションファイルに行ったとします。

add_reference :products, :user, foreign_key:true

すると以下のように、前述の2つの記述に加えて、add_foreign_keyの記述がdb/schema.rbに追加されます。

t.integer "user_id"
t.index ["user_id"], name: "index_products_on_user_id"
add_foreign_key "products", "users"

(参考)add_referenceとt.referencesの機能は基本的に同じ

add_referenceとcreate_tableのブロックの中で呼び出すt.referencesは基本的に同じ機能を提供します(オプション以外の引数の部分で違いはありますが。) 実際にt.referencesへ渡せるオプションはadd_referenceと同じです。 また、t.referencesの実装add_referenceの実装のどちらもReferenceDefinition.newを内部で呼ぶ形になっています(v8.1.2で確認)。

0
Raw
https://www.techtips.page/ja/comments/1107

ActiveRecord::Rollbackで例外を伝播させずにロールバック後の処理を継続する

wakairo @wakairo

Active RecordのTransactionブロック内で例外が投げられた場合、ActiveRecord::Rollback以外の例外はロールバックの後に再度投げられます。

したがって、処理失敗で例外を投げるメソッド(save!等)を使えば、基本的にその例外はロールバックの後Transactionブロックの外へ伝播します。 これをキャッチしなければ、RailsはHTTPのエラーレスポンスをブラウザ等のクライアントに返します。 つまり、「処理に失敗したら、ロールバックして、あとの処理は切り上げて、エラーレスポンスを返す」という挙動は簡単に実装できます。

一方で、「ロールバック後に例外を出さずに処理を継続する」場合には、ActiveRecord::Rollbackを活用できます。 以下は、Transactionの成否でリダイレクト先を変えるコード例です。

completed = false

ActiveRecord::Base.transaction do
  unless obj1.save && obj2.save
    raise ActiveRecord::Rollback
  end
  completed = true
end

if completed
  redirect_to completed_path
else
  redirect_to uncompleted_path
end
0
Raw
https://www.techtips.page/ja/comments/1106