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

週刊Railsウォッチ: Webpackerが公式に引退宣言、『Everyday Rails』日本語版がRails 7に対応ほか(20220124前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

今回は以下の中からChangelogが更新されているものを中心に見繕いました。久しぶりに更新が少ないですね。

🔗 atomic_writeの競合状態の挙動を修正

production環境で見かけたatomic_write関連のバグがある(他にもさまざまなところで見た)。スタックトレースによれば、FileUtils.touchはそのbasedirが存在しなくなったパスを操作していて、それが原因でErrno::ENOENT例外が発生していた。どこかで競合条件が発生しているのかもしれない。いずれにしろFile.probe_stat_inはデバッグには使えそうにないので、atomic_writeが爆発したとしても爆発させるべきではないだろう。
同PRより


つっつきボイス:「privateメソッドprobe_stat_inENOENT例外に対応していなかったのを修正したようですね↓」

# activesupport/lib/active_support/core_ext/file/atomic.rb#L56
  def self.probe_stat_in(dir) # :nodoc:
    basename = [
      ".permissions_check",
      Thread.current.object_id,
      Process.pid,
      rand(1000000)
    ].join(".")
    file_name = join(dir, basename)
    FileUtils.touch(file_name)
+ rescue Errno::ENOENT
+   file_name = nil
  ensure
    FileUtils.rm_f(file_name) if file_name
  end

「ときどき見かけるENOENTってどう読むのか謎だったんですが、Error No Entoryの略だったんですね」「Eで始まるのがいかにもPOSIXのエラーっぽいので探してみるとやはりそうだった↓」「POSIXにこんなにたくさんエラーがあるとは知らなかった〜」

参考: Man page of ERRNO

ENOENT
そのようなファイルやディレクトリはない (POSIX.1-2001)
通常は、このエラーは、指定されたパス名が存在しないか、パス名のディレクトリプレフィックスの構成要素のいずれかが存在しないか、指定されたパス名が壊れた (dangling) シンボリックリンク、の場合に発生する。
linuxjm.osdn.jpより

参考: POSIX - Wikipedia

🔗 preload_link_tagrel: 'modulepreload'を使うよう修正

このプルリクは、preload_link_tagmoduleスクリプトのプリロードをサポートするように修正する。
type: 'module'が指定されると、rel: 'preload'ではなくrel: 'modulepreload'を使うようになる。
背景
従来は、preload_link_tagでのタグのレンダリングとレスポンスのearly hintの両方について、rel=preloadが(moduleスクリプトを含む)すべてのスクリプトで使われていた。
型が合わない(スクリプトはプリロードされるがモジュールはインクルードされていない)ので、ブラウザはこのプリロードされたリソースを無視する
同PRより


つっつきボイス:「お、今まではrel属性にpreloadを指定してJavaScriptファイルをプリロードしていたのをmodulepreloadでやるように修正されたのね↓」「JavaScript用のリンク種別なのか」

# actionview/lib/action_view/helpers/asset_tag_helper.rb#L319
      def preload_link_tag(source, options = {})
        href = path_to_asset(source, skip_pipeline: options.delete(:skip_pipeline))
        extname = File.extname(source).downcase.delete(".")
        mime_type = options.delete(:type) || Template::Types[extname]&.to_s
        as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
        crossorigin = options.delete(:crossorigin)
        crossorigin = "anonymous" if crossorigin == true || (crossorigin.blank? && as_type == "font")
        integrity = options[:integrity]
        nopush = options.delete(:nopush) || false
+       rel = mime_type == "module" ? "modulepreload" : "preload"

        link_tag = tag.link(**{
-         rel: "preload",
+         rel: rel,
          href: href,
          as: as_type,
          type: mime_type,
          crossorigin: crossorigin
        }.merge!(options.symbolize_keys))

-       preload_link = "<#{href}>; rel=preload; as=#{as_type}"
+       preload_link = "<#{href}>; rel=#{rel}; as=#{as_type}"
        preload_link += "; type=#{mime_type}" if mime_type
        preload_link += "; crossorigin=#{crossorigin}" if crossorigin
        preload_link += "; integrity=#{integrity}" if integrity
        preload_link += "; nopush" if nopush
        send_preload_links_header([preload_link])
        link_tag
      end

参考: リンク種別: modulepreload - HTML: HyperText Markup Language | MDN

🔗 ServerTimingミドルウェアのドキュメント更新

  • 0547b16で入ったActionDispatch::ServerTimingのAPIドキュメントを追加
  • rails_on_rack.mdガイドで抜けていたミドルウェアや解説を更新
    同PRより

つっつきボイス:「これはドキュメントの更新もれの修正ですね👍」「そういえばServerTimingミドルウェアは少し前に追加されていましたね(ウォッチ20211011)」「そうそう、Server-Timingはパフォーマンス測定用にレスポンスに追加できるヘッダー」

参考: rails/server_timing.rb at 0547b1646d09a80d0685a03c932fb0ba09c3e614 · rails/rails
参考: Server-Timing - HTTP | MDN


後で調べると、Rails 7のproduction環境とtest環境ではデフォルトでServerTimingがオフになっていました↓(development環境ではオンでした)。

$ RAILS_ENV=production dip rails middleware
Creating enno_docker_backend_run ... done
use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use Rack::Runtime
use RailsSameSiteCookie::Middleware
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::Callbacks
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ContentSecurityPolicy::Middleware
use ActionDispatch::PermissionsPolicy::Middleware
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
use Rack::TempfileReaper
use Rack::Attack
use Rack::Attack
run App::Application.routes

Rails 7のdevelopment環境でChrome DevToolsの[Network]タブを見ると、こんな感じで出力されていました↓。DevToolsの[Timing]タブをクリックするとグラフも表示されます。

Server-Timing: cache_read.active_support;dur=0.063720703125, cache_write.active_support;dur=0.029296875, start_processing.action_controller;dur=1.03662109375, sql.active_record;dur=4.445556640625, !render_template.action_view;dur=27.783203125, render_partial.action_view;dur=16.796875, render_template.action_view;dur=12.628173828125, render_layout.action_view;dur=22.005126953125, process_action.action_controller;dur=68.377197265625

🔗Rails

🔗 Webpackerが引退


つっつきボイス:「とうとうWebpackerが引退とは」「Rails 7リリースやここ最近のRailsの動きから、うすうす予感していた人は多いでしょうね」「Twitterを見ていてもそんな印象でした」

つっつき後に、Webpackerは以下のshakapackerに引き継がれると教わりました↓。

shakacode/shakapacker - GitHub

「Rails 7で入ったjsbundling-railsを使えば↓引き続きJavaScriptのバンドリングはできるので、TypeScriptなどを使う人はjsbundling-railsに移行してくれということでしょうね」「移行ガイドも出ていますね」

rails/jsbundling-rails - GitHub

参考: Webpacker 5->jsbundling-rails移行ガイド jsbundling-rails/switch_from_webpacker.md at main · rails/jsbundling-rails

「ちなみにHotwireのdiscussionサイトで見つけたんですが、UJSはRails 7では削除されていないそうです↓」「互換性についてだけturbo-railsのREADMEに書いておいたのね: rails-ujsはTurboと共存できるように名前空間が別になっていますね👍」

We stopped removing UJS, and instead just document the compatibility: https://github.com/hotwired/turbo-rails#compatibility-with-rails-ujs
discuss.hotwired.devよりDHHのコメント

参考: Rails UJS/Turbolinks -> Turboアップグレードガイド turbo-rails/UPGRADING.md at main · hotwired/turbo-rails


なお、Rails 7でbin/rails new --helpすると以下のようにJavaScriptビルドツールの選択肢が表示されます。importmapwebpackesbuildrollupから選択できます。なおデフォルトのimportmapの場合はjsbundling-railsはインストールされず、importmap-railsがインストールされます。

-j, [--javascript=JAVASCRIPT] # Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]

Rails 7: importmap-rails gem README(翻訳)

なお私はesbuildが速いという評判を見かけたのでRails 7でjsbundling-rails+esbuildにしてみました。

参考: Node.js のビルドツール「esbuild」について!
参考: [Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない | Kabuku Developers Blog

🔗 メモリリークとmemory bloat(Ruby Weeklyより)

memory bloatの日本語定訳が見当たらないので英ママとしました。


つっつきボイス:「メモリリークとmemory bloatの違いを解説するのかな?: メモリリークは完全にバグですが、memory bloatはメモリが非効率な状態になることですね」「なるほど」「実際はmemory bloatであるものをメモリリークと呼ぶのは、仕様をバグと呼ぶようなものかもしれませんね」

参考: メモリリーク - Wikipedia

後で探すとQuoraで見つかりました↓。

参考: (4) プロダクションの Rails サーバーの利用メモリがひたすら増加していくような挙動を観測したとき、どう対応するのがよいですか? - Quora

これは使われないメモリへの参照が残るバグとしてのmemory leakではなくて、ちゃんと開放してるにもかかわらずメモリ使用量が減らないmemory bloatだからです。
jp.quora.comより

「実際、Railsに限らず大規模なデータを扱うサーバーサイドアプリケーションではmemory bloatは多かれ少なかれ生じるものなので、これはもう仕方ないとも言えます」「そうなんですね...」「もちろんメモリ使用量を監視していればmemory bloatに気づけますが」

「記事ではいくつかmemory bloat検出用ツールが紹介されていますね↓」

brynary/rack-bug - GitHub

binarylogic/memorylogic - GitHub

noahd1/oink - GitHub

「ちなみに3つ目のoinkは英語でブタの鳴き声を表す擬音ですね」「へ〜」「もしかするとmemory bloatを監視・検出するツールの動作を、トリュフを見つけるブタの鳴き声になぞらえているのかなと想像してみました」「あ〜それありそう」

参考: oinkの意味・使い方・読み方|英辞郎 on the WEB

🔗 rails_same_site_cookie: RailsのSameSite cookieを自動設定

pschinis/rails_same_site_cookie - GitHub

参考: SameSite cookies - HTTP | MDN
参考: いまさら聞けないSameSite CookieとGoogle Chrome 80 | ecbeing


つっつきボイス:「私のRailsアプリ(5.2)でSameSite cookieを自力で設定するのが面倒だったんですが、以下の記事でこのrails_same_site_cookieを知って、作り中のRails 7アプリでこれを入れてみようかなと検討中です」「ほほぉ〜」

「このgemを入れたらすぐ使える感じですね」「はい、これといったコンフィグなしで導入できて、期待どおりにローカル環境ではSameSite=Noneのcookieでsecureがオンになってくれました」「なるほど、Rackの機能を使わずに独自にcookieを設定しているようですね: この機能がRailsに公式に入ってもよさそうな気もしますが、この種のcookieのデフォルト設定は注意が必要なので、Rails本家にはすぐには入らないんじゃないかな🤔」「あ〜そうなのかも」

「Qiita記事でもcookieのテストをUser-Agentごとに書いてる↓: テストを書かないと振る舞いがよくわからなくて困りそうなcookieみたい」「う〜む、もう少し調べます」

# https://qiita.com/jnchito/items/1290a7b1dbf0ccf40c7b より
require 'rails_helper'

RSpec.describe 'Cookies', type: :request do
  describe 'Cookie の SameSite 属性' do
    before do
      user = create :user
      login_as user
    end
    it 'User-Agent指定無しの場合 SameSite=None がつく' do
      get new_user_session_path
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/
    end

    it 'SameSite=Lax がデフォルトになる Chrome 80 では SameSite=None がつく' do
      mac_chrome_80 = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.42 Safari/537.36      '
      win_chrome_80 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.16 Safari/537.36'

      get new_user_session_path, headers: { 'User-Agent' => mac_chrome_80 }
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/

      get new_user_session_path, headers: { 'User-Agent' => win_chrome_80 }
      expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/
    end

    it 'SameSite=Noneの扱いにバグがある iOS12 Safari では SameSite=None がつかない' do
      iphone_ios12_user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1'
      get new_user_session_path, headers: { 'User-Agent' => iphone_ios12_user_agent }
      expect(response.headers['Set-Cookie']).to include '_your_app_session='
      expect(response.headers['Set-Cookie']).not_to include 'SameSite'
    end
  end
end

後で探すと、Railsへのプルリクを1件見つけましたがcloseしていました。上のテストにもあるようにSafariでSameSite=Noneが期待どおりに動かない問題があったようです。

参考: Add SameSite to Cookies by cfabianski · Pull Request #28297 · rails/rails -- closed
参考: cookieのSameSite属性 おぼえがき - kasei_sanのブログ -- Safariのバグについて書かれています

🔗 bullet gemがRails 7に対応

flyerhzm/bullet - GitHub


つっつきボイス:「bullet gemがRails 7で動くようになった🎉」「この間まで動きませんでしたが、さっきGemfileでコメント解除したら無事インストールできました」

🔗 mrujs: TypeScriptで書かれたujs

paramagicdev/mrujs - GitHub

参考: Form submit with turbo-streams response, without redirect - General - Hotwire Discussion -- このコメントでmrujsを知りました


つっつきボイス:「Rails 7のフォームで確認ダイアログを出す方法を調べていて見つけました」「モダンujsと書かれているので、ujsの機能をTypeScriptで書き直した感じかな: 確認ダイアログのようなujsの機能をひととおり使えるらしい」「ちなみにjnchitoさんの記事↓のおかげで最終的にTurboで確認ダイアログを表示できたので、mrujsはとりあえず使わずに済みました」

参考: Rails 7.0 + Ruby 3.1でゼロからアプリを作ってみたときにハマったところあれこれ - Qiita

🔗 その他Rails

つっつきボイス:「RSpecによるテスト入門書『Everyday Rails』がRails 7に対応したそうです」「お〜、継続的にアップグレードされているんですね」「無料アップグレードできるのか〜」「期間限定で割引セールですって」「いつ買ったか思い出せないけど電子書籍で買ってたはず...(探す)あった、2014年にLeanpubで買ってた」

「電子書籍をいろんなサイトで買うと後から探すのが大変ですよね」「PDFやepubをどこからダウンロードしたかわからなくなる問題、あるある」


前編は以上です。

バックナンバー(2022年度第1四半期)

週刊Railsウォッチ: Ruby 2.5〜3.1ベンチマーク、Opal 1.4、JRubyが20歳に、2022年のCSSほか(2022018後編)

今週の主なニュースソース

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

Rails公式ニュース

Ruby Weekly


CONTACT

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