- 2024/11/07にRails8.0リリースされましたね
- Ruby on Rails 8.0 Release Notes — Ruby on Rails Guides にリリースノートが書かれている
- リリースノートには特にメジャーフィーチャーは書かれていないけど Ruby on Rails — Rails 8.0: No PaaS Required の方に書かれているkamal2やTrusterなどがメジャーフィーチャー扱いっぽい
- 上記のメジャーフィーチャー以外で気になった箇所をざっくりまとめたものです
- Add script folder and generator by jeromedalbert · Pull Request #52335 · rails/rails
- きっかけは大倉さんの「1回だけ実行するスクリプトをどこに置くかのRails公式な場所ないの?」という投稿
- DHHはscriptディレクトリ配下においている
- 昔はscriptディレクトリが今で言うbinディレクトリ相当のコマンド(と1回だけ実行するスクリプト置き場)になっていたんだけど公式のコマンド類がbinディレクトリに移行した結果ディレクトリもなくなっていた
- Rails4.0から削除された模様
- v4.0.0のGuideでscriptディレクトリがないGetting Started with Rails — Ruby on Rails Guides
- Rails2.x系では
./script/server
でrails s
相当のことをしていた - Rails3.0では
rails s
で起動するようになっている- Ruby on Rails Guides: A Guide to The Rails Command Line
- が、railsコマンドはscript/rails に置かれている Ruby on Rails 3.0 Release Notes — Ruby on Rails Guides
- Rails4.0でscript/railsが消えているのでおそらくここでscriptディレクトリも消えたと思われる
rails g script my_script
でscript/my_script.rb
が生成できるようになる- 個人的にはrakeタスクで
once:my_script
みたいなタスク名にして一回だけのスクリプトですよ、というのを明示的にしたrakeタスクを作る運用をしていました - seeds.rbのように「rails公式としてはこういう方法を用意しています」とした結果それを使う人自体は増えそうですね
- Add not-null modifier to migrations by dhh · Pull Request #52327 · rails/rails
string
のように属性の型を指定するところでstring!
とするとnot null制約が追加されるようになる- 本を書く立場としては超絶便利で最高
- Drop default permissions policy initializer by dhh · Pull Request #52341 · rails/rails
- permission policyとは Permissions-Policy - HTTP | MDN
- そうそう使わないからrails newしたときに生成されるテンプレートからは消すね、ということらしい
- DHHらしくばっさりいったな、という印象
- Remove channels from default app/ structure by dhh · Pull Request #52344 · rails/rails
- hotwired/turbo-rails: Use Turbo in your Ruby on Rails app経由でActionCableを簡単に使えるようになって、カスタマイズしたchannelの必要性が減ったのでrails newのときにはいらないんじゃん、とのこと
- 必要な人は
bin/rails generate channel notification
でできるよね
- lib/assets is too rare of a use case to warrant a default directory by dhh · Pull Request #52447 · rails/rails
- rails newしたときにできる lib/assetsが削除された
- アプリケーション用のassetsはapp/assetsで、外部ライブラリのものはvendor/assetsがあるので不要lib/assetsは不要ということらしい
- Default Regexp.timeout to 1s by rafaelfranca · Pull Request #53490 · rails/rails
- Rails8.0から有効(
config.load_defauts 8.0
に入っている)- Ruby3.2から
Regexp.timeout=
で正規表現の実行時間の上限を決めることができるようになっている - https://docs.ruby-lang.org/en/3.3/Regexp.html#method-c-timeout-3D
- タイムアウトするとRuntimeError
- Ruby3.2から
- Add an internal route for bin/rails notes by deepakmahakale · Pull Request #49240 · rails/rails
/rails/info/notes
で確認できる- 今どきはIDEなどでも確認できるのですごい便利!ということもないけど、TODOに気づくための導線が多いのは良いことではないでしょうか
- Silence healthcheck requests from the log by dhh · Pull Request #52789 · rails/rails
- DHHからのPR
- rails 7.1からデフォルトで
/up
がヘルスチェック用のエンドポイントとして生えた- Add a default health controller by dhh · Pull Request #46936 · rails/rails
- 死活確認用のリクエストでログが埋まるのが嫌なので、ログの対象外にした
config.silence_healthcheck = path
を設定したときだけ有効
- Fix third party hook for
rails stats
Thor command by p8 · Pull Request #52226 · rails/rails - Rename
CodeStatistics.add_directory
toCodeStatistics.register_directory
by p8 · Pull Request #52706 · rails/rails - 例えば
app/services
みたいなディレクトリを独自に生やしても、rails statsの対象ではなかった - rails statsの対象は
STATS_DIRECTORIES
という定数で管理されていたので、これをゴニョゴニョすることで対象範囲を拡大する、というハックが横行していた- 例えばrspecなどはspec配下のファイルをテスト用のディレクトリとして自動で登録していた
- が、これはrakeタスク時にフックして実行されている処理
- 最近、rails statsがrakeタスクからthorに寄せられた
- Use Thor for built-in stats task by JuanVqz · Pull Request #47713 · rails/rails
- これによりrspecなどのハックが無効に
- という流れで公式で
CodeStatistics.register_directory
が追加された
- Defer route drawing to the first request, or when url_helpers called by gmcgibbon · Pull Request #52353 · rails/rails
- 何度もrevertされつつようやくはいった
- Defer route drawing to the first request, or when url_helpers called by gmcgibbon · Pull Request #51614 · rails/rails · GitHub のベンチマークを見るとわかるんだけど、2000ルーティングあるアプリケーションでroutesを使わない処理(例:
bin/rails runnner ""
)をしたときに200ms短縮されている
- Deprecate hash key path mapping by gmcgibbon · Pull Request #52422 · rails/rails
- 今後は
get "/users", to: "users#index"
のようにtoを使って書いてねとのこと - この記法を廃止することで1.25~1.5倍くらい速くなるとのこと
- Prefer ETag over Last-Modified for
fresh_when
andstale?
according to the HTTP specification by casperisfine · Pull Request #52274 · rails/rails - これまで、fresh_whenメソッドを利用したときにETagとLast-Modifiedが両方指定されたときは両方ともチェックして、両方ともvalidであれば304を返すという仕様だった
- が、これはRFC7232で指定されている振る舞いとは異なっていた
- RFC7232で指定されているように変更された
- 両方送られてきたらETagのほうが優先される
- しかしこれは破壊的変更なので設定で仕様を切り替えるようになる
config.action_dispatch.strict_freshness = true
(デフォルトはfalse)- Rails 8.0のデフォルト設定(
config.load_defaults 8.0
)としてはtrueになる模様
expires_in
やhttp_cache_forever
にimmutable: true
オプションが追加された- これは「このURLがfreshな間はずっと要素は変わらないぞ」ということを表すもの
- Cache-Control - HTTP | MDN
- ハッシュ値をつけるassets類では常につけておくと良さそう
- これは一見
max-age=すごく大きい数字
と何が違うの?となるんだけどブラウザをリロードしたときの挙動が違う- Cache-Control の Immutable 拡張によるリロード時のキャッシュ最適化 | blog.jxck.io
- リロードしたときはmax-ageの範囲内でもリクエストが飛ぶ
- facebookやxなどのsnsではよくリロードされそうなので、immutableつける価値はありそう
- それ以外のwebサービスはそこまで気にしなくても良いかも?
- AllowBrowser: support method names for
:block
by seanpdoyle · Pull Request #53158 · rails/rails - allow_browserでblockオプションはprocオブジェクトしか入れられなかったのをシンボルでメソッドを指定する事もできるようになった
- 各種コールバックと同じ使い勝手になってよいですね
- Add
Parameters#expect
to safely filter and require params by martinemde · Pull Request #51674 · rails/rails - これまで、次のような形でユーザ入力値のフィルタリングが行われていた
- scaffoldするとこんな感じのがコード生成されるのでみんな真似しているはず
user_params = params.require(:user).permit(:name, :age)
- userは必須で、そこから先のnameとageだけフィルタリングする
- しかしこの書きかただと、↓のようなクエリで4xxではなく500になってしまう
/path?user=string
params.require(:user) #=> "string"
になってしまうため、後続のpermitでNoMethodErrorになる- 主にこれを解決するために新しいメソッドであるexpectが導入された
user_params = params.expect(user: %i[name age])
- これだと、先程のクエリではNoMethodErrorではなくActionController::UnpermittedParametersになり、(適切にエラーハンドリングしていれば)4xxになる
- 1メソッドだけですむので学習コストも下がる
- 追加機能として、パラメータ中に配列が含まれているときの判定が厳密になる、というのもある
# Hash is expected with `permit`
Parameters.new(user: { name: "Martin" }).permit(user: [:name]).require(:user)
# => {"name"=>"Martin"}
Parameters.new(user: [{ name: "Martin" }]).permit(user: [:name]).require(:user)
# => [{"name"=>"Martin"}]
# Hash is expected with `expect`
Parameters.new(user: { name: "Martin" }).expect(user: [:name])
# => {"name"=>"Martin"}
Parameters.new(user: [{ name: "Martin" }]).expect(user: [:name])
# => param is missing or the value is empty: user (ActionController::ParameterMissing)
- permitだと、nameが配列でもそうでなくてもOKになっているけど、expectの場合は配列だとエラーになる
- 配列を指定したい場合は
Parameters.new(user: [{ name: "Martin" }]).expect(user: [[:name]])
のように書く - 今後はrequireとpermitよりexpectを推奨
- requireやpermitがdeprecatedになる、というのは今のところ予定にない
- Add ability to use multiple rate limits per controller by fatkodima · Pull Request #52960 · rails/rails
- rate_limitでカウンタとして利用するキーはコントローラごとに基本同一なものが使われているため、例えば同一コントローラ中のshowアクションとcreateアクションで別々のrate limitをかけたい、という要求に答えられなかった
- ↓のようにnameオプションを指定すると、それがキーの一部として使われるのでこの問題を解決できる
class SessionsController < ApplicationController
rate_limit to: 3, within: 2.seconds, name: "short-term"
rate_limit to: 10, within: 5.minutes, name: "long-term"
end
- Introduce
ActiveModel::AttributeAssignment#attribute_writer_missing
by seanpdoyle · Pull Request #52417 · rails/rails ActiveModel::AttributeAssignment
によって生えるassign_attributesは、単に渡されたキーと値でアサインしていくメソッドhoge: 'fuga'
が渡されたらmodel.hoge = 'fuga'
が実行される- なので属性として定義していないものが渡されるとエラーになる
ActiveModel::UnknownAttributeError
- しかし外部APIを叩いて戻り値をモデルにアサインするようなケースでは、外部APIの仕様変更でいきなりエラーになる可能性がある
- 困る
- attribute_writer_missingというメソッドを上書きすることで、
ActiveModel::UnknownAttributeError
を発生させずログやSentryに通知するだけで済ませることができるようになる
class Rectangle
include ActiveModel::AttributeAssignment
attr_accessor :length, :width
def attribute_writer_missing(name, value)
Rails.logger.warn "Tried to assign to unknown attribute #{name}"
end
end
rectangle = Rectangle.new
rectangle.assign_attributes(height: 10) # => Logs "Tried to assign to unknown attribute 'height'
- Add
:except_on
option for validations by DRBragg · Pull Request #43495 · rails/rails - べんり?
- そもそもon使いたくなったら負け感があります
- Add an internal API to trigger association loading asynchronously by casperisfine · Pull Request #52807 · rails/rails
- private APIとしてasync_load_targetというメソッドが追加された
- byrootが実験のために入れたメソッドで、うまくいけばpublicになりそう?
- もちろん内部ではload_asyncを使っている
- Add support for SQLite3 full-text-search and other virtual tables by zachasme · Pull Request #52354 · rails/rails
- migration用のメソッドとしてcreate_virtual_table, drop_virtual_tableが新設された
- これでschema.rbで SQLite FTS5 Extension の機能を管理できるようになる
- campfireではstructure.sqlを利用してsqlite3+全文検索に対応していたけど、今後はschema.rbでいけるようになる
- sqliteで日本語の全文検索をするにはそこそこ頑張る必要がありそう
- Allow use of alternative database interfaces by tsvallender · Pull Request #52656 · rails/rails
rails dbconsole
したときに、これまでは例えばpostgresqlだったらpsqlが決め打ちで使われていたのだけどこれを変更できるようになった- ↓のpgcliは自動補完とシンタックスハイライト機能があるとのことで便利そう
Rails.application.configure do
config.active_record.database_cli = { postgresql: "pgcli" }
end
Product.in_batches(cursor: [:shop_id, :id]) do |relation|
# do something with relation
end
- これまで主キーで順番にやっていたのが、主キー以外も指定できるようになった
- in_batchesだけじゃなくてfind_each, find_in_batchesでも同様
- 順番に意味がある(positionみたいなカラムがある)ようなケースで有用
- class_attribute: reduce reliance on define_method by casperisfine · Pull Request #52722 · rails/rails
- class_attributeとはクラス変数をいい感じにしたもの
- これは、セッターを実行したときに毎回ゲッターのメソッドを定義しなおす、という形で実現していた
- 「メソッド定義し直す」は遅いので、ローカル変数を利用するように変更したところYJIT環境下で約17倍速くなった
- Rename check_box in checkbox by byroot · Pull Request #52432 · rails/rails
- CheckBoxのように2つの単語がくっついたものではなく、Checkboxのように1つの単語として使われていることが多いのでcheckboxにしようぜ、ということらしい
- input タグ的にもcheckbox
- Can we have a checkbox alias? · Issue #52430 · rails/rails
- check_boxも引き続き使える