Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

週刊Railsウォッチ: RuboCop実行結果のキャッシュ機能が追加、ZJIT登場ほか(20250430)

こんにちは、hachi8833です。RubyKaigi 2025お疲れさまでした。以下のscrapboxまとめがものすごい量になっていますね。

参考: RubyKaigi 2025 - ruby-jp

来年は函館ですね。

週刊Railsウォッチについて

  • 各記事冒頭には🔗でパーマリンクを置いてあります: 社内やX.comでの議論などにどうぞ
  • 「つっつきボイス」はRailsウォッチ公開前ドラフトを(鍋のように)社内有志でつっついたときの会話の再構成です👄
  • お気づきの点がありましたら@hachi8833までメンションをいただければ確認・対応いたします🙏

TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)

🔗Rails: 最近の改修(Rails公式ニュースより)

🔗 インデックス作成/変更でMySQLのINVISIBLEやMaria DBのIGNOREDを指定可能になった

動機/背景

このプルリクの目的は、MySQL v8.0.0以降の(ALTER/CREATE) INDEX ... INVISIBLEとMariaDB v10.6.0以降の(ALTER/CREATE) INDEX .... IGNORED機能のサポートをRailsに統合し、Rails環境内でのインデックス管理機能を強化すること。

インデックスを非表示にすると、インデックスのメンテナンスや更新は引き続き可能になるが、クエリオプティマイザでの利用が許可されなくなる。この機能により、インデックスの変更をより段階的に展開できるため、インデックスの利用や削除が確定される前に開発者がパフォーマンスへの影響を監視できるようになる。詳しくは以下を参照。

現在、このマイグレーションはSQL文として実行可能である。ただし、この制限により、schema.rbファイルへの反映が不十分となり、schema:loadプロセス中にインデックスの表示/非表示の状態が正確に取得されない。

インデックスの表示/非表示を、既存のカラムメソッドやインデックスメソッドを介して変更できる機能を追加することで、schema.rb内でインデックスのインデックスの表示/非表示状態が適切に表現されるようになる。この機能強化により、インデックスの表示/非表示が明確になるだけでなく、これらのオプションの管理も容易になり、必要に応じてインデックスの表示/非表示の変更を容易に実装・取り消し可能になる。

詳細

このプルリクは、インデックス作成時にINVISIBLE/IGNOREDを有効・無効にするサポートをActive Recordのマイグレーションに追加する。

        add_index :users, :email, enabled: false
        enable_index :users, :email
        add_column :users, :dob, :string, index: { enabled: false }

        change_table :users do |t|
          t.index :name, enabled: false
          t.index :dob
          t.disable_index :dob
          t.column :username, :string, index: { enabled: false }
          t.references :account, index: { enabled: false }
        end

        create_table :users do |t|
          t.string :name, index: { enabled: false }
          t.string :email
          t.index :email, enabled: false
        end

インデックスオプションが拡張され、enabled: true/falseオプションを認識するようになった。このオプションを使うことで、既存のカラムメソッドやインデックスメソッドでインデックスを非表示として作成可能になる。

既存のインデックスの表示/非表示を管理するためのenable_indexメソッドとdisable_indexメソッドも2つ追加された。どちらのマイグレーションも取り消し可能である。

この機能はMySQL v8.0.0以降とMariaDB v10.6.0以降でのみサポートされており、それに応じてArgument/Not Implementedエラーが発生する。

SQLiteには対応する機能が存在しない。
PostgreSQLにはindisvalidフラグがあり、これがtrueの場合、インデックスは維持されるがクエリプランナーで使えなくなる(「INVISIBLE/IGNORED」インデックスと同様)。
ただしこのフラグは、インデックスが破損していると見なされた場合に PostgreSQLによって自動的に更新されることもあり、必要に応じて更新するにはスーパーユーザー権限が必要。これは、クエリプランナーでのユーザー管理インデックスの扱いとはあまり似ていないため、このプルリクではこの機能を PostgreSQLには拡張しないことにした。近い将来この機能をPostgreSQLのインデックスのenable/disable機能に追加することについて以下で議論している。

