今日(2026/10/07)時点でまだRails8.2はリリースされていませんが、今日開催のGinza.rb 第99回 でRails8.2特集をするので、これまでにRails8.2用に入った変更をまとめました。
これまでは節ごとにリンクが付与されるのが便利でgistに書いていたのですが、どうやら最近からはてなブログでも節のリンクが付与されるようになったので今回からはてなブログに書いていこうと思います。
自分が気になったものだけを書いているので抜け漏れ等あると思います。間違いもあるかもしれないので気づいたらコメントください。
今日以降で新しい機能が追加されたり変更されたりしたら追記や編集などする予定ですが、ベストエフォートでやるつもりなのでご了承ください (( ⁰⊖⁰)/)
過去のまとめ
Railties
存在しない環境名でrails sしたときにエラーになる
ローカルCIでfast failできるようになった
ローカルCIで並列実行できるようになる
group "Checks", parallel: 3 do
step "Style: Ruby", "bin/rubocop"
step "Security: Brakeman", "bin/brakeman --quiet"
step "Security: Gem audit", "bin/bundler-audit"
end
- groupはネストすることができる
- parallelを書けるのは一番外側のgroupだけ
- 内側のgroupは並列に処理されない
- ↓はTestsの二つのstepは順番通りに実行される
group "Checks", parallel: 2 do
group "Tests" do
step "Tests: Rails", "bin/rails test"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
end
step "Style: Ruby", "bin/rubocop"
step "Security: Brakeman", "bin/brakeman --quiet"
step "Security: Gem audit", "bin/bundler-audit"
end
deprecation情報がStructured Eventに通知されるようになった
boot時にroutes.rbに対応するものがない時に警告してくれるようになる
CI実行時に rake_eager_loadが効くようになる
- ブログに書いたので詳しくはこちら
- フォローアップのPR
Chromiumのワークスペース自動検出用のエンドポイントがRailsに追加された
rails statsで統計を取りたいファイルの拡張子を一つずつ追加できるようになった
Rails::CodeStatistics.pattern = /^(?!\.).*?\.(rb|js|ts|css|scss|coffee|rake|erb|slim)$/
- ライブラリ(例: slim)が動的に拡張子を足したいときにこれだと困る(他にも追加があるかもしれない)ので、拡張子を一つ追加するメソッド
register_extensionが追加された
Rails::CodeStatistics.register_extension("slim")
Rails.applicationをRails.appと短縮してかけるようになった
Rails.app.revision の導入
Useful in many context, deployment tracking, error reporting, monitoring, even sometimes in cache keys.
- ↑とのことで将来的に結構使われるんじゃないかなという気がします
rails consoleがexecutorでwrapされるようになった
Rack::Sendfileがデフォルトから外れた
rails queryコマンドが追加された
- Query command for read-only database queries by lewispb · Pull Request #57156 · rails/rails
- railsのサブコマンドとしてqueryが追加された
rails query "Account.where(plan: 'premium').limit(2)"のように使うと、リードレプリカなDBに対してクエリを発行し、結果をjsonで返してくる
rails query --sql "SELECT id, name FROM accounts LIMIT 2"のようにARを通さずに直接クエリを投げることもできる
- 他にも
rails query schemaでschema全体の情報を取得する、とかrails query schema acountsのようにして特定のテーブルの定義を見る、とかrails query modelsで全モデルのメタ情報を見るとかrails queyr explainでexplainの情報を見るとかできる
- 実際はrails runnerで実行したクエリをJSONで返すのを簡単にしたくらいの機能ではある
- Rails公式で使えるようになることでAIエージェントが本番のデータを見て何かする、がやりやすくなるのが良い
アプリケーションコード限定でfrozen string literalをデフォルトにする
- Enable frozen string literal by default by byroot · Pull Request #57252 · rails/rails
- frozen string literalは古いgemではまだ対応しきれていないものがあるので、
--enable-frozen-string-literalとするのはなかなか難しい
- アプリケーションコードだけなら自分たちが管理しているので切り替えやすい
- bootsnapに「アプリケーションコードだけfrozen string literalにする」を可能にするオプションが追加された
- Rails8.2でrails newするとこれが設定されたコードが出力される
- 合わせてrubocopの"StringLiteralsFrozenByDefault"が有効になる
- これは
Style/FrozenStringLiteralCommentとは違って、rubocopにfrozen string literalが有効になってますよ、というのを伝えるフラグ
- 例えば
Style/MutableConstant はCONST = 'hoge'に対して.freezeをつけろ、と言ってくるけど"StringLiteralsFrozenByDefault"なら不要
- だいたいのプロジェクトは
Style/FrozenStringLiteralCommentによって# frozen_string_literal: trueがついていると思うのだけど、これが有効になればマジックコメントを削除できる
- Rails8.2を待たずともbootsnapをアップグレードして設定をするだけで有効にできる
rails plugin new以外のサブコマンドを明示的にエラーにした
i18nファイルの存在確認を遅延させてRailsの起動を早くした
rails cした時にバナーが出るようになった
rails newしたときに生成されるGitHub Actionsの各ジョブにデフォルト15分制限が追加された
publicディレクトリ配下のエラーページがGitHubの言語統計に含まれないようになった
Active Support
SecureRandom.base32が追加された
Rails標準のテストで並列にテストを実行したときの順番を固定できるようになった&テスト実行時間の平準化ができるようになった
this_week?, this_month?, this_year?メソッドが追加された
RedisCacheStoreでキャッシュを読み込むのと同時に消す、がやりやすくなった
TimeやDateのRails用変換フォーマットが追加された
Date#this_quarter?とTime#this_quater?メソッドの追加
ActiveSupport::Ractors.on_mainメソッドが追加された
class Something
def self.something
@ivar ||= :a
end
end
class Something
def self.something
@ivar || ActiveSupport::Ractors.on_main { @ivar ||= :a }
end
end
ActiveSupport::TimeZone#parseでエラーになっていなかった引数をエラーにできるようになった
Action Pack
routes.rbで:controllerと:actionは使えなくなる
get "photos", to: "photos#index"
get "photos/:id", to: "photos#show"
- 他にも色々細かい差分がある
- 過去には
get "show", to: PhotosControllerという書き方もできたらしいのですがこれからはget "show", to: PhotosController, action: "show"と書く必要があるとのこと
controller: /photos|users/という書き方が過去にできたみたいだけどこれも無効
- routingをロードする前やRackアプリケーションをマウントしている時に
request.controller_classを呼ぶとActionDispatch::MissingControllerを返すようになる
paramsをパターンマッチできるようになる
if params in { search:, page: }
Model.search(search).limit(page)
else
…
end
case (value = params[:primitive_or_nested])
in String
in { nested_key: }
else
end
Accept: text/markdownでリクエストを投げたときのエラーページをテキスト形式にする
CSRFトークンを使わずにCSRFを防げるようになる
rails routes --expandedでアクションメソッドの位置も見れるようになる
--[ Route 1 ]----------------------------------------------------------------------------------------------------------------------------
Prefix | users
Verb | GET
URI | /users(.:format)
Controller#Action | users#index
Source Location | /Users/shinichi.maeshima/tmp/2025/12/17/mainhoge/config/routes.rb:2
Action Location | /Users/shinichi.maeshima/tmp/2025/12/17/mainhoge/app/controllers/users_controller.rb:5
--[ Route 2 ]----------------------------------------------------------------------------------------------------------------------------
さらにEDITOR環境変数が設定されているときにルーティングエラーページを表示すると次のようにペンのアイコンが表示されて、クリックするとエディタで対応するページが開くようになる。

