こんにちは、hachi8833です。先週のつっつきの直前に7.1が正式リリースされました🎉。
Rails 7.1: Dockerfiles, BYO Authentication, More Async Queries, and more!https://t.co/c32MdUifNs
— Ruby on Rails (@rails) October 5, 2023
🔗Rails: 先週の改修(Rails公式ニュースより)
Rails 7.1に含まれる最終組のプルリクたちです。
🔗 ログをブロードキャストするpublic APIを追加
背景
#44695を手がけていたときに、広く使われている
Broadcasting
がまだprivate APIのままであることに気づいた。@rafaelfrancaによれば、public APIにするなら元の実装を理解しやすくメンテナンスしやすくするためのリファクタリングが必要とのこと。ブロードキャスティング
ブロードキャスティングは、要するに既存のロガーを「変換」してブロードキャストされたものにする形で動作する。
その後、ロガーは自身のメッセージをログに出力してフォーマットし、他のロガーにも伝える役目を担う。この方法の問題は以下のとおり。
- メタプログラミングを多用する。
ブロードキャスト内でロガーにアクセスすることも、ブロードキャストからロガーを削除することもできない。
さらに重要なのは、メインのロガー(他の部分にログをブロードキャストする)を変更できなかったこと。主にこの点が誤解の元になっていた。
logger = Logger.new(STDOUT) stderr_logger = Logger.new(STDER)) logger.extend(AS::Logger.broadcast(stderr_logger)) logger.level = DEBUG # これは「他のすべてのロガー」のレベルを変更する logger.formatter = ... # 他のすべてのロガーでフォーマッタが変更される
Rails.logger
の戻り値を変更しないようにするため、新しいBroadcastLogger
クラスでは素のRuby Loggerクラスと同じメソッドを持つダックタイピングを実装する。
これはシンプルで退屈なPOROであり、ブロードキャストに含まれるすべてのロガーの配列を保持し、ログが送信されるたびにイテレーションする。これで、ユーザーはブロードキャスト内ですべてのロガーにアクセスし、リアルタイムでそれらを変更可能になる。また、ブロードキャストから任意のロガーをいつでも削除できるようになる。
# 改修前 stdout_logger = Logger.new(STDOUT) stderr_logger = Logger.new(STDER) file_logger = Logger.new("development.log") stdout_logger.extend(AS::Logger.broadcast(stderr_logger)) stdout_logger.extend(AS::Logger.broadcast(file_logger))
# 改修後 broadcast = BroadcastLogger.new(stdout_logger, stderr_logger, file_logger)
ブロードキャストの責務は、すべてをブロードキャスト内のロガーに渡すことだけであることがユーザーにとってもっと明確になるべきだとも思っている。そうなれば、
broadcast.level = DEBUG
を呼び出したときにブロードキャスト内のすべてのロガーでレベルが変更されることに驚かなくて済むようになる。また、ブロードキャストのログを別の場所にブロードキャストする(
broadcast.broadcast_to(stdout_logger, other_broadcast)
)といった、より複雑なセットアップも理解しやすくなる。
同PRより
つっつきボイス:「これは先週手短に取り上げたBroadcastLogger
ですね(ウォッチ20231004)」「ログを複数の場所にブロードキャストする機能は今もあったような気がしますね🤔」「そのあたりの機能を柔軟に作り替えて、ブロードキャストのログをさらにブロードキャストするみたいなこともやりやすくなったそうです」
参考: ActiveSupport::BroadcastLogger
- Ruby on Rails API
つっつき後に、「この機能名は振る舞いから言ってBroadcastよりもMulticastの方が適切なのではないか」という指摘がありました。
🔗 has_one_attached
にFileやPathnameをアタッチできるようになった
この変更が必要な理由
テストでモデルを作成するときに、(たとえばfile_fixture
で)FileやPathnameをModel.create
に渡すといったことがやりやすくなる。問題の解決方法
attachableをアタッチするときに、FileまたはPathnameかどうかをチェックして適切に処理する。
同PRより
つっつきボイス:「これはActive Storageの改修ですね: こういう感じでテストのモデルに添付ファイルを手軽に渡せるようになったのはいい👍」「FileはRuby標準ライブラリのクラスで、Pathnameはdefault gemなんですね」
# 同PRより
User.create!(avatar: File.open("image.jpg"))
User.create!(avatar: file_fixture("image.jpg"))
参考: class File
(Ruby 3.2 リファレンスマニュアル)
参考: class Pathname
(Ruby 3.2 リファレンスマニュアル)
参考: standard librariesとdefault gemsとbundled gemsの違い - ESM アジャイル事業部 開発者ブログ
🔗 register_parser
を追加
- PR: Introduce
ActionView::TestCase.register_parser
by seanpdoyle · Pull Request #49194 · rails/rails
動機/背景
指定のMIMEタイプでレンダリングされたコンテンツをデコードするcallableを登録する。
登録された個別のパーサーは、
#rendered.$MIME
というヘルパーメソッドも定義する($MIME
はmime
引数の値に対応する)。詳細
引数:
mime
: レンダリングされたコンテンツのMIMEタイプ名のシンボルcallable
:String
をデコードするcallable、String
値だけを引数として受け取るblock
:callable
を省略すると、ブロックがパーサーとなる
ActionView::TestCase
では、デフォルトで以下のパーサーが定義される。
:html
:Nokogiri::XML::Node
のインスタンスを返す:json
:ActiveSupport::HashWithIndifferentAccess
のインスタンスを返す事前登録済みのパーサーには、対応するヘルパーも定義されている。
:html
:rendered.html
を定義する:json
:rendered.json
を定義する例:
レンダリングされたコンテンツをパースしてRSSにするには、
RSS::Parser.parse
への呼び出しを登録する。register_parser :rss, -> rendered { RSS::Parser.parse(rendered) } test "renders RSS" do article = Article.create!(title: "Hello, world") render formats: :rss, partial: article assert_equal "Hello, world", rendered.rss.items.last.title end
レンダリングされたコンテンツをパースして
Capybara::Simple::Node
にするには、以下のようにCapybara.string
を呼び出して:html
パーサーを再登録する。register_parser :html, -> rendered { Capybara.string(rendered) } test "renders HTML" do article = Article.create!(title: "Hello, world") render partial: article rendered.html.assert_css "h1", text: "Hello, world" end
後方互換性のため、
document_root_element
の既存サポートをrendered.html
に基づいて再定義した。追加情報
この提案は、
ActionDispatch::Testing::RequestEncoder
と、そのresponse.parsed_body
テストメソッドからヒントを得ている。
同PRより
つっつきボイス:「これはテストへの機能追加かな」「レンダリングされたデータを解析できるパーサーをregister_parser
で登録するとテストで使えるようになるそうです」「テストで欲しいパーサーを差し替えられるのか: 中身をこじあけて正規表現で取り出したりしなくて済むのはよさそう👍」「HTMLをCapybara.string
で解析したりRSSをRSS::Parser.parse
で解析したりできるんですね」
参考: register_parser
-- ActionView::TestCase::Behavior::ClassMethods
- Ruby on Rails API
🔗 tag
とcontent_tag
に無効なHTML文字を渡すとエラーをraiseするようになった
#49120の続きであり、#44948における空文字列
""
の場合の修正も行う。動機/背景
#49120 (comment)での議論の結果、
tag
、content_tag
ヘルパーメソッドで無効なHTML文字をバリデーションすることが最終的に決定された。詳細
tag
メソッドとcontent_tag
メソッドにHTMLタグ名のバリデーションを追加した。
content_tag
メソッドは、指定のタグ名がHTMLの仕様に準拠しているかどうかをチェックする。無効なHTMLタグ名が指定された場合、このメソッドは適切なエラーメッセージと共にArgumentError
を発生する。修正前:
content_tag("12p") # "<12p></12p>" content_tag("") # "<></>" tag("image file") # "<image_file></image_file>"
修正後:
# ArgumentError: Invalid HTML5 tag name: 12p content_tag("12p") # Starting with a number # ArgumentError: Invalid HTML5 tag name: content_tag("") # Empty tag name # ArgumentError: Invalid HTML5 tag name: "image file" tag("image file") # Contains a space
cc @byroot
同PRより
つっつきボイス:「今まではtag
やcontent_tag
に空文字みたいな無効なものを渡すと通っちゃっていたのか」「直接そういうものを渡すコードを書くことはあまり考えられないけど、渡すものをeach
で回してtag
やcontent_tag
に渡したりすると起きそう」「細かいけど、見逃すとつぶすのが面倒なヤツですね」
なおビューヘルパーのtag
メソッドが登場して以来、content_tag
は随分前からレガシー記法とされていますが、現在も一応使えます。詳しくは以下の記事をどうぞ。
🔗 ActiveStorage::Blob
の#signed_id
にexpires_at
オプションが追加
- PR: Add
expires_at
option toActiveStorage::Blob#signed_id
by aki77 · Pull Request #48115 · rails/rails
動機/背景
有効期限付きのURLを
expires_in
オプションで生成すると、生成のたびにURLが変更される。
以下の例に示すように、expires_at
オプションを使うことで、ブラウザキャッシュが一定期間効くようになる。rails_blob_path(user.avatar, disposition: "attachment", expires_at: 2.hours.from_now.beginning_of_hour)
<%= image_tag rails_blob_path(user.avatar.variant(resize: "100x100"), expires_at: 2.hours.from_now.beginning_of_hour) %>
詳細
ActiveStorage::Blob
の#signed_id
とURLヘルパーにexpires_at
オプションを追加する。
同PRより
つっつきボイス:「Durationを指定する従来のexpires_in
オプションに加えて、絶対時刻を指定するexpires_at
オプションも追加されたんですね」「expires_in
で有効期限を指定すると期限切れ前でも生成のたびにURLが変わっていたのね」「キャッシュが効かなくなって悲しいヤツだ」「委譲先のActiveRecord::SignedId#signed_id
には元々expires_in
とexpires_at
が両方あるので、Active Storageのファイルアップロード機能にもexpires_at
を足したんですね」「なるほど、今の挙動は変えずにオプションを追加したのか」「英語的にも、前置詞がat
だとその時刻きっかりで、in
だとその時刻までのどこかの時刻というニュアンスと合ってますね: "I'll be there in 5 minutes."が"あと5分以内に着くから"となるので、もっと早く着いてもよかったりします」
参考: signed_id
-- ActiveRecord::SignedId
- Ruby on Rails API
# activestorage/config/routes.rb#L59
direct :rails_storage_redirect do |model, options|
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
+ expires_at = options.delete(:expires_at)
if model.respond_to?(:signed_id)
route_for(
:rails_service_blob,
- model.signed_id(expires_in: expires_in),
+ model.signed_id(expires_in: expires_in, expires_at: expires_at),
model.filename,
options
)
else
- signed_blob_id = model.blob.signed_id(expires_in: expires_in)
+ signed_blob_id = model.blob.signed_id(expires_in: expires_in, expires_at: expires_at)
variation_key = model.variation.key
filename = model.blob.filename
route_for(
:rails_blob_representation,
signed_blob_id,
variation_key,
filename,
options
)
end
🔗 Action MailerにFormBuilder
を導入
関連: #48477
メールでフォームを使うことは一般的ではないが、可能。また、コントローラーとメーラーの間でビューを共有することも可能。
現在は、グローバル設定と異なるdefault_form_builder
をコントローラーで設定しても、同じビューを使っているメーラーで同じFormBuilder
がデフォルトにならない。これを修正するため、このプルリクではメーラー用のdefault_form_builder
メソッドを追加する。このメソッドの振る舞いはコントローラーにある類似のメソッドと同じ。
同PRより
つっつきボイス:「Action Mailerで送信するメールのフォームをコントローラと同様に上書きしてカスタマイズしたりできるようになったということですか?」「そうみたいです」「メールでフォームを使うのって、たしかに可能だと思いますけど今までやったことなかったな〜」「ユースケースよくわからないけど、Google Formsみたいなフォームをメールに埋め込んでフォーム送信可能にするみたいなのをやりたいのかも?🤔」
参考: ActionMailer::FormBuilder
- Ruby on Rails API
参考: Google Forms: オンライン フォーム作成ツール | Google Workspace
🔗 リファクタリング: deep_merge
をActiveSupport::DeepMergeable
に切り出した
- PR: Factor out
deep_merge
intoAS::DeepMergeable
by jonathanhefner · Pull Request #45411 · rails/rails
ActiveSupport::DeepMergeable
モジュールにより、クラスは単純にmerge!(other, &block)
メソッドを実装するだけで、deep_merge
とdeep_merge!
メソッドを提供できるようになる。値は、deep_merge?
と互換性がある場合にのみディープマージされる。デフォルトでは、同じクラス(またはそのサブクラス)のインスタンスだけを含む。deep_merge?
をクラスでオーバーライドすることで、ディープマージ可能な値のドメインをさらに制限または拡張することも可能。このプルリクによって、振る舞いがわずかに変更される。
従来のHash#deep_merge
はHash
インスタンスのみをディープマージしていたが、互換性のある任意のDeepMergeable
インスタンスもディープマージするようになる。
これは、#45369のコメントにヒントを得た。現時点の
ActiveSupport::DeepMergeable
は:nodoc:
になっているが、publicにしたいという要望があれば:nodoc:
を削除してもよい。
同PRより
つっつきボイス:「Active Supportのdeep_merge
って何と何をマージするんでしたっけ?」「ハッシュの中にハッシュがあるみたいなヤツでよかったと思います↓」「マージする側とされる側に同じ項目があると後勝ちになるんですね(引数側が優先される)」
# https://github.com/rails/rails/pull/45411/files#diff-716bf4cfb5055399688aa207b69ef1881e526c21940758beb1ad9b04754443bbR36
# 略
@hash_1 = { a: 1, b: 1, c: { d1: 1, d2: 1, d3: { e1: 1, e3: 1 } } }
@hash_2 = { a: 2, c: { d2: 2, d3: { e2: 2, e3: 2 } } }
@merged = { a: 2, b: 1, c: { d1: 1, d2: 2, d3: { e1: 1, e2: 2, e3: 2 } } }
# 略
test "deep_merge works" do
assert_equal Wrapper[@merged], Wrapper[@hash_1].deep_merge(Wrapper[@hash_2])
end
「そもそもRails 7.1のActionController::Parameters
にdeep_merge
とdeep_merge!
が駆け込みで追加されていたんですね(CHANGELOG)」「とりあえずHash
とActionController::Parameters
にはDeepMergeable
がinclude
されている(#45411コメント)」
- PR: Support
ActionController::Parameters#deep_merge
by seanpdoyle · Pull Request #45369 · rails/rails
「#45369ではHash
インスタンスしかディープマージできなかったんですね」「DeepMergeable
モジュールをinclude
してmerge!
を実装すれば、Struct
やもっと複雑なものもディープマージしたりブロックを渡して処理を追加したりすることも可能になる、なるほど」「APIドキュメントはまだprivateですけどね」
# activesupport/test/deep_mergeable_test.rb
class DeepMergeableTest < ActiveSupport::TestCase
Wrapper = Struct.new(:underlying) do
include ActiveSupport::DeepMergeable
def self.[](value)
if value.is_a?(Hash)
self.new(value.transform_values { |value| self[value] })
else
value
end
end
delegate :[], to: :underlying
def merge!(other, &block)
self.underlying = underlying.merge(other.underlying, &block)
self
end
end
「お、今Railsガイドをインクリメンタル検索してました?」「自分はRailsガイドを有料(700円/月)で使っているのでAlgoliaのインクリメンタル検索などの機能が使えます」「これいいな〜❤️」
参考: Proプラン - Railsガイド
参考: Site Search & Discovery powered by AI | Algolia
🔗 Range#overlap?
に空のrangeを渡した場合の振る舞いを修正
動機/背景
従来の
#overlap?
は、rangeが事実上「空」の場合にも誤ってtrue
を返していた。(2...2).overlap? 1..2 # => true (1..2).overlap? 2...2 # => true
詳細
Range#overlap?
はRuby 3.3の実装で修正済みなので、このコミットはRuby 3.3より前の場合も修正する。追加情報
追加したテストはRubyリポジトリから引っ張ってきたものであり、実装もCの修正をRubyバージョンにしたもの。
同PRより
つっつきボイス:「2...2
は空だからどのrangeとも重ならない、言われてみればたしかに」「overlap?
は前からRailsのActive Supportにあるみたいだけど、Ruby 3.3.0-preview2ではまだ動かないので、これからRuby 3.3に入るoverlap?
が先に修正されたということか」「Railsにはエイリアスのoverlaps?
もあるのね」
参考: Feature #19839: Need a method to check if two ranges overlap - Ruby master - Ruby Issue Tracking System
参考: overlap?
-- Range
- Ruby on Rails API
「空の概念はいろいろ難しい」「ゼロとかnil
とかNULL
みたいなものって間違いの元になりやすくて怖いですよね」
🔗 SQLite3のdatabase.ymlにretries
オプションを追加
動機/背景
SQLite3の
busy_timeout
は、低レベルのbusy_handler
関数固有の実装だが、これはtimeout
(単位はms)に達するまで「指数関数バックオフ」で単純にリトライする(ただし真の指数関数ではなく、バックオフが[1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50 ,100]ミリ秒ずつ増加し、以後はステップごとに100ミリ秒ずつ増加する)。
これをdatabase.yml
ファイル内のtimeout
オプション経由でアクセス可能にするのは、便利ではあるものの不十分。SQLite3をproduction向けRailsアプリケーションのデータベースエンジンとして利用することがますます一般的になっている。SQLite3のパフォーマンス最適化では、バックオフを待つ代わりに、ビジーなコネクションを直ちに再試行することが推奨されている。
詳細
このプルリクは、
database.yml
に新しいretries
オプションのサポートを追加する。このオプションはシンプルなbusy_handler
関数で使われ、指定した最大回数まで指数関数的バックオフなしでビジーなコネクションをリトライするようになる。
同PRより
つっつきボイス:「先週に続いてSQLite3に強い@fractaledmindさんのプルリクで、しかもバックオフ絡みです(ウォッチ20231004)」「今回はジョブのリトライではなくてSQLite3のデータベースコネクションが切れたときのリトライなので、最初のうちはバックオフしないで積極的にリトライをかけるように修正したのね」「PostgreSQLやMySQLのリトライはどんな戦略なのかちょっと気になりますね」
# activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L714
def configure_connection
- @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
+ if @config[:timeout] && @config[:retries]
+ raise ArgumentError, "Cannot specify both timeout and retries arguments"
+ elsif @config[:timeout]
+ @raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout]))
+ elsif @config[:retries]
+ retries = self.class.type_cast_config_to_integer(@config[:retries])
+ raw_connection.busy_handler do |count|
+ count <= retries
+ end
+ end
raw_execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
🔗 Active Jobのscheduled_at
の値をTime
としてシリアライズ/デシリアライズするようになった
動機/背景
このプルリクを作成した理由は、ジョブの
scheduled_at
の値をシリアライズ/デシリアライズすることでジョブ実行中にアクセス可能にするため。このプルリクでは、
scheduled_at
とenqueued_at
の振る舞いを調整して、内部で(エポック秒のFloat
や文字列ではなく)Time
オブジェクトを使うようにする。この変更により、主要なset(wait:, wait_until:)
インターフェースを利用するユーザーにとってscheduled_at
が透過的になり、値を直接設定する場合でも壊れなくなる(例:job.scheduled_at = 10.minutes.from_now.to_f
)。アダプタインターフェースは変更していない(引き続きエポック秒のFloat
パラメーターを引数として受け取る)。詳細
この変更は当初2018年に@adamnotoによって#32071で提案されたが、
scheduled_at
がバックエンドのキューアダプタの実装詳細とみなされたためクローズされた。あれからエコシステムに多少の変化が生じたのと、
scheduled_at
がActive Jobでどう処理されているかを再検討する必要があると思ったので、このプルリクを再度オープンした。エコシステムの変化: 私は自分自身の
good_job
gemバックエンドと、Active Job向けに設計されたskiplock
について触れておきたい。これらは5年前に以前のプルリクがクローズされた時点では存在していなかった。これらのActive Jobネイティブアダプタを使うことで、スケジュールされたジョブやリトライの再スケジュールでscheduled_at
が直接使われるようになる。今日のActive Jobは価値を主張する立場にあり、適切でない場合でも、アダプタは実行前に値をオーバーライド/再割り当て可能になる(通常はjob.provider_job_id
を使う)。Active Jobの変化:
ActiveJob::Base
インスタンスにインターフェイスがより集中するようになってきている(例:#set
の導入(#43434)やperform_all_later
のインターフェイス(#46603)など)。エンキュー前にインスタンスに設定された属性を、実行中のエンキュー後も利用できるようになるとよい。この変更を再開するきっかけとなったのは、最近見かけた
sidekiq-expiring-job
gemをActive Jobのbefore_perform
で簡単に実装できれば良いと思ったこと。例:
class ApplicationJob < ActiveJob::Base ExpiredJobError = Class.new(StandardError) discard_on ExpiredJobError before_perform do |job| raise ExpiredJobError if job.scheduled_at < 30.minutes.ago end end
最後に、
scheduled_at
はunixタイムスタンプなので、大好きというわけではない。このプルリクがマージされたら、非推奨サイクルを回してenqueued_at
(#35238)と同様にDateTime
に変換するようにしてもよい(何ならマージされる前でもよい)。追加情報
関連issue:
つっつきボイス:「以前も紹介したgood_jobというgemの作者によるプルリクです(ウォッチ20200803)」「good_jobはPostgreSQLのスケジューリング機能を使ってActive Jobのジョブキューを管理するんですね」
「実行中のジョブからscheduled_at
を取れるようになるとどんな点が嬉しいですか?」「非同期ジョブを管理するのって実は難しくて、人間が目で監視するならSidekiqのコンソールとかで見ればいいんですが、ジョブ同士が関連しているような場合なんかにプログラム的にscheduled_at
のような情報を取りたいことはありますね」「なるほど」「たとえばジョブがスケジューリングされた時刻と実際にワーカーを生成して実行開始した時刻が乖離していないかをチェックして、乖離が大きい場合はワーカー数を調整するとかかな: データベースに書いてもジョブで書き込みに失敗したら情報を取れなくなるかもしれないので、ジョブから取れるようになるといいかもしれませんね👍」
🔗 MySQLのスキーマダンプで引用符のエスケープが重複するバグを修正
概要
MySQLは、生成されたCHECK制約式内の引用符を自動的にエスケープする。
Railsがスキーマを(schema.rbに)ダンプすると、生成されたスキーマ内に含まれる引用符が\\'
のように二重エスケープされてしまう。これを修正するため、フェッチした式でMySQLが行うエスケープを削除する。
修正: #42424
その他の情報
テストをcheck_constraint_test.rbに書くと以下のようなコードになるので、MySQL固有のテストとすることにした。
if current_adapter?(:Mysql2Adapter) assert_equal "`name` <> 'forbidden_string'", constraint.expression elsif current_adapter?(:PostgreSQLAdapter) assert_equal "(name)::text <> 'forbidden_string'::text", constraint.expression else assert_equal "name != 'forbidden_string'", constraint.expression end
コードでわかるように、MariaDBすら引用符の扱いがMySQLと異なっている。
同PRより
つっつきボイス:「わかりやすいバグ」「MySQLとMariaDBで振る舞いが違っているというのがちょっとびっくりですね」
参考: MariaDB - Wikipedia
参考: DBごとのSQLのクォーテーションを整理したった - Qiita
🔗Rails
🔗 Rails 7.1がリリース
つっつきボイス:「ついさっき(注: 日本時間の10/5夕方)Rails 7.1がとうとうリリースされましたね🎉」「概要はRailsガイドの7.1リリースノート↓にもう少し詳しく載っていますし、TechRachoのRails 7.1リリース記事からリンクしている一連のCHANGELOG記事もリリース版のものに更新しました」
参考: Ruby on Rails 7.1 リリースノート - Railsガイド
「本家リリース記事に載っている目玉機能については以下でもう少し詳しく読めます↓」
- 新規アプリでDockerfileが生成されるようになった(ウォッチ20230125など)
- 認証機能の強化(TechRacho記事など)
- 非同期クエリメソッドの追加(
async_*
)(ウォッチ20220510など) - GitHubのTrilogyアダプタ(MySQL互換)をサポート(ウォッチ20230502など)
- 複合主キーのサポート(ウォッチ(多数))
perform_all_later
によるジョブの一括エンキュー(TechRacho記事)- オートロード用の
config.autoload_lib
とconfig.autoload_lib_once
などを追加(ウォッチ20230725) - Bunのサポートを追加(ウォッチ20230926)
「なお7.1では新たにガイドが2つ追加されています↓」
参考: Active Record の複合主キー - Railsガイド
参考: Rails アプリケーションのエラー通知 - Railsガイド
「一連の7.1 CHANGELOG記事は単なる翻訳ではなく、更新ソースやテストやドキュメントやスレッドにも目を通して、可能な限りコードも実際にRailsコンソールで動かしてみました: たぶん人生で一番たくさんCHANGELOGを読んだと思います😂」「CHANGELOGまだ1/3ぐらいしか読めてない😅」「特にActive RecordのCHANGELOGは200件近くあるので書いててきつかったです」「ボリュームの多さがヤバい」「CHANGELOGの原文にはプルリクidが載っていないんですが、読む人が一番欲しい情報だと思ったので、GitHubのblame表示で履歴をたどりながら実際のプルリクやコミットへのリンクや関連記事へのリンクや補足といった便利情報もムキになって追加しました」「お疲れさまです〜」
参考: ファイルの行ごとのリビジョン履歴の表示 -- ファイルの表示 - GitHub Docs
🔗 bun.lockbをGitで管理する
つっつきボイス:「Rails 7.1でサポートされた例のBun(ウォッチ20230926)で使われているロックファイルがバイナリ形式でGitに乗せにくいんだそうで、どうやってGitで差分を見られるようにするかを調べた記事です」「3つほど方法が示されているけど、Bun公式の方法があるといいですよね」「Rails 7.1でもこのあたりをケアする必要がありそうかな?」
参考: Bun — A fast all-in-one JavaScript runtime
🔗Ruby
🔗 YARPがprismにリネーム
つっつきボイス:「Ruby 3.3で導入される予定のYARP(パーサー)gemが、つい先頃prismという名前にリネームされたことを以下のRailsプルリクで知りました↓」「お、prismですか」
Done and merged! On Friday I'll release the first version of the `prism` gem, which will be the name going forward.https://t.co/Dneg5hyd72
— Kevin Newton (@kddnewton) September 27, 2023
「#49438でRipperをprismに置き換えるとパフォーマンスが倍近くなってますね↓」
# #49438より
Warming up --------------------------------------
Prism 173.000 i/100ms
Ripper 97.000 i/100ms
Calculating -------------------------------------
Prism 1.731k (± 1.5%) i/s - 8.823k in 5.098289s
Ripper 942.045 (± 4.1%) i/s - 4.753k in 5.054548s
Comparison:
Prism: 1731.0 i/s
Ripper: 942.0 i/s - 1.84x slower
「個人的にはprismという名前は、光をプリズムで分光するアナロジーがパーサーの振る舞いとよく合うので、パーサーの名前としてとてもいいなと思いました」「JavaScriptの人たちがPrism.jsのことかと思っちゃいそうですけどね」「ありゃ、それもそうか😅」
参考: プリズム - Wikipedia
参考: Prism -- prismjs.com
今週は以上です。
バックナンバー(2023年度第4四半期)
週刊Railsウォッチ: Rails 7.1.0.rc1と7.1.0.rc2がリリース、SQLite3コンフィグの最適化ほか(20131004)
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)