参考: Thread: Proposal to Enable/Disable Index using ALTER INDEX : Postgres Professional

このオプションをenabled: true/falseと命名した理由は、クエリでインデックスが使われているかどうかをより明確にするため(他の候補にはvisible: true/falseignored: true/falseもあった)。

ALTER INDEXは、MySQLとMariaDBの両方でインデックスの「表示/無視」の状態を変更するために使われている。しかし、ALTER INDEXはPostgreSQLでも他のインデックス操作に使われているため、alter_indexメソッドをクエリでのインデックス利用と結びつけたくなかった。そこで、代わりにクエリにおけるインデックス利用を「有効化/無効化」する専用のメソッドを作成した。
同PRより


つっつきボイス:「MySQLだとINVISIBLEでMariaDBだとIGNORED、PostgreSQLだとindisvalidで、SQLiteにはないんですね」「インデックスのINVISIBLE/IGNORED機能は以前調べたことあったかも: 大規模アプリの細かいDBチューニングで使いたいことはありそうですが、通常の規模のRailsアプリではそこまで積極的に使うことはなさそうですね」「なるほど」

🔗 ErrorReporterAssertionscapture_error_reportsが追加

ActiveSupport::Testing::ErrorReporterAssertions#capture_error_reportsを導入

指定のエラークラスに一致するブロック内から報告されるすべてのエラーをキャプチャする。

reports = capture_error_reports(IOError) do
  Rails.error.report(IOError.new("Oops"))
  Rails.error.report(IOError.new("Oh no"))
  Rails.error.report(StandardError.new)
end

assert_equal 2, reports.size
assert_equal "Oops", reports.first.error.message
assert_equal "Oh no", reports.last.error.message

Andrew Novoselac
同Changelogより


つっつきボイス:「capture_error_reportsブロックで囲まれたエラーはキャプチャされるけど、止まらずに実行を継続するようですね」「これはテストで便利なんでしょうか?」「キャプチャするけどraiseしないみたいなテストコードは一般にはあまり書かないかな...と思ったけど、よく見ると、これは割と最近(Rails 7.0)入ったErrorReporterの一部なので、あくまでエラーレポートのための機能なのか」「あ、そっちでしたか!」「上のコードで生成しているエラーオブジェクトはエラーレポート用なので、raiseするかどうかとは関係ないヤツですね: エラーレポートならこの機能が欲しくなるのもわかる👍」

参考: Rails アプリケーションのエラー通知 - Railsガイド
参考: Rails API ActiveSupport::Testing::ErrorReporterAssertions

🔗 GitHub Actionsワークフローで実行済みのRuboCopをキャッシュする機能を追加

GitHub ActionsワークフローテンプレートのRuboCopジョブにRuboCopキャッシュ復元機能を追加。

Lovro Bikić
同Changelogより

このプルリクを#54703 (コメント) (cc @byroot@Earlopain)のフォローアップとしてオープンする。

GitHub ActionsアプリとプラグインのワークフローテンプレートのRuboCopジョブに、RuboCopキャッシュの復元機能を追加する。目的は、実行するワークフロー同士で可能な限りキャッシュを共有することでRuboCopチェックを高速化すること。この高速化は、コードベースが大きくなるにつれてはっきり現れる (個人的な例: 自分が作業しているコードベースでは、キャッシュなしでRuboCopの違反をスキャンするのに8分かかるが、以後のキャッシュあり実行では約40秒で済む)。

