こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
- 公式更新情報: Ruby on Rails — This Week in Rails: bug report template changes, new test helpers and more!
- 公式更新情報: Ruby on Rails — Omakase RuboCop, Brakeman, Ruby 3.1+, allow_browser, rate_limit, and more
🔗 url_helpers
モジュールが別のconcernでinclude
されるとエラーになる問題を修正
- PR: Fix inclusion of
url_helpers
module in concern by jonathanhefner · Pull Request #50403 · rails/rails
#46530の続き。
動的に生成される
url_helpers
モジュールはActiveSupport::Concern
である。したがって、それが別のActiveSupport::Concern
でinclude
されると、そのincluded
ブロックは後者のconcern自体が別の場所でinclude
されるまで延期される。この場合、def self.included(base)
のincluded
ブロックがまだ_routes
メソッドを定義していないのでbase._routes
呼び出しがraiseされる。このコミットは、最初に
base
が_routes
に応答するかどうかをチェックすることでエラーを防ぐ。
同PRより
つっつきボイス:「concernの使われ方によってdefer(先延ばし)されたurl_helpers
モジュールがエラーになっていたのはバグ」「base.respond_to?(:_routes)
チェックを追加して修正したんですね↓」
# actionpack/lib/action_dispatch/routing/route_set.rb#L605
def self.included(base)
super
- if !base._routes.equal?(@_proxy._routes)
+ if base.respond_to?(:_routes) && !base._routes.equal?(@_proxy._routes)
@dup_for_reinclude ||= self.dup
base.include @dup_for_reinclude
end
end
🔗 assert_queries_count
、assert_no_queries
、assert_queries_match
、assert_no_queries_match
アサーションを公開
#50281の続き。
最初に導入(公開)されたヘルパーには、以下のような小さい制約がいくつかあった。
- 期待するクエリ数を正確に指定しなければならない
- アプリケーション関連のクエリしか考慮されず、スキーマやトランザクション関連のクエリは常にスキップされる
- マッチャーは1つしか指定できない
これらの制約は通常のユーザーにとってはほとんど問題にならないが、より低レイヤのテストで困ることがある(ある種のgem内部のテストなど)。
たとえば、ある操作を実行したときにインデックスや外部キーが作成されるかどうかをテストできなかった。
修正後は以下のようにテスト可能になる。assert_queries_match(/create index/i, include_schema: true) do do_something end
同PRより
つっつきボイス:「先週Arelで改修されたassert_queries
とassert_no_queries
の続きです(ウォッチ20240117)」「アプリケーションの純粋なクエリ件数しか取れなかったりしたのをスキーマのクエリなども取れるようにするなどして改善したのか」「assert_queries_match
とassert_no_queries_match
も追加されたんですね」「assert_queries
とかだとクエリの何をアサーションするのかわかりにくかったけど、assert_queries_count
とかの方がネーミングとしてもたしかにいい👍」
先週のChangelogも更新されました↓。
以下のアサーションをpublicにした:
assert_queries_count
assert_no_queries
assert_queries_match
assert_no_queries_match
期待される件数のクエリが作成されるというアサーションのため、Rails 内部で
assert_queries_count
やassert_no_queries
が使われている。特定のSQLクエリが作成されたというアサーションには、assert_queries_match
やassert_no_queries_match
が使われている。これらのアサーションがアプリケーションでも利用可能になった。class ArticleTest < ActiveSupport::TestCase test "queries are made" do assert_queries_count(1) { Article.first } end test "creates a foreign key" do assert_queries_match(/ADD FOREIGN KEY/i, include_schema: true) do @connection.add_foreign_key(:comments, :posts) end end end
Petrik de Heus, fatkodima
同Changelogより
🔗 バグレポートのgem版テンプレートを廃止した
コントリビュータが、あるバージョンをテストするために1行変更する場合、必ずしもレポートの種類ごとにファイルが1つずつ必要になるわけではない。
gemバージョンを維持することについての議論で自分が唯一理解できるのは、ユーザーがRailsの特定のバージョンのバグを報告するときは、たいていアップグレード中であるということだ。Railsのmainブランチでアプリケーションをテストするユーザーはほとんどいないだろう。
さらに、gemバージョンのテンプレートがあればCIでmainとstableの両方についてRailsをテストすることになるので、それなりのメリットは一応ある。
しかし私見では、レポートファイルを分けることのコストと、テンプレートをさらに増やすことで生じる混乱の方が、そのメリットを上回ると思う。
(追加のアダプタなどの)レポートのテンプレートを増やしたいというのが自分の動機だが、gemバージョンのテンプレートも維持するとなると、そのリストが肥大化して管理不能になる。
/cc @yahonda @skipkayhil(これについて議論したことがあったと思うので)
同PRより
つっつきボイス:「Railsにバグを報告するときのテンプレートはこれまでgem版(stable)とmain版の2種類だったんですが↓、gem版のテンプレートを廃止したそうです」「stableの方はRailsがアップデートされるたびにテンプレート内のgemのバージョンを更新しないといけなくなるので、テンプレートのメンテの手間を減らすためにテンプレートをmain版に一本化したということのようですね」「この改修を前提に次の#49986の改修が行われていました」
🔗 Action View用のバグレポートテンプレートをサンプルテスト付きで追加
動機/背景
コントリビュータ向けにAction Viewのバグレポートテンプレートを導入し、
ActionView::TestCase
インスタンスの失敗に関するissueを再現できるようにするため。詳細
inline:
キーワードでERBをレンダリングする機能に加えて、サンプルテストにHelpers
モジュールを含めておくことでビューヘルパーを再現スクリプトに組み込む方法を示すようにする。
同PRより
つっつきボイス:「上の#50317の続きです」「Action Viewの特定機能を単体で動かして再現するバグレポート用のテンプレートが新たに追加されたんですね: Action Viewを単体で動かすようなコードは普段書かないので、こういうサンプルコード付きのテンプレート↓があると問題を再現しやすくて助かる👍」
# guides/bug_report_templates/action_view.rb
# frozen_string_literal: true
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
gem "rails"
# If you want to test against edge Rails replace the previous line with this:
# gem "rails", github: "rails/rails", branch: "main"
end
require "minitest/autorun"
require "action_view"
class BugTest < ActionView::TestCase
helper do
def upcase(value)
value.upcase
end
end
def test_stuff
render inline: <<~ERB, locals: { key: "value" }
<p><%= upcase(key) %></p>
ERB
element = rendered.html.at("p")
assert_equal element.text, "VALUE"
end
end
🔗 Active Storageでwebpとavifをインライン配信できるようになった
# activestorage/lib/active_storage/engine.rb#L67
config.active_storage.content_types_allowed_inline = %w(
+ image/webp
+ image/avif
image/png
image/gif
image/jpeg
image/tiff
image/bmp
image/vnd.adobe.photoshop
image/vnd.microsoft.icon
application/pdf
)
つっつきボイス:「Active Storageで配信できるフォーマットが追加されたというシンプルな改修ですね: このactive_storage.content_types_allowed_inline
はブラウザでファイルのURLに直接アクセスしたときに(1)ブラウザでそのまま開く(インライン配信)か、(2)ダウンロードダイアログを開くかをContent-Dispositionヘッダで制御しているのですが、その処理対象のContent-Typeを指定する設定のようですね」「なるほど」「今回はDHHによるコミットが多いんですが、この改修もこの後のDHHによる#50505と関連しているようです」
参考: Content-Disposition
- HTTP | MDN
参考: WebP - Wikipedia
参考: AV1 Image File Format(AVIF) -- AV1 - Wikipedia
webpやavifについては以下のシリーズ記事もどうぞ↓。
🔗 非推奨化された古いRails UJSのコードを削除
- PR: Rails UJS has been deprecated since Rails 7, time to die by dhh · Pull Request #50535 · rails/rails
公式パッケージは引き続き
@rails/ujs
にあり、アセットパイプラインの最終コンパイル済みターゲットにも残される。しかしそれ以外のものはすべて消えるべき。同PRより
つっつきボイス:「Rails UJSでもう消せるものはさすがに消してもいいだろうという判断が下ったんですね」「time to dieか〜」「@rails/ujs
は引き続きメンテされるので大丈夫だと思います👍」
🔗 PWA用のマニフェストファイルとサービスワーカーファイルをデフォルトで追加する
app/views/pwa
から配信され、ERBで動的にレンダリング可能なマニフェストとサービスワーカー用のデフォルトPWAファイルを追加する。生成されるルーティングファイル内のデフォルトルーティングを用いて、これらのファイルを明示的にrootにマウントする。DHH
同Changelogより
つっつきボイス:「PWA(Progressive Web App)といえば、Webアプリをスマホで表示したときにホーム画面にアプリアイコンを追加するのに使いますよね」「ストアに登録しなくてもホーム画面に追加できるヤツですね」「マーケティング方面でPWAがもてはやされてましたね」「PWA関連のファイルをサポートするのは別に構わないんですが、デフォルトで有効にするとPWAの存在に気づかないまま導入されてしまいそうな気がする」「あ〜言われてみればRailsのデフォルトアイコンとかがそのままPWAで使われたりしそう」「デフォルトではPWAはオフにしておいて、オプションで追加できる形でサポートする方がいいのかもしれないと思いました」
参考: Progressive web app - Wikipedia -- PWA
🔗 GitHub CI向けの設定ファイルをデフォルトで追加するようになった
dependabot、brakeman、rubocop、およびテスト実行用のGitHub CIファイルを追加する。
--skip-ci
を指定することでスキップ可能。DHH
同Changelogより
rubocopとbrakemanがRailsのデフォルトになったので、新しいアプリケーションにデフォルトのGitHub CIワークフローファイルを追加するのが合理的だ。これにより、特に初心者がスキャンやlintやテストの自動化を始めやすくなる。これは、私たちが単体テストを行うようになって以来やってきたことを自然な形で現代でも継続するものである。
そういうわけで、よいデフォルト設定を用いて
.github/workflows/ci.yml
と.github/dependabot.yml
を追加する。これは
--skip-ci
でスキップ可能。
- [x] アプリケーションがMySQLまたはPostgreSQL用の場合はデータベースサービスをセットアップする
同PRより
つっつきボイス:「先週取り上げたRubocop設定やbrakeman設定(ウォッチ20240117)に加えて、GitHub CIワークフローファイルもデフォルトで追加されるのは地味にありがたい」「CIのyamlをどこかからコピーしたりしなくて済みますね」「データベースに応じて設定ファイルをif
文で変えるようになっている↓: 個人的にはそこまでしなくてもプロジェクトで普通にカスタマイズすればいい気はするけど、Railsが初めての人向けのサポートとしてよさそう👍」
# railties/lib/rails/generators/rails/app/templates/github/ci.yml.tt
<%- if options[:database] == "sqlite3" -%>
# services:
# redis:
# image: redis
# ports:
# - 6379:6379
# options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
<%- else -%>
services:
<%- if options[:database] == "mysql" || options[:database] == "trilogy" -%>
mysql:
image: mysql
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
<%- elsif options[:database] == "postgresql" -%>
postgres:
image: postgres
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
<%- end -%>
🔗 アプリでの利用を許可するブラウザの最小バージョンをallow_browser
で指定できるようになった
すべてのアクションへのアクセスを許可する(または
only:
やexcept:
で何らかの制約をかける)ブラウザのバージョンを指定する。指定したバージョンを下回る場合、
versions:
に渡されたハッシュまたは名前付きセットで一致するブラウザだけがブロックされる。つまり、それ以外のすべてのブラウザと、User-Agentヘッダーを報告していないエージェントはアクセスを許可されることになる。
ブロックされたブラウザには、デフォルトでpublic/426.htmlのファイルが配信され、HTTPステータス"426 Upgrade Required"となる。
名前のあるブラウザバージョンに加えて、セットになっている
:modern
を渡すことで、WebP画像、Webプッシュ、バッジ、importmap、CSSネスティング、CSS:has
をネイティブにサポートするブラウザのみにサポートを制限することも可能。これにはSafari 17.2以降、Chrome 119以降、Firefox 121以降、Opera 104以降が含まれる。利用する機能をサポートするブラウザのバージョンはhttps://caniuse.comで確認可能。
ActiveSupport::Notifications
を使うことで、ブロックされているブラウザのイベントをbrowser_block.action_controller
というイベント名でサブスクライブできる。例:
class ApplicationController < ActionController::Base # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has allow_browser versions: :modern end class ApplicationController < ActionController::Base # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+. allow_browser versions: { safari: 16.4, firefox: 121, ie: false } end class MessagesController < ApplicationController # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action. allow_browser versions: { opera: 104, chrome: 119 }, only: :show end
同PRより
つっつきボイス:「ユーザーがモダンでないブラウザをアプリで使うとブラウザのアップデートをユーザーに促す機能が入ったそうです」「WebPやimportmapあたりをサポートしないブラウザはモダンではないということになるのね」「HTTP 426 Upgrade Requiredというレスポンスを返せるとは知らなかった」「こういう機能はあっていいと思います👍」
参考: 426 Upgrade Required - HTTP | MDN
🔗 Railsの必須Rubyバージョンが3.1.0以上に変更
従来のRailsでは、新しいメジャーなRubyバージョンのうち古いRubyとの互換性を削除していただけだったが、このポリシーの変更を提案する。今のままでは、EOLになって久しいRubyとの互換性を残すか、さもなければ複数の古いRubyバージョンをまとめて削除するためにRailのメジャーバージョンアップをもっと頻繁に行うことになる。
私見では、現状のインセンティブがうまく調整されていないと思える。新しいマイナーバージョンがEOL(3年経過)になるたびにサポートを打ち切る方がずっといいのではないか。
Rubyはアップストリームへの依存なので、セマンティックバージョニング違反にすらならない。
Rails 7.2は数か月前には計画されていなかったので、今年3月にEOLとなるRuby 3.0は既に削除可能。
FYI: @matthewd(これに関する意見があったと思うので)
同PRより
つっつきボイス:「言われてみれば、これまではRailsのメジャーバージョンがアップデートするタイミングでRubyの必須バージョンをアップデートしていましたね」「従来のアップデートタイミングはシンプルな分わかりやすいと言えばわかりやすいけど、必要ならそれ以外のタイミングでもRubyの必須バージョンをもっと頻繁にアップデートするのはありなんじゃないかな」「ところでRails本体の必須Rubyバージョンを上げるのは大丈夫だと思いますけど、Railsで使うサードパーティgemが組み合わせによってはRubyの必須バージョン変更にすぐ対応しきれないことは割りとあったりするんですよね」「たしかに」
🔗 レート制限APIを追加
RedisとKredis limiter typeを利用したレート制限APIを追加する。
class SessionsController < ApplicationController rate_limit to: 10, within: 3.minutes, only: :create end class SignupsController < ApplicationController rate_limit to: 1000, within: 10.seconds, by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new end
Note: レート制限は、アプリケーションにアクセス可能なRedisと、バンドルでKredis 1.7.0以降に依存する。
同PRより
つっつきボイス:「rack-attack gem↓でやっているようなレート制限をredisの機能を使ってRailsでサポートするようになったんですね: こういう機能はそろそろRailsフレームワーク本体でやってもよさそう👍」
前編は以上です。
バックナンバー(2024年度第1四半期)
週刊Railsウォッチ: Ruby 3.3でYJITを有効にすべき理由、Turbo 8の注意点8つほか(20240119後編)
- 20240117前編 Rails 8マイルストーン、2023年のRails振り返り、Solid Queueほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)