Deprecation Warning [import]: Sass @import rules are deprecated and will be removed in Dart Sass 3.0.0.
More info and automated migrator: https://sass-lang.com/d/import
╷
1 │ @import "bootstrap/mixins/banner";
│ ^^^^^^^^^^^^^^^^^^^^^^^^^
╵
_bootstrap.scss 1:9 @use
app/assets/stylesheets/application.scss 1:1 root stylesheet
Active Recordのスコープは、ブロックの評価結果がnilまたはfalseになった場合、
自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。
そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 32547
# Running:
/x/vendor/bundle/ruby/3.4.0/gems/railties-8.0.4/lib/rails/test_unit/line_filtering.rb:7:in 'run': wrong number of arguments (given 3, expected 1..2) (ArgumentError)
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:467:in 'block (2 levels) in Minitest::Runnable.run_suite'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:463:in 'Array#each'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:463:in 'block in Minitest::Runnable.run_suite'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:505:in 'Minitest::Runnable.on_signal'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:492:in 'Minitest::Runnable.with_info_handler'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:462:in 'Minitest::Runnable.run_suite'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:355:in 'block in Minitest.run_all_suites'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:355:in 'Array#map'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:355:in 'Minitest.run_all_suites'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:310:in 'Minitest.run'
from /x/vendor/bundle/ruby/3.4.0/gems/minitest-6.0.0/lib/minitest.rb:84:in 'block in Minitest.autorun'
Bootstrap 5に由来するDart Sassの非推奨警告を抑制する:Railsアプリ(dartsass-rails)を例に
問題:Bootstrap 5に由来する大量の非推奨警告
Bootstrapは、 Sassの仕様変更に対して長期的に取り組みを進めている 最中であるため、Dart Sassでコンパイルすると以下のような非推奨(Deprecation)警告が大量に出力されます。
この大量の警告にまぎれてしまい、自分で書いたコードに対する警告を見つけにくくなるという問題があります。 その結果、自分のコードに警告が出ていないことを確認するだけでも、手間と時間がかかってしまいます。
なお、2024年にリリースされたDart Sass 1.80.0以降、
@importなどの非推奨警告がより積極的に出力されるようになり、この問題がさらに目立つようになりました。解決策:
--quiet-depsで依存ライブラリへの非推奨警告を抑制するこの問題の解決策は、
sassコマンドを実行する際に--quiet-depsオプションを付与することです。 これにより、Bootstrapを含む依存ライブラリ(厳密には、ロードパス経由でインポートされたコード)に対する非推奨警告が出力されなくなります。 なお、ロードパス経由でない自分のコードに対する警告は引き続き出力されます。Railsアプリ(dartsass-rails)で
--quiet-depsを付与する方法dartsass-rails gemを利用するRailsアプリでは、
config/initializers/dartsass.rbを開いて(なければ新規作成して)、 以下のコードを記述します。参考情報
Railsモデルのクラスメソッドはメソッドチェーンでも呼べる ― 注意点とscopeへ整理する方針
Railsでモデルにクラスメソッドを定義すると、メソッドの内容に関わらず、メソッドチェーン(ActiveRecord::Relation)経由でもそのメソッドを呼び出せます。 これが便利な場合がある一方で、エラーにはならないものの、意味的に不正確なコードが書けてしまったり、気を付けないとバグの温床になったりすることもあります。 特に、リレーションを返す用途のクラスメソッドと、そうでないクラスメソッドが混在している場合に混乱が生じやすくなります。 本記事ではその仕組みと注意点、そして実務上の整理方針を解説します。
モデルのクラスメソッドがActiveRecord::Relation経由でも呼べる仕組み
以下のようにPenクラスにクラスメソッドを定義したとします。
定義したこのメソッドは以下のように呼べます。これはRubyにおける通常のクラスメソッド呼び出しです。
では、メソッドチェーンでの呼び出しはどうなっているでしょうか。
まず、
whereなどのスコープメソッドを呼び出した結果は、以下で確認できる通り、 PenクラスではなくPen::ActiveRecord_Relationのインスタンスになります。このリレーションインスタンスに対しても、クラスメソッドを以下のように呼び出せます。 つまり、モデルに定義したクラスメソッドは、そのモデルに対応したActiveRecord::Relationクラスのインスタンスからも呼べる仕組みになっているのです。この仕組みが、クラスメソッドをメソッドチェーンから呼べるようにしています。
メソッドチェーンで呼べてしまうからこそ、モデルのクラスメソッドは責務と名前を明確にする
with_colorのようにクエリを組み立てるメソッドであれば、メソッドチェーンで呼び出せても違和感がないと思います。 しかし、データベースクエリと関係のないメソッドでもクラスメソッドであれば同様にリレーション経由で呼び出せてしまいます。その結果、クラスメソッドであれば、その処理内容に関わらず、クエリを組み立てるメソッドを間に挟んだメソッドチェーンにおいて、エラーにならず実行できてしまいます。 具体例を見てみましょう。
export_to_csvがクラスメソッドだとすると以下のようにwhereを間に挟んでもエラーにならずに実行できます。では
export_to_csvメソッドは、countのように「条件に合ったレコードに対する処理をするメソッド」なのでしょうか? それとも、クエリ構築とは関係がない「ユーティリティメソッド」で、このメソッドチェーンで呼び出すコードは何かしらのミスの結果なのでしょうか?この例から分かるのは、モデルクラスに定義するクラスメソッドの名前はより慎重に付けなくてはならないということです。 名前だけではありません。メソッドの責務についても、クエリ用途であるか否かなど、明確さがあることが望まれます。
そのため、モデルのクラスメソッドの責務と名前に一定の規則性を持たせることも1つのアイデアです。 例えば、「
findやcreateなど、Active Recordのクエリメソッドで使われる単語から名前が始まる場合は、必ずクエリ関連の責務とする」といったルールが考えられます。また、名前だけでなくコードドキュメントにおいても、他のメソッド以上に使い方をはっきりと示すことが望まれます。
クエリ構築用クラスメソッドでelse節を省くとメソッドチェーンの途中でエラーを招く
クエリ構築用のクラスメソッドの場合には、条件分岐の
else節を省いてしまうと、メソッドチェーンの途中で予期せぬエラーを引き起こす原因になるという注意点もあります。以下のように、先ほどの
with_colorメソッドからelse節を削除してみましょう。引数が有効値のとき(
color.present?が真になるとき)はエラーになりませんが、 以下のように引数が空文字列で条件を満たさない場合はNoMethodErrorが発生してしまいます。これは、以下のように条件を満たさなかった場合の戻り値が
nilになってしまうためです。nilに対してcountメソッドを呼び出そうとしたため、エラーになったのです。クエリ構築用メソッドとしてクラスメソッドを設計するなら、 常にActiveRecord::Relationインスタンス(またはそれに準ずるオブジェクト)を返す設計にしておくことが求められます。
リレーションを返す場合はscopeに統一する
クエリ構築の一環としてリレーションを返すことを意図したメソッドは、 クラスメソッドではなくscopeでの定義に統一するのが有効です。 これは、前述したモデルのクラスメソッドに関する困難さを和らげる、シンプルなアプローチの1つです。
1つのプロジェクト内でクラスメソッドでの定義とscopeでの定義を混在させることは、 前述のクラスメソッドの注意点とscopeの注意点を足し合わせることになり、チーム開発における理解コストを上げてしまいます。 では、混在をやめてどちらに統一するのが良いかと言えば、scopeに統一することが有力な戦略です。以下にその理由を3つ述べます。
理由1: scopeとクラスメソッドでは挙動に違いがある
まず大きな違いは、
nilまたはfalseを返したときの挙動です(この点については理由3で詳しく後述します)。 また、レシーバとしてモデル名を書いた際にも明確な挙動の違いが確認できます(下記「参考」を参照)。 これらに限らず、他の挙動の差異が潜んでいる可能性もあります。このように仕様や挙動に差異がある場合、二通りのやり方が混在していると「片方で問題ない書き方がもう片方でバグになる」という厄介さを抱えることになります。また、両方の仕様・挙動に対する知識が求められるため、コードベースの理解コストも上がってしまいます。
(参考)モデルクラス名を明示的にレシーバとした場合の挙動の違い
クエリ構築を行うscopeとクラスメソッドでは、モデルクラス名を明示的にレシーバとして書いてしまうと、 前の条件を引き継ぐか否かに違いが出てしまいます。
具体的には、まず以下のように、
Pen.where(color: color)とだけ記述したscopeとクラスメソッドをそれぞれ定義します。次に、それぞれを実行してみると、以下のような違いが出ます。
クラスメソッドの
with_color_mは前の条件where(price: 80)が適切にマージされているのに対し、 scopeのwith_color_sはprice: 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節がありませんが、メソッドチェーンの途中で呼び出されてもエラーを引き起こしません。
これにより、先ほど述べたクラスメソッドが持つ「nilを返してチェーンが壊れる」問題を回避しつつ、条件付きクエリを簡潔に表現できます。
さらにRubyでは、全く同じ処理を以下の一行に書き直せます。
このようにscopeを使うことで、意図が明確かつ簡潔なコードを書くことが出来ます。
まとめ
nil返却エラーを防いだり、コードの読み書きを容易にしたり出来ます。Active Recordのallが持つ条件を引き継ぐ性質と動的クエリ構築への応用
RailsのActive Recordの
allメソッドは、 その名前から「テーブルに存在する全レコードを取得するメソッド」と理解されがちです。 しかし実際には「その時点までに積み上げられた条件に該当するものを全て取得する」という実務上とても重要な性質を持っています。本記事では、この
allの性質の基本と、動的にデータベースクエリを組み立てる際の実践的な応用テクニックについて解説します。allは「構築済みの条件」を引き継ぐデータベースに
Penモデルのレコードが3件保存されているとします。 以下のようにPenクラスに対して直接allを呼び出すと、テーブルに存在する全てのレコードが取得されます。では、別の条件に続けて
allを呼び出すとどうなるでしょうか。 以下の実行例を見ると分かるように、allは「全て」ではなく、「それまでの条件を満たすもの全て」を返します。 ここではcolor: "red"を満たすレコードだけが取得されています。このように、
allは単に全件取得する機能だけでなく、 手前に条件がある場合には「そこまでに構築されたクエリ条件(Relationオブジェクト)を引き継ぎ、そのまま返す」という機能を担う側面があります。応用例:動的クエリ構築において
allを起点にしてメソッドチェーン可能にするこの「そこまでに構築された条件を引き継ぐ」という
allの性質は、 「URLのクエリパラメータが存在する場合だけ条件を追加する」といった動的なデータベースクエリ構築において役立ちます。例えば、クエリパラメータを処理する
filter_byというスコープを定義することを考えてみましょう。 このスコープは、以下のように「1年以内に作成されたレコード」という前提条件(where)のあとにメソッドチェーンで呼び出される想定です。この挙動を実現する
filter_byスコープの実装は以下のようになります。 最初にallを呼び出して現在のRelation(既に構築済みのwhere条件など)を変数pensに格納し、 そのpensに対してパラメータの有無をチェックしながら条件を継ぎ足しています。ここで重要になるのが、
filter_byの中で最初にallを呼んで変数に入れている点です。allは「何も条件がない状態」ではなく、 「このスコープが呼ばれた時点のRelationをそのまま受け取る」役割を果たしています。 そのため、Pen.where(created_at: …)のように手前で条件を付けていても、 それをリセットすることなくさらに条件を積み上げられます。 つまり、allを変数に入れることで、 手前の条件を維持した上でさらに条件を積み上げていくための「起点」を作ることができるのです。 なお、クエリパラメータが全て空で条件追加がない場合でも、allが返した元のRelationがpensに入っているため、 メソッドチェーンをそのまま継続できます。(参考)個別スコープへの切り出しとそのタイミング
先ほどの例では
filter_byの内部で変数に再代入しながら条件を追加していましたが、 もしcolorやpriceによる絞り込みをアプリケーション内の他の場所で単独で利用したい場合は、 それぞれを個別のスコープとして切り出すことでコードをよりキレイに書くことができます。Active Recordのスコープは、ブロックの評価結果が
nilまたはfalseになった場合、 自動的に元のRelationをそのまま返す(条件を追加せずにスルーする)という便利な仕様になっています。 そのため、以下のようにメソッドチェーンでそのままつなぐだけで、条件分岐をスコープの中に自然にカプセル化できます。なお、YAGNI原則の観点からは、 これらの個別スコープ(
with_colorやwith_max_price)が他の場所で「本当に必要になるまで」は、 無理に切り出さない方が良いでしょう。 つまり、最初から再利用性を過剰に意識してスコープを量産するよりは、 まずは前述のpens = allを使うアプローチで1箇所にまとめておき、 個別スコープが本当に必要になったタイミングで切り出す方が、 結果的には開発コストを減らせるはずです。RailsのAction MailerとAction Mailboxはmail gemを利用している
RailsでMailクラスに遭遇したら、それはmail gemのものです。ですので、そのAPI情報などは、Railsの情報源ではなく、以下のようなmail gemの情報源を見ると得られます。
Active Recordマイグレーション:t.referencesとadd_referenceで外部キー制約を詳細設定する方法
要点
Active Recordのマイグレーションファイルにて
t.referencesまたはadd_referenceを使う際、foreign_key:オプションに対してtrueの代わりに ハッシュを渡すことで外部キー制約の詳細を設定 できます。このハッシュで指定できるオプションはadd_foreign_keyのAPIドキュメントに記載されているオプションです。
具体例
t.referencesを使う場合(新規テーブル作成時)add_referenceを使う場合(既存テーブルへの追加)説明
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ドキュメントをご覧ください。Active Recordマイグレーションでのadd_foreign_keyとadd_referenceの違い
Railsガイドを一読しただけでは、 add_foreign_keyとadd_referenceが、それぞれどのようなもので、どう違うのかがいまいちよく分かりませんでした。 そこで、add_foreign_keyとadd_referenceによって
db/schema.rbがどのように変化するかを基に、 それぞれの機能について確認してみました。add_foreign_key
add_foreign_keyは外部キー制約の追加だけを行います。よって、カラムやインデックスの追加は行いません。
外部キー制約とは、子テーブルが参照している親のIDが親テーブルに存在することをデータベースレベルで保証する制約です。
例えば、以下の記述をマイグレーションファイルに行ったとします。
すると以下の記述が
db/schema.rbに追加されます。この記述は、products(子テーブル)のuser_idカラムに登場するIDが、users(親テーブル)のidカラムに必ず存在するように制約をかけています。
add_reference
add_referenceは、テーブル間の関連付けに関する複数の設定を一度に行える便利メソッドです。
add_referenceの基本機能は、カラムとインデックスの追加です。 例えば、以下の記述をマイグレーションファイルに行ったとします。
すると以下のように、users(親テーブル)に関連付ける2つの記述、具体的にはuser_idのカラムとインデックスをproducts(子テーブル)に追加する2つの記述が、
db/schema.rbに追加されます。またadd_referenceは、
foreign_key:trueオプションを追加することで、前述の2つに加えて外部キー制約も同時に設定できます。 例えば、以下の記述をマイグレーションファイルに行ったとします。すると以下のように、前述の2つの記述に加えて、add_foreign_keyの記述が
db/schema.rbに追加されます。(参考)add_referenceとt.referencesの機能は基本的に同じ
add_referenceとcreate_tableのブロックの中で呼び出すt.referencesは基本的に同じ機能を提供します(オプション以外の引数の部分で違いはありますが。) 実際にt.referencesへ渡せるオプションはadd_referenceと同じです。 また、t.referencesの実装とadd_referenceの実装のどちらも
ReferenceDefinition.newを内部で呼ぶ形になっています(v8.1.2で確認)。ActiveRecord::Rollbackで例外を伝播させずにロールバック後の処理を継続する
Active RecordのTransactionブロック内で例外が投げられた場合、ActiveRecord::Rollback以外の例外はロールバックの後に再度投げられます。
したがって、処理失敗で例外を投げるメソッド(
save!等)を使えば、基本的にその例外はロールバックの後Transactionブロックの外へ伝播します。 これをキャッチしなければ、RailsはHTTPのエラーレスポンスをブラウザ等のクライアントに返します。 つまり、「処理に失敗したら、ロールバックして、あとの処理は切り上げて、エラーレスポンスを返す」という挙動は簡単に実装できます。一方で、「ロールバック後に例外を出さずに処理を継続する」場合には、ActiveRecord::Rollbackを活用できます。 以下は、Transactionの成否でリダイレクト先を変えるコード例です。
minitest6にアップデートするとrails testがエラーになる
1/9にリリースされたRails v8.1.2は、minitest6で問題なさそうです。簡単にテストした分には、正常にテストが走りました。
ちなみにv8.1.1は、minitest6にすると、エラーにはならないものの、テストが全く拾われず
0 runs, 0 assertions, 0 failures, 0 errors, 0 skipsになっていました。minitest6にアップデートするとrails testがエラーになる
現象
2025/12/18にリリースされたminitest 6.0.0のgemにアップデートしたところ、
bin/rails testの実行で以下のエラーが発生して止まりました。ちなみに、テストが1つも無い状態ではこのエラーは発生しませんでした。とりあえずの回避策
Gemfileの
group :testのところに以下の記述を追加してminitestのバージョンを6よりも下に設定したところエラーは出なくなりました。SprocketsからPropshaftへの移行に関する注意点
Propshaftへ移行したらSprockets用の設定は削除可能
config/environments/production.rbにある以下の設定はSprocketsの設定なので、 Propshaftへ移行したら、削除可能です。参考情報
https://www.techtips.page/ja/comments/658