この実装の理由は、こちらに詳しい(後世のためのWaybackリンク)。
重要なポイントを以下にまとめる。

  • このキャッシュは、「GitHub ActionsランナーのOS」「Rubyバージョン」「コードベース内の任意の.rubocop.ymlファイル」「Gemfile.lock」のいずれかが変更されると無効化される。これは、ランナーOSの変更時にキャッシュを無効化するためのRuboCopのキャッシュの有効性ルールとキャッシュアクションのプラクティスに厳密に沿っている。

  • デフォルトブランチ(master/main)では、ワークフローを実行するたびに新しいキャッシュエントリが作成される。これにより、RuboCopキャッシュが最新の状態になる。このように実装した理由は、GitHub Actionsキャッシュはイミュータブル(不変)であり、それを「更新」する唯一の方法は新しいエントリを作成することとなっているため。

  • デフォルト以外のブランチでは、このキャッシュはベースブランチから復元され(利用可能な場合)、RuboCopの実行終了後に新しいエントリとしてそのブランチに保存される。これらのブランチには単一のキャッシュが存在する(無効化が発生しない場合: 最初の箇条書きを参照)。

これらのブランチで単一のキャッシュを使うことを選択したのは、キャッシュストレージの使用量増加と、以後のコミットで多数のソースファイルが変更された場合にRuboCopのチェック速度が若干低下する(これらのファイルに対するRuboCopのキャッシュが無効化され、新しいキャッシュが作成されるため)こととのトレードオフの結果である。
デフォルトブランチでは、コミットごとに新しいキャッシュエントリを作成して最新の状態に保つ必要があるが、それ以外のブランチですべてのコミットをキャッシュするのは費用対効果が低いと思われる(特にコミット間で大きな変更がない場合)。

GitHub Actionsの古いキャッシュエントリは、7日間以上利用されない場合は削除される
ユーザーは、キャッシュエントリを削除するためのカスタムワークフローを作成してキャッシュをより早い段階で削除することも可能。
同PRより


つっつきボイス:「GHAって何だろうと思ったらGitHub Actionsでした」「RuboCopの実行結果のキャッシュをtmp/rubocopに保存しているんですね: コードベースが大きくなればなるほどRuboCopの実行は重くなるので、GitHub ActionsでRuboCopの実行結果をキャッシュできるならぜひ使いたい👍」「CIで時間を節約できる機能はありがたいです🙏」

# railties/lib/rails/generators/rails/app/templates/github/ci.yml.tt#L48
<%- unless skip_rubocop? -%>
  lint:
    runs-on: ubuntu-latest
+   env:
+     RUBOCOP_CACHE_ROOT: tmp/rubocop
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: .ruby-version
          bundler-cache: true

+     - name: Prepare RuboCop cache
+       uses: actions/cache@v4
+       env:
+         DEPENDENCIES_HASH: ${{ hashFiles('.ruby-version', '**/.rubocop.yml', 'Gemfile.lock') }}
+       with:
+         path: ${{ env.RUBOCOP_CACHE_ROOT }}
+         key: rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-${{ github.ref_name == github.event.repository.default_branch && github.run_id || 'default' }}
+         restore-keys: |
+           rubocop-${{ runner.os }}-${{ env.DEPENDENCIES_HASH }}-

      - name: Lint code for consistent style
        run: bin/rubocop -f github

「ちなみにGitHub Actionsは無料枠だとこうしたキャッシュなどの容量が割と小さいので、自分で消さないと容量不足になったりしがち」「必要なら課金しないとですね」

🔗 PostgreSQLとSQLiteでOUTER JOINの結果をupdate_allするときにself-joinするようになった

PostgreSQLとSQLiteのUPDATEは、OUTER JOINON句内の更新済みテーブルを参照する場合、クエリを中断せずにJOIN条件をWHERE句に移動できない。そのため、現在の実装ではサブクエリを使うしかない。これは、より積極的な操作で対応できる。更新済みテーブルを主キーのself-join(自己結合)でFROM句に複製する。

UPDATE "products" SET ...
LEFT JOIN "categories" ON "products"."category_id" = "categories"."id"
  ... 他のjoin ...
WHERE ...

つまり、上は以下に変換される。

UPDATE "products" AS "alias" SET ...
FROM "products"
LEFT JOIN "categories" ON "products"."category_id" = "categories"."id"
  ... 他のjoin ...
WHERE "alias"."id" = "products"."id" AND ...

この変換により、"products"."?"が参照するテーブルが変更される。最上位のFULL|RIGHT OUTER JOINは、LEFT|INNER JOINに変換される。
更新コンテキストを考慮すれば、これは問題ないはず。