ActionController::Liveで一部のコンテキストをコピーしないようにできる
class MyController < ActionController::Base
include ActionController::Live
def stream
response.headers['Content-Type'] = 'text/event-stream'
100.times {
response.stream.write "hello world\n"
sleep 1
}
ensure
response.stream.close
end
end
- 実際にストリーミング送信は新規にスレッドを生成して実行している
- このとき親となるスレッドからコンテキストをコピーして実行する、という仕組み
- 状況によっては全部親のをコピーすると都合が悪い
- 例えば複数DBでレプリカを使う、というコンテキストでストリーミングするけどストリーミング処理中ではDB書き込みをしたいというケース
config.action_controller.live.streaming_excluded_keys = [:active_record_connected_to_stack]のようにするとコピーしないように修正できる
docker環境でも開発時エラー画面からエディタを開けるようになる
{
"containerEnv": {
"EDITOR": "code",
"RAILS_HOST_APP_PATH": "${localWorkspaceFolder}"
}
}
401レスポンス時のconent-typeを変更できるようになった
http_basic_authenticate_with(
name: "admin", password: "secret",
message: '{"error":"Access denied"}',
content_type: "application/json"
)
リクエストが安全なメソッドを利用しているかどうかを表すメソッドが追加された
ActionController::Parameters#fetch_valuesが追加された
params = ActionController::Parameters.new(name: "Francesco", age: 22)
params.fetch_values(:name, :age)
params.fetch_values(:name, :none)
params.fetch_values(:name, :none) { |key| key }
ユーザごとにrate limitをかける、がやりやすくなった
class CommentsController < ActionController::API
rate_limit to: 2, within: 2.seconds, by: -> { current_user }
private
def current_user
User.find(1)
end
end
Acceptヘッダに*/*が含まれていた時に必ずHTMLを返す、をやめた
トークン認証時に認証スキーム情報を渡せるようになった
QUERYメソッド対応が入った
Active Model
Errorsオブジェクトに引数に渡したエラー種別に関するメッセージを返すメソッドが追加された
person.errors.where(:name, :invalid).map(&:message)
person.errors.where(:name, :invalid).map(&:full_message)
もともと↑のように書けていたのが↓のようにかけるようになる
person.errors.messages_for(:name, :invalid)
person.errors.full_messages_for(:name, :invalid)
allow_nilやallow_blankに条件をつけることが簡単にできるようになった
validates(
:course_id,
inclusion: {
in: -> (form) { form.permitted_courses.map(&:id) }
},
allow_nil: :saving_progress?
)
- ifやunlessで同様のことはできるが、こっちのほうが簡潔にかけるぞという主張
- バリデーション条件が複雑になるのは微妙だけど、選択肢が増えること自体はいいんじゃないでしょうか
has_secure_passwordでbcrypt以外にargon2も使えるようになった
json型のカラムのスキーマを定義できるようになった
class Account < ApplicationRecord
has_json :settings, restrict_creation_to_admins: true, max_invites: 10, greeting: "Hello!"
has_delegated_json :flags, beta: false, staff: :boolean
end
a = Account.new
a.settings.restrict_creation_to_admins?
a.settings.max_invites = "100"
a.settings = { "restrict_creation_to_admins" => "false", "max_invites" => "500", "greeting" => "goodbye" }
a.settings.greeting
a.staff
a.staff = true
a.staff?
- 次のようにしてjson型のカラムのスキーマを定義することができる
- シンボルでプロパティの型を定義する(boolean, integer, string)か、デフォルト値を決めることができる
- 決めている型以外の値がアサインされたらArgumentError
- ネストは不可
- デフォルト値はインスタンスを作るときとbefore_saveのタイミングで設定される
has_delegated_jsonは名前からわかるように各値へのアクセスを簡単にするためのメソッド
- べんりな気もするけど、スキーマ決まってるならjsonじゃなくて普通にカラムを定義したらいいんじゃん?という気もしますね
- サンプルコードのように設定値やフラグのようなものを一つずつカラムを定義して管理するのが面倒というケースを想定しているのかな
validatesのトップレベルの条件とvalidatorレベルの条件が両方とも効くようになった
validates :title, presence: { if: :local? }, if: :global?
- 理由は
Hash#mergeでオプションのマージをしていたので、上の例だとifキーで衝突してglobalが勝つ
- これを両方の条件を実行してANDで評価するような修正が入った
- これはバグフィックスで良いと思うけどこのバグの振る舞いに依存しているコードとかあったりしないかやや心配
validates_numericality_of ... in: にprocやシンボルを渡せるようになった
validates_numericality_of :price, in: ->(o) { 0..o.max_price }
validates_numericality_of :price, in: :price_range
def price_range
0..max_price
end
Active Record
enumのnegative scopeにnullなレコードも含むようになった
class Book < ApplicationRecord
enum :status, [:proposed, :written, :published]
end
not_をつけるとnegative scopeとして、通常のscopeの否定のscopeができていた
- not_proposed
- not_written
- not_published
- enumが指定するカラムがNULLを含むときに、negative scopeでNULLを含むレコードが対象にならないのはおかしい、ということでNULLのカラムも対象になるようにnegative scopeが修正された
- 非互換な変更なので設定で切り替えるのかな?と思いきやアップグレードガイドに変わるぞ、と追記されて完了
- これ設定で切り替えるほうがいいんじゃないかな
explainのフォーマットを指定できるようになった
マイグレーションの仕方をDBごとにカスタマイズすることができるようになった
with_lockのブロック引数にTransactionオブジェクトが渡されるようになった
person.with_lock do |transaction|
transaction.after_commit { puts "hello" }
end
トランザクションにフックして何かする、が簡単にできるようになった
class Account < ApplicationRecord
private
def implicit_persistence_transaction(connection, &block)
if connection.transaction_open?
yield
else
super
end
end
end
- この例だとトランザクションをすでに開いていたらなにもせずに実行するというようなコードになっている
- Shopifyではこれを使って「特定のモデルであれば決められた分離レベルで実行する」がやりたいみたい
create_table時に発行されるクエリをまとめて実行するようになった
add_indexに配列を渡せるようになった
t.index 'lower(email), status'
t.index [ "lower(email)", :status ]
t.index [ "lower(email)" ]
- ちょっとした変更だけど、式じゃなければ元々配列でかけたので一貫性としてはいいんじゃないでしょうか
in_order_ofの引数をグループ化できるようになった
Posts.in_order_of(:state, [[:published, :canceled], :archived]).order(created_at: :desc)
insert_allとupsert_allでキーが揃っていない引数を渡した時のエラーメッセージが改善された
Book.insert_all [
{ name: "Rework", author_id: 1 },
{ name: "Remote", author_id: 1, isbn: "..." }
]
- このAI時代、エラーメッセージが改善されるとトークンが節約できるので、エラーメッセージが今までよりも大事になっている気がします
enumでinvalidな値を指定したときのエラーメッセージの内容がより詳細になった
Book.new(status: "bogus")
デフォルトのorderを設定できるようになった
class Post < ApplicationRecord
has_many :comments, default_order: :likes
has_many :comments, -> { default_order(:likes) }
end
post = Post.first
post.comments
post.comments.order(:created_at)
query log tagがコネクションプールごとに設定できるようになった
db/schema.rb にこれまで実行したmigrationのリストを追加できるようになった
ActiveRecord::Schema[8.2].define(version: 2026_04_16_011000) do
end
- これは次のようなエッジケースで困る
- 自分のブランチでまだ実行していないmigrationファイルが存在する状態でmainブランチをmergeする
- versionが、自分のmigrationファイルよりも新しくなる
bin/rails db:resetする
- config/schema.rbが適用される
- このとき、versionより古いマイグレーションは全て適用済み扱いになる(!!)
- よって、この後bin/rails db:migrateしても自分のブランチでまだ実行していないmigrationファイルは実行されない
- エッジケースの方はそれほど踏んだ人は多くなさそうだけど、versionのところでコンフリクトするのはよくある
- これらの問題を解決するために、実行したmigrationのバージョンを全部schema.rbに書くようにする、というのがこのPR
config.active_record.dump_schema_migrations = trueとしたときに有効になる(デフォルトはfalse)
- 次のように、schema.rbの末尾に適用済みマイグレーションのバージョンが追記される
ActiveRecord::Schema[8.2].define do
end
ActiveRecord::Schema.load_schema_migrations(__FILE__)
__END__
- バージョンの記述順は、バージョンをto_sしてreverseしたもの順
- バージョンを追加する場所が重複するとコンフリクトするので、reverseすることである程度ランダムな箇所にバージョン情報が挿入されることになりコンフリクトの確率を下げている
- ソート順を明示的に指定することもできる
config.active_record.dump_schema_migrations_sort_by = ->(version) { Digest::MD5.hexdigest(version) }
- Allow prepared statements with query log tags by bschrag620 · Pull Request #58274 · rails/rails
- prepared statementsは、DBのクエリを先に登録しておく仕組みで、SQLインジェクションを防いだり何度も実行するクエリの実行が速くなったりする
- query log tagsは、クエリの発行元をクエリのコメントとして追加する設定
- prepqred statementsはクエリのコメント部分も見て同一のクエリなのかどうかを判定するのでquery log tagsと相性が悪く、query_log_tagsを使う時は必ずprepared statementsはオフになっていた
- でもあえて両方使いたい時もあるんや、ということで設定で両方使えるようになった
config.active_record.disable_prepared_statements = falseとすると両方使える
- 両方使いたい理由は特にPRに書いてないんだけど、コメントの種類が限られている時はデメリットが少ないので使ってもいいんじゃんということなのかなあ
スキーマキャッシュから特定のテーブルを除外する設定が統合された
- Add config.active_record.schema_ignored_tables by edcarvio · Pull Request #58554 · rails/rails
- スキーマキャッシュは、Railsが起動するときにスキーマ情報を取得する際のクエリを投げずに済ませるために使えるキャッシュ
- スキーマキャッシュなしでたくさんのRailsアプリケーションプロセスが一斉に起動すると、DBに無駄に負荷がかかる
- 特定のテーブルをスキーマキャッシュから除外するときに、これまで二つの設定があった
ActiveRecord::SchemaDumper.ignore_tables: DBスキーマの出力(schema.rb)から除外する設定
config.active_record.schema_cache_ignored_tables: スキーマキャッシュの生成から除外する設定
- 二つに分かれてる意味ないやろ、ということで
config.active_record.schema_ignored_tablesに統合するぞ、というのがこのPR
- 従来の二つの設定はdeprecatedになっています
「SELECTでorderがない場合はshuffleして返す」ができるようになった
config.active_record.shuffle_unordered_selects = true
- 100%気付けるわけではないにしろ、テスト環境でオンにしておくと便利じゃないかと思います
PostgreSQLのエラーメッセージの詳細度をdatabase.ymlから指定できるようになった
ERROR: duplicate key value violates unique constraint "index_users_on_email" DETAIL: Key (email)=(alice@example.com) already exists.
- 個人情報をログやSentryに出したくない!!!
- PostgreSQLにはメッセージの詳細度を変更するための関数PQsetErrorVerbosityがある
- これをdatabase.yml経由で設定できるようになった
- ↓のようにすると、エラーメッセージにメールアドレスが含まれなくなる
production:
adapter: postgresql
error_verbosity: <%= PG::PQERRORS_TERSE %>
Action Mailer
ActionMailerのassertionの追加
mail = UsersMailer.create(user)
assert_part :text, mail do |text|
assert_includes text, "Welcome, #{user.email}"
end
assert_part :html, mail do |html|
assert_dom html.root, "h1", text: "Welcome, #{user.email}"
end
assert_no_part :html
assert_no_part :text
Action View
collectionをrenderするときにブロックを渡せるようになった
# _post.html.erb
<article class="post">
<h1><%= post.title %></h1>
<div class="post__body"><%= post.body %></div>
<%# Render additional content %>
<%= yield post if block_given? %>
</article>
# index.html.erb
<%= render @posts do |post| %>
<%= link_to "Edit", [:edit, post] %>
<% end %>
ビューヘルパで属性をネストした時にハイフンでつなぐようになる
before
tag.button "POST to /clicked", hx: { post: "/clicked", swap: :outerHTML, data: { json: true } }
=> "<button hx=\"post /clicked swap outerHTML data {json: true}\">POST to /clicked</button>"
after
tag.button "POST to /clicked", hx: { post: "/clicked", swap: :outerHTML, data: { json: true } }
number_fileld_tagとrange_field_tagにendless、beginlessのrangeを渡せるようになる
color_fieldにvalue: nilを渡すとnilが無視されるのが無視されなくなった
ERBの設定をする口が公式にできた
Rails8.2ではherbがデフォルトのテンプレートエンジンになる
Active Storage
Active Storageでサムネイル変換を即時で行えるような選択肢が追加された
has_one_attached :avatar_with_immediate do |attachable|
attachable.variant :thumb, resize_to_limit: [4, 4], immediate: true
end
ActiveStorageで即時サムネイル生成するときに解析もやるようになった
ActiveStorage::Attachment.after_upload do
end
アップロードファイルの解析は遅延処理ではなくバリデーション前に行うのがデフォルトになる
Active StorageがAPIモードで使いやすくなった
ActiveStorageにattach!が追加された
has_many_attachedで関連づけられているファイルの容量合計を返すヘルパーメソッドbyte_sizeが追加された
document.images.byte_size
ImageProcessing gemのメジャーバージョンアップ
variant生成に独自のクラスを指定できるようになった
ffmpegとffprobeに対してオプションを渡せるようになった
marcelのバージョンが2にアップグレードされた
ActiveStorageのダイレクトアップロードに関するルーティングを設定しない、ができるようになった
Active Job
一回deprecatedになったconfig.active_job.enqueue_after_transaction_commitの設定が復活した
retry_onのwaitに渡すprocの第二引数にエラーオブジェクトが追加された
class RemoteServiceJob < ActiveJob::Base
retry_on CustomError, wait: ->(executions, error) { error.retry_after || executions * 2 }
def perform
end
end
Active Jobにattributeメソッドが追加されて、Active Job Continuation利用時に途中経過を保存するのがやりやすくなった
class SubmitEnrollmentJob < ApplicationJob
include ActiveJob::Continuable
attribute :payment_token, :string
attribute :billing_profile_id, :integer
def perform(enrollment)
step(:tokenize_payment_instrument) do
self.payment_token = PaymentGateway.tokenize(enrollment.user.payment_instrument)
end
step(:create_billing_profile) do
self.billing_profile_id = BillingProfileApi.create(customer_id: enrollment.user_id)
end
step(:submit_enrollment) do
submission_id = EnrollmentApi.submit(enrollment, billing_profile_id)
enrollment.update!(status: 'processing', submission_id: submission_id)
end
end
end
特定のキューを中断する時にActive Job Continuationの中断ができるようになる
retry_onのwaitオプションにfloatを渡せるようになった
DBの接続が不調でエラーな時はリトライしたい
- Introduce
ActiveJob::DeserializationError::RecordNotFound by rosa · Pull Request #57742 · rails/rails
- HeyでDB接続が不調で、引数のデシリアライズ中にエラーになりジョブの実行が失敗する、ということがあった
- この時のエラーはActiveJob::DeserializationErrorという広い意味でのエラーになる
discard_on ActiveJob::DeserializationErrorとしているとリトライができない
- DBへのコミットからジョブ実行までに該当レコードが削除されているような時はdiscard_onで良いのだけど、DB接続が不調なときはリトライさせたい
- というわけで
ActiveJob::DeserializationErrorのサブクラスとして ActiveJob::DeserializationError::RecordNotFoundが新設された
discard_on ActiveJob::DeserializationError::RecordNotFoundとすれば「DBへのコミットからジョブ実行までに該当レコードが削除されている」時だけdiscardできる
Action Text
Action Textにto_markdownが追加された
Railsの出力する標準のログ出力がStructured Event経由に変更になった
Credentialsと環境変数をまとめて取り扱う仕組みができた
Rails.app.creds.require(:db_host)
Rails.app.creds.require(:dataase, :host)
Rails.app.creds.option(:cache_host, default: "cache-host-1")
Rails.app.creds.option(:cache_host, default: -> { "cache-host-1" })
- 環境変数だけを対象にしたenvsメソッドも同じように扱える
Rails.app.envs.require(:db_password)
Rails.app.envs.require(:aws, :access_key_id)
Rails.app.envs.option(:cache_host)
Rails.app.envs.option(:cache_host, default: "cache-host-1")
Rails.app.envs.option(:cache_host, default: -> { HostProvider.cache })
- 開発環境の時だけ
.envも読む
- デフォルト設定だと
.env決め打ちで、.env.developmentみたいなファイルは考慮していない
- 環境変数と.env、Credentials以外に対象となる要素を追加することもできるので、config/application.yml なども追加したら全て
Rails.app.credsで賄うこともできそう
- そのときはcredsからリネームしたほうが良いかもしれない
Railsのお作法に則っていないgemを検出する機能がRails公式に入る
#{component.inspect} was loaded before appliction initialization.
Prematurely executing load hooks will slow down your boot time
and could cause conflicts with the load order of your application.
Please wrap your code with an on_load hook:
ActiveSupport.on_load(#{component.inspect}) do
# your code here
end
次のように設定するとログ出力の代わりに例外にすることができる
config.action_on_eary_load_hook = :raise
tメソッドのscopeを現在のアクション/テンプレートの相対パスで表現できるようになった
t(".foo.bar")
t("bar", scope: ".foo")
t("bar", scope: ".foo")