- Ruby / Rails関連
週刊Railsウォッチ: Rubyに新しくRJITがマージされた、Shopifyのタスク管理gem maintenance_tasksほか(20230322)
こんにちは、hachi8833です。今回のRailsウォッチは単品でお送りします。
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 Active Record Encryptionのダイジェストアルゴリズムを変更可能になった
このプルリクは、Active Record Encryptionのダイジェストアルゴリズムを設定する新しいオプションを追加する。Rails 7.1からはSHA-256を新しいデフォルトとして設定し、従来バージョンではSHA-1を設定する。
これはこの問題への再挑戦である(#42929を参照)。この最初のプルリクのことを忘れていたが、マージされないままだった🙄。
Rails 7は新しいデフォルトでSHA-256を使うようになった。ただしこのバグのせいで、Active Record Encryptionは、この変更で意図せず修正されるまでSHA-1を使い続けていた。この修正は最近のものなので、7.1より前についてはSHA-1をデフォルト設定にするのが合理的である。
このことから、Active Record Encryptionでダイジェストアルゴリズムを修正するメカニズムが有用である理由が示される。ダイジェストを変更すると既存の暗号化済みメッセージを復号できなくなるので、Railsで新しいダイジェストアルゴリズムを選ぶときにダイジェストが不用意に変更されないようにしたい。
これは以下の新しいオプションで設定できる。
config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256
最初にレポートした@boomer196、#44540のトラブルシュートで大きく貢献した@georgeclaghorn、そしてこのバグを露呈する変更(#44540)を行った@matthewdに感謝したい。
cc: @matthewd
同PRより
つっつきボイス:「Active Record暗号化機能のデフォルトのダイジェストアルゴリズムがSHA-1だったんですね」「Rails 7.1からはSHA-256になるけど、SHA-1も引き続き選べるようにしておかないと、既存のSHA-1ダイジェストを使って暗号化したデータがすべて復元できなくなってしまう」
参考: SHA-1 - Wikipedia
参考: Active Record と暗号化 - Railsガイド
🔗 rails new
の--css sass
でdartsass-railsが使われるようになった
Railsアプリを生成するときに
--css sass
を指定すると、常にcssbundling-rails
のSassが使われる。今ならdartsass-rails
があるのだから、--css tailwind
を指定すればtailwindcss-rails
が使われるのと同じように、Node.jsの不要なdartsass-rails
を使う方がよいだろう。
同PRより
つっつきボイス:「今まではrails new
で--css sass
を指定してもdartsass-railsじゃないSassがインストールされてたのか」「dartsass-railsならバイナリ実行版のDartsassがインストールされるのに、cssbundling-railsでインストールされたらもったいない」「--css tailwind
は既にできてたんですね」「7.1がリリースされたら関連の過去記事を更新します」
🔗 has_many :through
関連付けで複合キーの設定をサポート
このプルリクは、#47230と同様に、複合
query_constraints
コンフィグを利用するhas_many :items, through: :items_source
関連付けをサポートする。この修正内容は、
query_constraints
の範囲内で行っている作業とほぼ同じ。foreign_key
がArrayの場合に、foreign_key
が単一の値であることを期待するコードが動くようにする。
この場合は、属性リストから複合query_constraints
のすべての部分を除外するようにthrough_scope_attributes
を変更している。
追加したテスト
壊れたユースケースをカバーするテストを追加したが、壊れたロジックは別のバグ#47485(これは#47534にも依存している)で隠蔽されるため、現時点ではmainブランチでパスする。そういうわけで、上記2つのプルリクを先にレビューしてもらえるとありがたい。それによって、このプルリクのテストが実際にmainブランチで失敗するようになり、提案する修正を加えればパスするようにできる。
別案として、上記のバグがあるにもかかわらずmainで失敗するテストを考える方法もあるが、これは新しいモデルのセットアップが必要なので、むやみにテストモデルを増やすよりもバグを修正する方にしたい。
同PRより
つっつきボイス:「今回サポートが追加された複合キーはhas_many :through
が対象なんですね」「複合外部キーは#47230でマージ済みですし、複合キーの対応が進んでいるのは嬉しい👍」
参考: Composite key - Wikipedia -- 複合キー
🔗 データベース設定のカスタムハンドラを登録可能になった
データベース設定をカスタムメソッドに応答させたい場合に、カスタムハンドラを登録可能にするメカニズムを追加した。これは、Rails以外のデータアダプタやVitessのように、標準の
HashConfig
やUrlConfig
と異なる設定を行いたい場合に有用。以下のデータベースYAMLで、
primary
データベースではUrlConfig
オブジェクトを作成し、animal
データベースではCustomConfig
オブジェクトを作成したいとする。development: primary: url: postgres://localhost/primary animals: url: postgres://localhost/animals custom_config: sharded: 1
カスタムハンドラを登録するには、最初にカスタムメソッドを持つクラスを作成する。
class CustomConfig < ActiveRecord::DatabaseConfigurations::UrlConfig def sharded? custom_config.fetch("sharded", false) end private def custom_config configuration_hash.fetch(:custom_config) end end
次にその設定をイニシャライザで登録する。
ActiveRecord::DatabaseConfigurations.register_db_config_handler do |env_name, name, url, config| next unless config.key?(:custom_config) CustomConfig.new(env_name, name, url, config) end
アプリケーションを起動すると、
:custom_config
キーを持つ設定ハッシュがCustomConfig
オブジェクトになり、sharded?
に応答するようになる。Active Recordがこのカスタムハンドラを利用すべき条件は、アプリケーションが処理しなければならない。
Eileen M. Uchitelle and John Crepezzi
同Changelogより
つっつきボイス:「Vitessって何だろうと思ったらMySQL互換のデータベースクラスタなんですね↓」「直接にはVitessに対応するためのような感じですが、従来は事実上HashConfig
とUrlConfig
しか設定に使えなかったのを、データベース設定用のクラスを書いて差し替え可能にすることで、Vitess以外のカスタムハンドラも登録可能になった👍」「今までだとクラスにモンキーパッチを当てるような形でカスタマイズするしかなかったんですね」「従来はポリモーフィックに設定できそうに見えて設定できなかったのが、今回ポリモーフィックに設定可能になったということだと思います」
参考: Vitess | Scalable. Reliable. MySQL-compatible. Cloud-native. Database.
参考: Kubernetes Forum@ソウル、YouTubeの本番で利用されるVitessのセッションを紹介 | Think IT(シンクイット)
参考: Rails API ActiveRecord::DatabaseConfigurations::HashConfig
参考: Rails API ActiveRecord::DatabaseConfigurations::UrlConfig
- PR: Allow configs_for to accept a custom hash key by eileencodes · Pull Request #47536 · rails/rails
configs_for
の:include_replicas
引数が削除された。今後は:include_hidden
引数を使うこと。
Eileen M. Uchitelleカスタムハッシュキー経由で設定を探索できるようになった
カスタム設定を登録した場合や、ハッシュが特定のキーにマッチする設定を探索したい場合に、
configs_for
にconfig_key
を渡せるようになった。
たとえば、db_config
にvitess
というキーがあれば、このキーにマッチするデータベース設定ハッシュを探索できるようになる。ActiveRecord::Base.configurations.configs_for(env_name: "development", name: "primary", config_key: :vitess) ActiveRecord::Base.configurations.configs_for(env_name: "development", config_key: :vitess)
Eileen M. Uchitelle
同PRより
「この#47536は、明らかに上の#47522と関連する改修ですね」「こちらもVitessが例に使われていますね」
参考: Rails API config_for
-- Rails::Application
🔗 ActionMailer::TestHelper
にdeliver_enqueued_emails
が追加された
ActionMailer::TestHelper
にdeliver_enqueued_emails
が追加された。このメソッドはエンキューされたすべてのメールジョブを配信する。def test_deliver_enqueued_emails deliver_enqueued_emails do ContactMailer.welcome.deliver_later end assert_emails 1 end
同Changelogより
つっつきボイス:「Action Mailer本体の改修かと思ったら、TestHelper
の改修だった: assert_enqueued_jobs
というテストメソッドが既にあるので、それと同じようなインターフェイスで使えるdeliver_enqueued_emails
を追加したようですね↓」
ActionMailer::TestHelper
にはassert_enqueued_emails
メソッドがあるが、これはメーラーの配信ジョブに対してonly
フィルタ付きでassert_enqueued_jobs
を呼び出すだけのものである。このPRではassert_enqueued_jobs
のアナロジーに基づいてdeliver_enqueued_emails
メソッドを導入し、配信ジョブのperform_enqueued_jobs
をフィルタ付きで呼び出す。
同PR詳細より
参考: Rails API ActionMailbox::TestHelper
🔗 ActionDispatch::Static
のヘッダーを小文字に修正
ActionDispatch::Static
で大文字と小文字が混じったヘッダーが使われていて、小文字のヘッダーとマージされる。これは重複ヘッダーを生成してしまう。これを避けるために、小文字ヘッダーを優先すること。
修正: #47456
同PRより
つっつきボイス:「これは修正すべき: このRackミドルウェアが最終的に出力するHTTPヘッダーは"Accept-Encoding"のように大文字で始まる形になりますが、Rackミドルウェアが内部で保持するヘッダーのキーや値は小文字で統一する前提になっているようで、大文字のキーや値が残っていて同じヘッダーが2回出力されていたのが修正されたんですね」「なるほど」
# actionpack/lib/action_dispatch/middleware/static.rb#L124
if content_encoding == "identity"
return precompressed_filepath, headers
else
- headers["Vary"] = "Accept-Encoding"
+ headers["vary"] = "accept-encoding"
if accept_encoding.any? { |enc, _| /\b#{content_encoding}\b/i.match?(enc) }
- headers["Content-Encoding"] = content_encoding
+ headers["content-encoding"] = content_encoding
return precompressed_filepath, headers
end
end
🔗 Docker関連の改修
# railties/lib/rails/generators/rails/app/templates/Dockerfile.tt#L43
<% if using_node? -%>
# Install node modules
COPY package.json yarn.lock ./
-RUN yarn install
+RUN yarn install --frozen-lockfile
参照: #46953
Rubyのどのredisクライアントもこのパッケージを必要としていない。
同PRより
参照: #47479
アプリの大半はLinuxでデプロイされるので、Gemfile.lockをこれらのプラットフォームで初期化しておくとよいだろう。これにより、GitHub Actionsなどのプラットフォームにプッシュするときにユーザーが手動で追加せずに済むようになる。
同PRより
つっつきボイス:「RailsのDocker関連の改修も増えていますね」「Dockerfileのyarn install
に--frozen-lockfile
がなかったのか」「#47492はM1環境でGemfile.lockにplatform << "--add-platform=aarch64-linux"
を追加するけど、コードコメントにはDockerにもaarch64-linux
の追加が必要だろうと書かれていますね」「Dockerのセットアップは各自こだわりがあると思いますが、公式にDocker対応が導入されて叩き台となることで改善が進むのはいいですね👍」
🔗Rails
🔗 sidekiq-iteration: Sidekiqジョブを一時停止・再開可能にする(Ruby Weeklyより)
sidekiq-iterationはSidekiqの拡張で、長時間実行されるジョブの一時停止や再開を可能にし、ジョブの進捗をすべて保存する(ジョブのチェックポイント)。
同リポジトリのREADMEより
つっつきボイス:「こういう実装になっているのは面白い: enumeratorを使う形にすることで、個別のenumrationとenumrationの間でジョブを一時停止・再開可能にしているようですね↓」
# 同リポジトリより
class NotifyUsersJob
include Sidekiq::Job
include SidekiqIteration::Iteration
def build_enumerator(cursor:)
active_record_records_enumerator(User.all, cursor: cursor)
end
def each_iteration(user)
user.notify_about_something
end
end
参考: class Enumerator
(Ruby 3.2 リファレンスマニュアル)
「enumrationとenumrationの間で実行されるフックを以下のようにカスタマイズできるらしい↓」
# 同リポジトリより
class NotifyUsersJob
include Sidekiq::Job
include SidekiqIteration::Iteration
def on_start
# Will be called when the job starts iterating. Called only once, for the first time.
end
def on_resume
# Called when the job resumes iterating.
end
def on_shutdown
# Called each time the job is interrupted.
# This can be due to throttling, `max_job_runtime` configuration, or sidekiq restarting.
end
def on_complete
# Called when the job finished iterating.
end
# ...
end
「READMEを見る限りでは、どのタイミングでも一時停止・再開できるわけではなく、あくまでenumrationとenumrationの間で可能だと思うので、これを有効に活用するには個別のenumration間でトランザクションが完結している必要があるでしょうし、個別のenumerationの処理もできるだけ小さく保つ方がいいでしょうね」「なるほど、時間のかかる単発ジョブ向けではないんですね」「ジョブがトランザクションを終えないうちに一時停止したらデータベースがロックされたままになるので、ジョブをそういう形で中断・再開すべきではありません」
🔗 maintenance_tasks: メンテナンスタスクの管理と実行(Ruby Weeklyより)
つっつきボイス:「ShopifyのRailsエンジンです」「こういうメンテナンスタスク管理が欲しくなるのもわかる: 使い捨てでないメンテナンスタスクはプロジェクトで個別のrakeタスクで手作りされることが多いんですが、maintenance_tasksはそうしたタスクを共通のGUI管理画面で実行・監視できるようにするものですね」「なるほど」
「rakeタスクは現代だと使い勝手があまりよくなくて、たとえば本番環境にコンソールで接続できない場合だとrakeタスクを実行するために工夫が必要になったりしがちですが、GUIでタスクを実行できるとそういうプロジェクトで便利そう」
参考: library rake
(Ruby 3.2 リファレンスマニュアル)
「通常のrakeタスクはプロジェクトのlib/以下に置くものですが、このmaintenance_tasksはapp/tasks/maintenance/ディレクトリ以下に素のRubyで書くようになっている↓」「なるほど、rakeタスクではなく、maintenance_tasks独自のタスクなんですね」
# 同リポジトリより
# app/tasks/maintenance/update_posts_task.rb
module Maintenance
class UpdatePostsTask < MaintenanceTasks::Task
def collection
Post.all
end
def process(post)
post.update!(content: "New content!")
end
end
end
「通常のrakeタスクと同様に、maintenance_tasksのタスクはコマンドラインでも実行できるようになっている↓」「引数も渡せますね!」
# 同リポジトリより
bundle exec maintenance_tasks perform Maintenance::UpdatePostsTask
bundle exec maintenance_tasks perform Maintenance::ImportPostsTask --csv "path/to/my_csv.csv"
「rakeタスクで作ると、引数を渡すときにシェルで誤って解釈されないようにエスケープが必要な場合があるなど、いろいろハマりどころに注意しないといけないんですよ」「たしかに」「タスク作成や実行によさそう👍: おそらくmaintenance_tasksのタスクはRailsのコンテキストできれいに動いてくれるんじゃないかな」
🔗Ruby
🔗 MJITに代わってRJITがRubyにマージされた(Ruby Weeklyより)
Merged a new Ruby JIT to CRuby https://t.co/I3WlpeHdaL
— k0kubun (@k0kubun) March 6, 2023
つっつきボイス:「少し前に新しいRJITというJITがRubyにマージされました」「これは驚き!」「@k0kubunさんが満を持して作り上げたんですね」「コミット数もすごい」「pure-Rubyアセンブラからネイティブコードを生成する形で作り直したんですね: これならCコンパイラやRustコンパイラはたしかに不要」「なるほど」「RJITには今後注目していきたいですね👍」
参考: 実行時コンパイラ - Wikipedia -- JIT
このプルリクは、現在のMJIT実装を"RJIT"という新しいJITに置き換える。
- RJITは純粋なRubyアセンブラからネイティブコードを生成する。
- MJITは実行時にCコンパイラが必要で、YJITはビルド時にRustコンパイラが必要だが、RJITはどちらも不要。
- このためRJITのウォームアップはYJITよりは遅くなる可能性があるが、それでもMJITよりはずっと高速。
- RJITで生成されるコードはYJITのものと極めて近い
- 実際、多くのメソッドはRustコードからRubyに直接変換されたものである
- これにより、Ruby VMからMJIT固有の実装を削除してシンプルにできるようになる
- 必要ならRJITでもYJITの初期実験をある程度行える
動機や詳細についてはFeature #19420を参照。
同PRより
以下はつっつき後に見つけたツイートです。
Writing JIT be like, 1% of the time spent on coding and 99% on debugging.
— k0kubun (@k0kubun) March 22, 2023
🔗 rubygemにgem exec
コマンドがマージ(Ruby Weeklyより)
つっつきボイス:「以前取り上げたgem exec
コマンドがついにマージされました(ウォッチ20230202)」「これはありがたい🎉」
🔗 Rubyクイズ: 配列操作編(Ruby Weeklyより)
つっつきボイス:「この間もVectorLogicブログのActive Record APIクイズを取り上げました(ウォッチ20230221)が、Ruby配列のクイズも公開されていました」「お、やるか」(一同でつっつく)
「今回は複数回答ありに気をつけたけど、制限時間が短いととやっぱり焦るな〜」「ですよね」「回答を全部入力すると正答と解説を見られるんですね」
なお、VectorLogicからブログの翻訳許可をいただいたので、翻訳記事を今後公開します。クイズそのものは翻訳しませんが、クイズ記事を参照している記事も翻訳したいと思います。
今回は以上です。
バックナンバー(2023年度第1四半期)
週刊Railsウォッチ: Wasm Workers Server 1.0、mruby 3.2.0リリース、irbtoolsほか(20230315後編)
- 20230314前編 Devise 4.9のHotwire/Turbo統合に対応する、英国政府のViewComponentほか
- 20230308後編 Ruby30周年記念イベント、37signalsのデプロイツールmrskほか
- 20230307前編 Action Mailerプレビューで全メールヘッダーを表示可能に、rubocop-graphqlほか
- 20230222後編 Ruby 3.2のData#initializeの設計、ruby-openai gemほか
- 20230221前編 Ruby30周年記念イベント、ActiveRecord APIクイズほか
- 20230215後編 Bundler 2.4リリース、RubyKaigi 2023参加募集開始ほか
- 20230214前編 AssumeSSLミドルウェア追加、Fly.ioとRails 7.1のDocker対応ほか
- 20230202後編 ShopifyのYJIT記事、RubyGemsのgem execコマンドほか
- 20230201後編 Ruby 3.2のベンチマーク記事、dry-cliで高度なCLIを作るほか
- 20230131前編 Evil Martiansが使っているgem、JavaScriptガイドが更新ほか
- 20230125前編 2022年のRails振り返り記事、RailsにDocker関連ファイルが追加ほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)