PostgreSQLでは、サブクエリと同様に、クエリプランにインデックススキャンステップが追加される。

残念ながら、この方法では曖昧さが生じる可能性がある。
SETまたはWHEREが対象テーブルの未修飾カラムを参照している場合、候補となるテーブルが2つ存在するため、曖昧さが生じる。これはActive Recordで生成するノードでは発生しないはずだが、手動でノードを作成するユーザーに影響する。
同PRより


つっつきボイス:「追加されたテストコード↓を見ると、left_outer_joinswhereで受け取ったActiveRecord::Relationオブジェクトに対してupdate_allを実行できるようにしたということのようですね」「なるほど」「プルリクにも書かれているように、OUTER JOINを使うとテーブル名が一意でなくなったりするんですよね: やりたいことはわかったけど、個人的にはこういう怖そうな書き方よりも、従来のようにidリストをpluckなどで取り出してからサブクエリでやる方がいいかも」

# activerecord/test/cases/relation/update_all_test.rb#174
  def test_update_all_with_left_outer_joins_can_reference_joined_table
    pets = Pet.left_outer_joins(:toys).where(toys: { name: ["Bone", nil] })

    assert_equal true, pets.exists?
    assert_equal pets.count, pets.update_all(name: Arel.sql("COALESCE(toys.name, 'Toyless')"))
    assert_equal "Toyless", Pet.where.missing(:toys).first.name
    assert_not_equal "Toyless", Pet.joins(:toys).first.name
  end

🔗 ブラウザキャッシュ制御用のmust-understandディレクティブを追加

RFC 9111に基づいてmust-understandディレクティブを実装。

このmust-understandディレクティブは、キャッシュがレスポンスのステータスコードのセマンティクスを理解しなければならないこと、理解できない場合はレスポンスを破棄するよう指示する。このディレクティブは、キャッシュの適切な動作を保証するために、必ずno-storeと併用する必要がある。

class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])

    if @article.special_format?
      must_understand
      render status: 203 # Non-Authoritative Information
    else
      fresh_when @article
    end
  end
end

heka1024
同Changelogより


つっつきボイス:「must-understandはHTTPの新し目のヘッダーに関連していそう: 名前から挙動が想像しにくいけど、RFC 9111をざっと見た限りでは、HTTPクライアント(ブラウザなど)がステータスコードに応じたキャッシュ要件を正しく理解できない場合はキャッシュするなと指示するためのディレクティブらしいので、いずれにしろキャッシュ制御関連の機能ですね」「また知らないヘッダーが登場した...」

must-understandレスポンスディレクティブは、レスポンスのキャッシュを、そのレスポンスのステータスコードの要件を理解し、それに準拠するキャッシュのみに制限する。
must-understandディレクティブを含むレスポンスには、no-storeディレクティブも含めるべき(SHOULD)。
must-understandディレクティブを実装したキャッシュが、このディレクティブを含むレスポンスを受信した場合、キャッシュがステータスコードのキャッシュ要件を理解し、実装している限り、no-storeディレクティブを無視すべき(SHOULD)。
RFC 9111 - HTTP Cachingより

参考: must-understand -- Cache-Control - HTTP | MDN

Cache-Control: must-understand, no-store

参考: Cache-Control: must-understand ディレクティブとは何か | blog.jxck.io

🔗 Active Recordにwith_default_isolation_levelが追加

大規模なアプリケーションのデータベースを新しいデータベース分離レベルに移行する場合、アプリ内の領域ごと(またはベースコントローラごと)にデフォルトの分離レベルを適用することを検討することになる。

新しいレベルとブロックを受け取る#with_default_isolation_levelのようなメソッドがあれば、このような移行作業が大幅に軽減されることが見込まれる。

例:

class ApiV2Controller < ApplicationController
  around_action :set_isolation_level

  def set_isolation_level
    Product.with_default_isolation_level(:read_committed) do
      yield
    end
  end
end

# ApiV2Controllerのサブクラスであるすべてのコントローラで新しい分離レベルを強制的に取得させる

