Skip to content

Instantly share code, notes, and snippets.

@willnet
Last active November 13, 2025 09:10
Show Gist options
  • Select an option

  • Save willnet/35b34f5f937683e69c62fd74c250a4a8 to your computer and use it in GitHub Desktop.

Select an option

Save willnet/35b34f5f937683e69c62fd74c250a4a8 to your computer and use it in GitHub Desktop.
Rails8.1のマイナーフィーチャーで気になったもの

これはなに

  • 2025/10/22にRails8.1がリリースされましたね
  • Ruby on Rails 8.1 Release Notes — Ruby on Rails Guidesにリリースノートが書かれている
  • 上記メジャーフィーチャーに含まれなかったマイナーフィーチャーのうち、気になったものをざっくりまとめています
  • 間違いの指摘や、これも追加してくれ〜という物があればコメントお願いします

railties

ヘルスチェック用のパスに来たリクエストをログに残さない、の拡張

開発環境とテスト環境ではYJITデフォルトオフになる

  • 開発環境だとリロードが走る
  • テスト環境だとメソッドの再定義(例: モック)が実行される
  • のでYJITをオンにしても速くならないでしょ、とのことで開発環境とテスト環境ではYJITはオフになった( config.load_defaults 8.1とした場合)
  • Don't enable YJIT in development and test environments · rails/rails@76e2b7e · GitHub
    • ↑のコミット作った人はYJITをオフにして30%速度が向上したとのこと

<meta name="application-name" content="Name of Rails Application">がrailsのレイアウトに追加された

RailsでYJITを起動するときにオプションを渡せるようになった

bin/bundle がなくなる

bin/bundle-audit がデフォルトで入った

  • Add a default bin/bundle-audit configuration by dhh · Pull Request #54695 · rails/rails
  • DHH作のPR
  • rubysec/bundler-audit: Patch-level verification for Bundler はGemfile.lockをみて脆弱性のあるバージョンがあったら教えてくれるもの
  • これがrails newしたときのデフォルトのGemfileにはいった
  • さらにrails newしたときにできるデフォルトのGitHub Actionsでも実行するようになった
    • ここで議論が起きている
    • 脆弱性を解消したgemがリリースされていないときにCIがずっと赤くなり、しかも解消できないのでCIにいれるのは反対派 (byroot)
    • 解消できないのがわかってるなら無視リストにいれるか、赤くても無視してマージしたらいいじゃん派 (dhh)
    • 議論自体は解決してないが、PR作ったのはdhhなのですっとマージされた
    • CIに対するスタンスの差が出た感
      • DHHはOne Person FrameworkとしてRailsを扱っているんだろうな、という感想を持ちました

QEMUごしにdocker buildすると時々ハングする件の解消

Active Support

ActiveSupport::Notificationsでイベントを送出していることを確認するテストヘルパーができた

assert_notification("post.submitted", title: "Cool Post") do
  post.submit(title: "Cool Post") # => emits matching notification
end

assert_notifications_count("post.submitted", 1) do
  post.submit(title: "Cool Post") # => emits matching notification
end

assert_no_notifications("post.submitted") do
  post.destroy # => emits non-matching notification
end

notifications = capture_notifications("post.submitted") do
  post.submit(title: "Cool Post") # => emits matching notification
end

コールバックの条件にsymbol procが渡せるようになる

before_save Proc.new { |r| r.history << "b00m" }, unless: lambda(&:history)

ErrorReporter#report にコンテキストを渡せるようになった

Rails.error.add_middleware(-> (error, context) { context.merge({ foo: :bar }) })
  • add_middlewareという名前からわかるように、rack middlewareのように複数のlambdaを渡すことができる

ActiveSupport::Cacheにread_counterとwrite_counterメソッドが追加された

  • Add Cache#read_counter and Cache#write_counter by ghiculescu · Pull Request #54855 · rails/rails
  • ActiveSupport::Cacheにはもともとincrementメソッドがあり、キャッシュをカウンターとして使うことができる
  • しかしカウンターの値を読み込む/書き込むときにはwrite/readメソッドにraw: trueをオプションをつけないといけなかった
    • キャッシュはmessagepackやmarshalやjsonでシリアライズされて格納されるので、例えばmessagepackで1をdumpすると"\x01"になりこれだとincrementできない
  • カウンター用の専用メソッドとしてread_counter, write_counterができた
    • 実際はraw: trueをつけつつread/writeしているだけ