マルチデータベースアプリで適切に動作させるため、デフォルト値をスレッド/Fiber変数ではなく、コネクションオブジェクトに保存することを選んだ。
同PRより


つっつきボイス:「データベースの分離レベルというと...」「データベースのトランザクション分離レベルは、とてもよく使われる概念ですね: 以下はPostgreSQLの場合↓ですが、設定方法や実際の細かい振る舞い(phantom readなどの対応マトリクス)についてはRDBMSごとに違っています」「そうでした、READ COMMITTEDとかREAD UNCOMMITTEDみたいな指定のことですね」

参考: SET TRANSACTION -- PostgreSQL 16.4文書

-- https://www.postgresql.jp/document/16/html/sql-set-transaction.htmlより
SET TRANSACTION transaction_mode [, ...]
SET TRANSACTION SNAPSHOT snapshot_id
SET SESSION CHARACTERISTICS AS TRANSACTION transaction_mode [, ...]


--ここでtransaction_modeは以下のいずれかです。

    ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
    READ WRITE | READ ONLY
    [ NOT ] DEFERRABLE

「トランザクション分離レベルは要件に応じてセッション単位で変えたりすることがありますが、このプルリクはトランザクション分離レベルを#with_default_isolation_levelで明示的にブロックレベルで指定できるようにしたということですね」「なるほど」「マルチデータベースでも動くように、この設定をコネクションオブジェクトに保存しているのもごもっとも」

参考: トランザクション分離レベル - Wikipedia

RSpec でデータベースの「トランザクション分離レベル」の挙動を確認する

🔗 SessionsController#destroyテンプレートのリダイレクトステータスを302から303に修正

動機/背景

Rails 8.0で導入された新しい認証ジェネレーターで生成されるSessionsControllerは、destroyアクションで302リダイレクトレスポンスを返す。これは、リダイレクト先に同じHTTP verbを適用するか、GETリクエストに変更するかがブラウザ任せになってしまうため、避けるべき。

関連するHTML仕様はここに記載されている。
この問題はRails APIドキュメントでも言及されており、いわゆる「二重削除」の潜在的な原因として挙げられている。

テストの結果、多くの最新ブラウザはHTTP verbをGETに変更することが確認されているが、この振る舞いは変更される可能性があるため、HTTP仕様を尊重するのが正しい判断だと思われる。

詳細

修正されるissue: #54848(自分がオープンした)

このプルリクは、ジェネレーターが生成するSessionsController#destroyアクションテンプレート内のリダイレクトステータスコードを302から303に変更する。

追加情報

実験ベースでは、以下のブラウザがHTTP verbをGETに変更することが示されている。

  • Firefox 136.0.4
  • Chrome 134.0.6998.89
  • Safari 18.3.1
    同PRより

つっつきボイス:「destroyアクションのリダイレクト後ステータスは303(see other)が基本だけど、SessionsControllerを生成したときのステータスコードがテンプレートで指定漏れで302(Found)になっていたんですね」「修正はわずか1箇所だけでした↓」

# railties/lib/rails/generators/rails/authentication/templates/app/controllers/sessions_controller.rb.tt#L17
  def destroy
    terminate_session
-   redirect_to new_session_path
+   redirect_to new_session_path, status: :see_other
  end
end

RFC 9110↓によると、ステータス302の場合はブラウザがPOSTのリダイレクトをGETに変更しない可能性もあるのがよろしくないんでしょうね」「変更してもいいということはしなくてもいいということか...」「ステータス303によるPOSTのリダイレクトは必ずGETに変更されるので望ましいということですね」

注: 歴史的な理由で、ユーザーエージェントは後続のリクエストでリクエストメソッドをPOSTからGETに変更してもよい(MAY)。この振る舞いが望ましくない場合は、代わりにステータスコード307(Temporary Redirect)を利用可能。
302 Found -- RFC 9110: HTTP Semanticsより

参考: 302 Found - HTTP | MDN
参考: 303 See Other - HTTP | MDN

「ブラウザがPOSTのリダイレクトをPOSTのままにすることは普通ないと思いますし、現実のメジャーなブラウザも302リダイレクトでPOSTをGETに変換してくれてはいるようですが、仕様で確定していないブラウザの振る舞いを当てにするよりは、仕様に沿った振る舞いになるステータスコードを使う方がよいでしょうね👍」

🔗 Cache#read_counterCache#write_counterが追加

  • Cache#read_counterCache#write_counterを追加
Rails.cache.write_counter("foo", 1)
Rails.cache.read_counter("foo") # => 1
Rails.cache.increment("foo")
Rails.cache.read_counter("foo") # => 2

Alex Ghiculescu
同Changelogより

関連: #54821 (comment)

このプルリクは、Rails.cacheに2つの新しいメソッドread_counterwrite_counterを導入する。

これらのメソッドの目的は、increment/decrementされている値を直接読み書きすること。これは、カウンタのread/write呼び出しでraw: trueを渡さなければならないというコードの臭いを回避するため(これは一部のキャッシュ実装でのみ必要)。
同PRより


つっつきボイス:「これはカウンタキャッシュをread_counterwrite_counterで即値(immediate value)を手軽に読み書きできるようにしたということですね」「ここで言うコードの臭いとは何でしょうか?」「readwriteで即値を読み書きするときに毎回raw: trueを指定しないといけないのがあまりよくないということだと思います」「なるほど、いちいちraw: trueを指定しなくてもread_counterwrite_counterだけで読み書きできるようにしたということなんですね」

🔗Rails

🔗 未完成の機能を隠す(Ruby Weeklyより)


つっつきボイス:「フィーチャーフラグの話かなと思ったら、もっとシンプルな記事でした」「環境変数で動作モードをチェックしてyieldするだけという素朴な方式ですね: 環境に依存するコードをアプリケーションに書くのはあまりいいことではないけど、環境の代わりにコンフィグから読み込むようにすれば改善できますね」「なるほど」

# 同記事より
# app/helpers/application_helper.rb
module ApplicationHelper
  def under_construction
    if Rails.env.development?
      yield
    end
  end
end

🔗 RubyUI: PhlexベースのUIコンポーネント集(Ruby Weeklyより)


つっつきボイス:「ViewComponentのライバルでphlex-railsというPhlexをベースにしたコンポーネントがありますけど、それをベースにした無料のUIコンポーネント集で、ビルドが高速なのが売りのようです」「現代だとこういうフロントエンドをどこまでRailsでやるかというのはありますが、UIコンポーネントは一通り揃っているようですし、使ってみたい人にはいいかも👍」

phlex-ruby/phlex-rails - GitHub


RubyUIより

「ViewComponentは、Railsだけで完結させるなら十分ありだと思うんですが、現代のフロントエンドと実装方法がまったく異なるのもあってか今ひとつ盛り上がっていない」「ViewComponent、筋はいいと思うんですけどね」

実践ViewComponent(1): 現代的なRailsフロントエンド構築の心得(翻訳)

🔗 rubyevents.org: Ruby関連の直近・今後のイベントを一望できるサイト(Ruby Weeklyより)


つっつきボイス:「この間取り上げたFindyさんのテックカンファレンス情報の海外版といった趣です」「Ruby関連のイベント情報を集約したサイトですね: トーク別登壇者別開催日別のビューもあって便利そう👍」

Balkan Ruby 2025って本当にバルカン半島(ブルガリア)で開催されるんですね」「RubyKaigi 2025はまだ個別のトークが登録されてないけど、これからかな」「ここでコントリビュート募集していますね」


rubyevents.orgは、どうやらRubyVideo.devがリニューアルしたようです↓。

🔗 その他Rails


つっつきボイス:「Railsの作者DHHがいる37signalsがジュニア開発者を募集したら2,200人も応募があったそうです」「この人数の応募者をAIを使わずに人力でチェックするのは大変そう」

🔗Ruby

🔗 Ruby 3.5.0 preview1リリース(Ruby公式ニュースより)