CurrentAttributesがリクエストごとにクリアされるようになった

  • Always fully clear current attributes by byroot · Pull Request #55139 · rails/rails
  • CurrentAttributesはリクエストごとに毎回attributeで設定した属性をreset(初期値に戻す)をする
  • が、CurrentAttributesのインスタンス自体は使い回すので、独自でインスタンス変数などを定義しているとリクエストをまたいで状態が残ってしまう
  • そういう使い方をしているのがよくないんだけど、そういう使い方をしても問題ないように毎回インスタンスを作り直すように変わりました

JSONではU+2028とU+2029をエスケープしないようになる

  • Stop escaping JS separators in JSON by default by etiennebarrie · Pull Request #55800 · rails/rails
  • 昔はvalidな文字ではなかったのだけど今はそうではない(validな文字列である)とのことでエスケープする必要がなくなったとのこと
    • 昔はJSエンジンが文字列中に2028や2029を見つけるとエラーになっていたが、今は普通に文字として扱われる
    • JSONパーサーは昔から2028や2029を扱えたのでこの差異をなくす、というのが2019年頃に行われたらしい
  • 非互換な変更なので設定で切り替えられるようになっている
  • ↓もしくはconfig.load_defaults 8.1で有効になる
config.active_support.escape_js_separators_in_json = false

Active Model

normalizesがActiveModelで使えるようになった

(before|after_validation)でもexcept_onが使えるようになった

has_secure_passwordにおけるパスワードリセットトークンの有効期限が可変になった

Active Record

シリアライズしている属性を保存する条件を変更するオプションの追加

  • Allow to tag serialized attributes as comparable by byroot · Pull Request #53946 · rails/rails
  • ARにserializeというメソッドがあり、例えばtext型のDBカラムにYAMLやJSONを突っ込むことができる
  • 変更があったときにUPDATEする
  • これまで「変更があった」はシリアライズしたYAMLやJSONなどを比較して変更があればUPDATEしていた
  • するとYAMLやJSONライブラリの仕様変更があると、別のカラムを更新するときにシリアライズなカラムも強制的にUPDATEの対象になってしまう
  • これがおそらくshopifyで問題になった
    • libyamlの実装が変更されて、末尾のスペースがなくなった
    • 結果として不要にUPDATEが実行された
  • 次のようにcomparable: trueをオプションとして渡すとデシリアライズされているオブジェクト同士の比較をして、差分がなければUPDATEしない、となりこの問題を回避できる
serialize :config, type: Hash, coder: JSON, comparable: true

「rails db:migrateするとschema.rbのカラム順だけ変わってしまう」がなくなるぞ

     t.string "county"
-    t.string "country_code"
     t.datetime "created_at", null: false
     t.datetime "updated_at", null: false
+    t.string "country_code"
     t.string "urn"
  • 今後はアルファベット順になるので↑のような差分がなくなって良い
  • カラムの定義順が大事なんだよ!という人はstructure.sqlを使ってくれという方針の模様

structure.sqlでschema_migrationsの順番をカスタマイズできるようになった

  • Introduce versions formatter for the schema dumper by fatkodima · Pull Request #53797 · rails/rails · GitHub
  • structure.sqlを利用しているとschema_migrations(適用したmigrationバージョンを管理しているテーブル)はバージョンの降順になる
  • つまり新しいmigration AとBを別々に適用すると、structure.sqlの同じ行が更新される
    • なので大きいチームで開発しているとstructure.sql起因のコンフリクトがよく起きていた
  • これを解消するために、バージョンをどのように挿入するかカスタマイズできるようになった
  • 例えばバージョンのハッシュ値順にする、などするとバラバラにバージョンが入るのでコンフリクトの確率は下がる
  • デフォルトはこれまでと同様バージョン番号の降順

MySQLでtinyint(1)のような書き方がdeprecatedになった件のRailsの対応が入った

  • Fix boolean columns triggering MySQL deprecation by skipkayhil · Pull Request #54144 · rails/rails
  • MySQLのbooleanはtinyint(1)だった
  • これはtinyint(-128~127)型で、表示幅が1桁ということを意味する
    • (1)は表示幅を表すだけで、格納方式には影響していない
      • のだけど1桁を表すのかな、と勘違いされやすいので廃止の方向
  • tinyint(1)のような書き方をするとwarningがでる
  • show create tablesの出力結果にも表示されなくなる
    • tinyint(1)だけはbooleanとして使われているので例外的に表示されるらしい
  • なのでRailsではtinyint(1)はやめてboolean(これはtinyint(1)のエイリアス)を使うようになった