つっつきボイス:「RubyKaigi 2025の会期中に3.5.0 preview1がリリースされていました」「以下は言語内部の小さな変更ですね: *nilnil.to_aを呼ばないようにすることで配列がアロケーションされないようにするとともに、**nilnil.to_hashを呼ばない振る舞いと一貫させるようにした」

「Unicodeのバージョンも15.1.0に上がるんですね🎉」「RubyKaigi 2025のオープニングキーノートでima1zumiさんが話していたヤツですね↓」

🔗 ZJIT


つっつきボイス:「RubyKaigi 2025でShopifyのMaximaさんが発表した新しいJITであるZJITが、これも会期中にマージされました」「YJITに続けて新たにZJITを投入するという決定がすごい」「しかもYJITの開発も停止することになったそうです(バグ修正は継続)」「なるほど、YJITは当面使われ続けることになりそうですね」

YJIT: CRuby向けの新しいJITコンパイラを構築する(翻訳)

🔗JavaScript

🔗 書籍『JavaScript for Rails Developers』


つっつきボイス:「翻訳記事でもお馴染みのRails Designerが最近出した書籍で、Hotwireを使い倒せばここまでやれるという感じの内容のようです」「書籍のプレビューをざっと見た限りでは、Stimulusの内部にも言及しているようですね: 特にStimulusはこういう体系的な解説があまり出回ってなさそうなので、そのあたりをまとめて読めるのはよさそう👍」「ボリュームディスカウントもやっていますね(5人で$169)」

Railsフロントエンド: ViewComponent+Tailwind CSS+Hotwireの便利技8つ(翻訳)

なお、同サイトにはこんなキャッチコピーがありました↓。

Finally, JavaScript explained while keeping my Ruby soul intact.
https://javascriptforrails.com/より

🔗言語/ツール/OS/CPU

🔗 wrkflw: GitHub Actionsをローカルでバリデーション・実行できるRust製ツール

bahdotsh/wrkflw - GitHub


つっつきボイス:「この間も話題に出たactも類似のツールだけど割と使いにくいので(ウォッチ20250409)、wrkflwでうまくやれるんだったら嬉しい👍」「READMEにあるTUIって何だろうと思ったらText User Interfaceのことみたいです↓」「昔からncursesなどでやっていたようなテキストベースのUIのことですね」

nektos/act - GitHub

参考: ncurses - Wikipedia


なお、取り急ぎ自分のMacbook環境で手元のRails 8アプリを試してみたところ、バリデーションは一瞬で終わりますが、実行はエラーになりました。後で--verboseなどでチェックしてみます。

🔗 benjdd.com/languages2: プログラミング言語の速度比較サイト


つっつきボイス:「プログラミング言語の速度を比較するという、よくあるサイトですが、ちょうどRubyKaigi 2025でもZJITのMaximaさんが以下のバージョン↓をネタとして軽く引き合いに出していました」「話のタネにする分には楽しいヤツですね」

「FORTRANが意外に上位にいるけど、こういうのはコンパイラの性能次第で大きく変わりがち」「language2にはYJITありのRubyも追加されてますね」「Kotlinは実質Javaだけあって同じぐらい速い」「単純なループはCやRustがやはり強い」

「あれ、このBunってnpmみたいなJavaScriptのパッケージマネージャかと思っていたけど、言語でしたっけ?」「公式サイトを見ると、JSランタイムも含めたオールインワンとなっていますね↓」「Bunにランタイムも入っているとは知らなかった...」

参考: Bun — A fast all-in-one JavaScript runtime

「Bunみたいなのがありなら、JRubyなども入れていいのでは」「JRubyはJavaのJVMで動くから速そうですよね」

参考: Home — JRuby.org -- 現在Ruby 3.4コンパチブルと表示されています


今回は以上です。

バックナンバー(2025年度第2四半期)

週刊Railsウォッチ: bin/ciが追加、HashWithIndifferentAccessへの変換がスキップ可能にほか(20250409)

ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。

Ruby 公式ニュース

Rails公式ニュース

Ruby Weekly


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。