AR#serializeでシンボルをキーにしたハッシュを扱うのが簡単になった

  • JSON serialized attributes can return symbolized keys by matthaigh27 · Pull Request #54172 · rails/rails
  • serializeでよく使うのはJSON
  • JSONは知っての通りデフォルトでパースすると{ "hoge" => "fuga"}のようにキーは文字列になってしまう
  • 毎回deep_symbolize_keysするのは面倒
  • serialize :config, coder: ActiveRecord::Coder::JSON.new(symbolize_names: true)
  • それを解決するために、↑のようにsymbolize_names: trueをオプションとして渡したActiveRecord::Coder::JSONオブジェクトをcoderオプションに渡すことを可能にしたのがこのPR

MySQLとMariaDBでインデックスを無効にする機能をmigration経由で使えるようになった

implicit_order_column から主キーを取り除くことができるようになった

  • activerecord: Don't always append primary keys etc. to order conditions by issyl0 · Pull Request #54679 · rails/rails
  • implicit_order_columnという設定がActiveRecordにある
  • これは、例えばfirstとかlastを実行したときに使われるソート用のカラム
  • なにも設定されていなかったらprimary keyが使われる
  • でも例えばUUIDをprimary keyにしていたら…?
    • firstlastでなにが返ってくるかよくわからないですよね
  • そういうときにimplicit_order_columnを設定する
  • 仮に"author_name"みたいなのをimplicit_order_columnを設定したとき、ORDER BY author_name ASC, id ASCのようにprimary keyが2つ目のorder用カラムとして指定される仕様になっていた
    • author_nameがユニークじゃないときでも一意なレコードを返せるようにする目的
    • でもauthor_nameがユニークだったらidの指定は不要ですよね
    • というわけで次のように配列の最後にnilをいれることでidの指定を外せるようになった
self.implicit_order_column = ["author_name", nil]

in_batchesメソッドの高速化

  • Optimize Active Record batching further when using ranges by maximerety · Pull Request #51243 · rails/rails
  • find_eachなどのメソッドが依存しているin_batchesというメソッドがある
    • find_eachなどが使うrelationを返すやつ
    • 関連だけほしいときはこっちを使う
  • in_batchesが返すrelationのデフォルトの振る舞いはこう(cursorでバッチに使うキーを設定しなかった場合)
    • デフォルト1000件の主キーをpluckで取得する
    • IN句で主キーを設定したリレーションを返す
  • use_rangesというオプションが明示的に指定されているか、単にUser.in_batchesのようになんの条件も設定されていなかったときの振る舞いはこうだった
    • デフォルト1000件の主キーをpluckで取得する
    • pluckで取得した主キーの最後の値を利用して  (id >= x AND id <= y)のようなクエリを発行するリレーションを作る
  • use_rangesはIN句よりは速いけどpluckで1000件取得したやつの最後の値しか使わないのが無駄
    • 最初の値は前回のイテレーションの結果もしくはstartオプションで明示的に指定しているのでわかっている
  • そこでpluckの代わりにoffset 999 limit 1なクエリを使って最後の値だけを取るようにして高速化したのがこのPR
    • これでレコード数の多いテーブルのin_batchesがかなり高速化した
    • 最後のイテレーションのときはoffset 999 limit 1だとうまく最後のレコードを取得することができないので追加で1クエリ必要になる、というのが欠点ではあるけど、トータルで見たらメリットが大きい

orderが確定しない状況でfirstとかlastを実行するとエラーになる

config.active_record.raise_on_missing_required_finder_order_columns = true
  • 普通にRailsアプリケーションを作っていればprimary_keyがあるので関係ないけど、特殊なスキーマ構成にしていると踏むかもしれませんね

JSONでキーが重複禁止になるのを受けてARのデシリアライズでjsonカラムのパースでエラーになったときにsentryなどに通知されるようになった

DBのコネクションプールを管理する設定が増えたり変わったりするぞ

  • Maintain AR connection pools in the background by matthewd · Pull Request #54175 · rails/rails

  • デフォルトの振る舞いは変わらない

  • poolがmax_connectionsにリネーム(poolはまだ使える)

  • つぎの設定が追加

    • keepalive
      • 指定した時間の間隔で接続になにか送ってまだ接続が生きているぞ、とサーバに示す
      • デフォルト600秒(idle_timeoutが300秒なのでデフォルトでは使われない)
    • max_age
      • ここで指定した時間以上使われていたコネクションは回収される
      • デフォルトはInfinity(回収しない)
    • min_connections
      • 常にDBに接続しておきたいコネクションの数
      • 必要になったタイミングでコネクションを作る、だと時間がかかるので一定数事前に接続しておきたいという要望があるみたい
  • 便利に使える機会はありそう

ignore_columnsの反対であるonly_columnsが追加された

update_columnsとupdate_columnにtouch: trueオプションが追加された

Action Pack

Cache-Controlレスポンスヘッダにmust-understandディレクティブを追加できるようになった

render json:のときにHTMLエスケープしないようになる

  • Don't always escape JSON when calling render json: by etiennebarrie · Pull Request #54643 · rails/rails
  • これまでrender json:でJSONを返すときは<>&U+2028U+2029などの特殊文字に対するエスケープが行われていた
  • jsやHTMLにJSONを埋め込むのであればエスケープは必要だけど、jsでfetchしてくるだけならエスケープは別にいらない
  • Rails8.1のデフォルト設定ではエスケープしない、になった
  • 非互換変更なので、8.1にアップグレードしたときにconfig.load_defaults 8.1にするか、Rails.configuration.action_controller.escape_json_responses = falseと明示的に設定を有効にする必要がある
  • 上記設定にかかわらずrenderに:callbackがついているときはエスケープされる(昔JSONPという仕組みがあってだな…)

Cache-Controlのディレクティブを取得できるメソッドが追加された

    # Boolean directives
    request.cache_control_directives.only_if_cached?  # => true/false
    request.cache_control_directives.no_cache?        # => true/false
    request.cache_control_directives.no_store?        # => true/false
    request.cache_control_directives.no_transform?    # => true/false

    # Value directives
    request.cache_control_directives.max_age          # => integer or nil
    request.cache_control_directives.max_stale        # => integer or nil (or true for valueless max-stale)
    request.cache_control_directives.min_fresh        # => integer or nil
    request.cache_control_directives.stale_if_error   # => integer or nil

    # Special helpers for max-stale
    request.cache_control_directives.max_stale?         # => true if max-stale present (with or without value)
    request.cache_control_directives.max_stale_unlimited? # => true only for valueless max-stale

エラーページからエディタを直接開けるようになった

エラーページのテキストをコピーするボタンが付いた

クエリのキーのエンコーディングがおかしいときに500じゃなくて400を返すようになった

存在しないコントローラに対するルーティングが404だったのが500になった

リダイレクト先として許可するドメインのリストを定義できるようになった

  • Allow hosts redirects from hosts Rails configuration by byroot · Pull Request #55420 · rails/rails
  • デフォルトのredirect_toは他のホストに遷移しようとするとエラーになる
    • allow_other_host: trueをつければ他のホストに遷移できるけど、どこでも遷移できてしまう
    • 自社で複数のドメインを扱っているときに「このドメインならリダイレクトOK」というリストがあると便利
  • そこでconfig.action_controller.allowed_redirect_hosts << "example.com" で指定できるようになった

redirect_toに相対URLを文字列で渡すときに/始まりでないものは禁止になる

  • Merge pull request #55308 from Shopify/raise-on-relative-redirects-wi… · rails/rails@46d2afa
  • 最近のredirect_toallow_other_host: trueをオプションとしてつけない限り他のドメインに遷移することはできない
  • OAuthなどの認証でallow_other_host: trueをつけざるを得ない、というケースが有る
  • このときに相対URL形式で/始まりじゃないものを受け付けると脆弱性につながるのでこれを禁止にする
    • 例えばredirect_to "@attacker.com"とするhttp://[email protected]に遷移する
    • @の前はユーザ名として扱われるので、相対URLとしてyourdomain.comに遷移するのを想定しているはずなのにattacker.comに遷移してしまう
    • /始まりじゃない文字列がredirect_toに渡された時の振る舞いは3つ設定できる
      • log (何も設定していないときのデフォルト)
      • notify
      • raise(config.load_defaults 8.1としたときのデフォルト)

ドメイン判別ロジックをカスタマイズできる口ができた

/upがjson対応した

rate_limitをコントローラをまたいで使えるようになった

class APIController < ActionController::API
  rate_limit to: 2, within: 2.seconds, scope: "api"
end

class API::PostsController < APIController
  # ...
end

class API::UsersController < APIController
  # ...
end

rate_limitに引っかかったときに429ではなく専用の例外クラスがraiseされるようになった

  • RateLimiting: raise ActionController::TooManyRequests error by seanpdoyle · Pull Request #55501 · rails/rails
  • 429をダイレクトに返すのではなくActionController::TooManyRequestsをraiseするようになった
  • 例外クラスをはさんでrack middlewareでrescueして429を返すので基本的な振る舞いは変わらないけど、ActionController::BadRequest→400やActiveRecord::RecordNotFound→404などと同じように振る舞わせたいということみたい
  • 自前でrescue_fromでなにか別の処理を挟むことができてべんり

rate_limitメソッドがbefore_action等と同じようにメソッド名を受け付けるようになった

Action View

dom_idを拡張したdom_targetが使えるようになった

dom_target(Post.find(45))                  # => "post_45"
dom_target(Post.find(45), :edit)           # => "post_45_edit"
dom_target(Post.find(45), :edit, :special) # => "post_45_edit_special"
dom_target(Post.find(45), Comment.find(1)) # => "post_45_comment_1"

相対時間を自然言語で返すヘルパメソッド relative_time_in_wordsが追加された

current_page?メソッドがGETとHEAD以外のHTTPメソッドに対応した

  • Allow current_page? to match against specific HTTP method(s) with a method: option by bensheldon · Pull Request #55286 · rails/rails
  • ビューヘルパーにcurrent_page?というメソッドがある
  • 現在のページのタブをアクティブな表示にするときに使われる
  • これは歴史的経緯でGETかHEADなリクエストにしか対応していなかった
  • バリデーションエラーのときにそのままHTMLをレンダリングするとcurrent_page?がfalseを返して表示がおかしくなる、というのをなんとかしたい模様
  • これを解決するためにmethodオプションを追加してそれ以外のメソッドでもOKにした
current_page?(controller: 'product', action: 'create', method: [:get, :post]) # => true

input type="hidden"なタグに存在していたautocomplete="off"が削除される

  • [Fix #55209] Remove autocomplete="off" from hidden inputs in button_to by nkulway · Pull Request #55336 · rails/rails
  • firefoxのバグで、hiddenなinputタグの値に適当な値をautocompleteしてしまうというのがあった
    • railsだとform中に_method=patchみたいなhidden fieldタグをつけることがよくある
    • その値が急に変わってしまって想定していないリクエストになってしまうことがある
  • これの対応として、hiddenなタグにはautocomplete="off"を自動でつけるというコミットがRails7.0から入っていた
  • しかし今はそのバグは存在しないらしいのでautocomplete="off"を削除することになった
  • 非互換な変更なのでconfig.action_view.remove_hidden_field_autocomplete = trueもしくはconfig.load_defaults 8.1で有効になる

Action Mailer

複数のメールを一度に送るdeliver_all_laterメソッドが追加された

Active Job

ActiveJobでのリトライ時にレポートさせる、ができるようになった

Action Text

trixがgemとして外出しされた

Rails周辺

37signals製push通知用ライブラリ

  • 37signals Dev — Introducing Action Push Native
  • 過去にはAmazon SNSとPinpointを使っていたけどクラウドやめたので自前でpush通知用機能を作ったという流れらしい
  • 最初はAction Native Pushという名前だったけど後にAction Push Naiveにリネームされた
    • nativeという名前がついていることからもわかるように、iOSとAndroid向けのpush通知
    • ブラウザ通知機能はない
    • 37signals社製だけあってRailsと親和性のあるインタフェースを持っている印象
  • 別途Action Push Webがリリース予定で、合わせてAction Pushとなる模様

Trixの後継Lexxyが発表された

  • RailsWorldのDHHの発表でも言及されていた
  • 37signals Dev — Lexxy: A new rich text editor for Rails
  • これに合わせてActionTextで使うエディタもどこかのタイミングでLexxyに差し替わるはず
  • trixでは実現していなかったmarkdown対応が入っている
  • もともとRailsWorld2024のときにはActionTextでmarkdown使えるようにするという話があって、ONCE — Writebook ではサーバ側で実現していたけど結局エディタ側でやることにしたということみたい
  • basecamp/lexxy
  • This is an early beta. It hasn't been battle-tested yet. Please try it out and report any issues you find.

  • 現状↑とのことなのでActionTextの依存として入るとしたら8.2と思われます

37signals社謹製のマルチテナント用gemが発表された

  • basecamp/activerecord-tenanted: Enable a Rails application to have separate sqlite database files for each tenant.
  • マルチテナント系のgemは大まかに次の2つのアプローチ
    • 単一DBを利用し、かつscopeやarelなどをゴニョゴニョして強制的に1つのテナント内のクエリになるようにする
    • テナントごとにDBをわけて切り替えることで強制的に1つのテナント内のクエリになるようにする
  • このgemは後者
  • 現状、sqlite3のみをサポート
  • sqlite3のレプリケーションのために独自のツールbeamerというのを作っている
  • kamal geo proxy(未公開)でマルチリージョンのルーティングをサポートするとのこと
  • これで世界各地のユーザに対して一番近いリージョンのサーバからレスポンスを返す、というのを可能にするらしい
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment