- Ruby / Rails関連
週刊Railsウォッチ: Ruby 3.2.0 Preview 2とRack 3.0リリース、packwerkでアプリコードの境界を強制ほか(20220920)
こんにちは、hachi8833です。RubyKaigi 2022お疲れさまでした。来年5月は長野県松本市ですね。
RubyKaigi 2023: May 11-13, 2023; See you in Matsumoto! #rubykaigi
— RubyKaigi (@rubykaigi) September 10, 2022
🔗Rails: 先週の改修(Rails公式ニュースより)
🔗 Ruby 3.1のerror_highlight機能でエラー発生位置が詳しく表示されるようになった
概要
このプルリクは、Ruby 3.1から利用可能になったerror_highlightを用いて、エラー発生位置を詳しく表示する。対象コード片の行番号だけではなく発生位置の範囲もハイライトされる。
以下はasub
をasuub
とスペルミスした場合の例。この機能は1行の中にメソッドチェインがある場合にさらに有用。
[]
のメソッドチェイン内でundefined method '[]' for nil:NilClass
エラーが発生したときに、どの[]
でエラーが発生したかがひと目で分かる。以下は:articles
を:article
とスペルミスした場合の例。その他情報
このプルリクには以下の2つのステップがある。
Exception#backtrace
よりもException#backtrace_locations
が望ましい(error_highlightが位置情報を特定するのにException#backtrace_locations
が必要)。- エラーが発生した位置を特定するのに
ErrorHighlight.spot
を用いている。error_highlightについて詳しくはFeature #17930: Add column information into error backtraceを参照。
同PRより
つっつきボイス:「この間取り上げたRubyのerror_highlightがRailsのエラー画面に取り入れられました🎉(ウォッチ20220906)」「RubyKaigi 2022でも話されていたトピックですね」
以下はつっつき後に見つけた会期中のツイートです。
error_highlight: user-friendly error diagnostics#rubykaigiA #rubykaigi #RubyKaigi2022 pic.twitter.com/Y4yH3frCVW
— 👨⚖️りさきゃん🧦 (@_risacan_) September 10, 2022
🔗 routes --grep
でパスにマッチするルートをフィルタできるようになる
概要
/users/orhantoy/settings
のようなパスを見ただけでは、どのコントローラとアクションがこのルートに対応するかがわからないこともある。そこで、パスを渡したらどのコントローラとアクションにマッチするかを調べられたら便利だろうと考えた。これは実はブラウザでhttp://localhost:3000/rails/info/routes
を開けば調べられることがわかった(知らなかった!)が、rails routes
コマンドでも同じ機能が使えると便利だろうと思った。例
#45874 (comment)以後の改修では以下のようになる(元の改修では別途--path
オプションを導入していた)
$ bin/rails routes -g /cats/1
Prefix Verb URI Pattern Controller#Action
cat GET /cats/:id(.:format) cats#show
PATCH /cats/:id(.:format) cats#update
PUT /cats/:id(.:format) cats#update
DELETE /cats/:id(.:format) cats#destroy
同PRより
つっつきボイス:「rails routes
の結果をパイプでgrep
につなぐとかは普段からやっているけど、この-gオプションはたとえば/cats/1
を渡すと/cats/:id
で絞り込んでくれるのがいいですね👍」「インテリジェントに絞り込めるんですね」
参考: §6.1 既存のルールを一覧表示する -- Rails のルーティング - Railsガイド
🔗 304 Not ModifiedレスポンスでCSPヘッダーを返さないよう修正
概要
CVE-2022-22577の修正後のRailsは、すべてのレスポンスでCSPヘッダーを送信するようになり、レスポンスにHTMLが含まれていない場合も送信していた。
しかしこれが有害となるケースが1つある。HTMLを含まない304 Not Modifiedを返すとブラウザはCSPヘッダーを更新するが、それ以外の場合はキャッシュ済みHTMLを再利用する。このHTMLにnonceを持つscriptタグが含まれていると、このnonceがCSPヘッダーの新しいnonceとマッチしなくなる可能性がある。これは、
request.session.id.to_s
をnonceジェネレータとして使っていれば通常は問題にならないが、古いSecureRandom.hex(16)
のバリアントを使っている人にとっては引き続き問題となる点に注意。
このプルリクは、304でCSPヘッダーの返送をスキップするというシンプルな修正。ブラウザは元のレスポンスのCSPヘッダーを使い続けるので、これで問題ないはず。
同PRより
w3c/webappsec-csp#161でブラウザの振る舞いがバグではないことが確認できたので、この修正でよいと思う👍
同PRコメントより
つっつきボイス:「CVE-2022-22577は今年4月のセキュリティリリースで修正されていました↓」「#44635ではJSON APIなどのHTML以外のレスポンスを返す場合に一律にCSPヘッダーを返すよう修正されたけど、現在のブラウザの振る舞いを考慮すると304 Not Modifiedのnonceで問題が生じる可能性があるので、304ではCSPヘッダーを返すべきではないと判断されたようですね」
参考: CSP: script-src - HTTP | MDN
🔗 ActiveRecord::Persistence#becomes
をvirtual attributeに適応させる
ソースクラスとターゲットクラスにある属性のセットが異なる場合に、ターゲットに余分にある属性が追加されるように属性を適応させる。
class Person < ApplicationRecord
end
class User < Person
attribute :is_admin, :boolean
after_initialize :set_admin
def set_admin
write_attribute(:is_admin, email =~ /@ourcompany\.com$/)
end
end
person = Person.find_by(email: "email@ourcompany.com")
person.respond_to? :is_admin
# => false
person.becomes(User).is_admin?
# => true
Jacopo Beschi, Sampson Crowley
同Changelogより
つっつきボイス:「上のサンプルコードでPerson
モデルをUser
にbecomes
したときに、子のPerson
にない親モデルのis_admin
属性にもUser
が応答するように改修したんですね」「改修後の挙動で若干副作用がありそうな気もするけど、becomes
の使われ方を考えると影響はそれほどなさそうかな」「なるほど」「becomes
っていうメソッドは前からActive Recordにありますね↓」「そうそう、あります」
参考: Rails API becomes
-- ActiveRecord::Persistence
「becomes
メソッドはSTIで必要になるときがあります」「あ、たしかに」「自分はそういうときにキャストしたりしましたけど、becomes
メソッドはRubyの機能によるクラスキャストではなくActive Recordにより適した形でキャストしてくれるんでしょうね」「becomes
はSTIを使ったことがないと知らない人も多いかも」
参考: §シングルテーブル継承 (STI) -- Active Record の関連付け - Railsガイド
指定された
klass
のインスタンスに現在のレコードの属性を含めたものを返す。becomes
が最も有用なのは、シングルテーブル継承(STI)構造に関連して、あるサブクラスをスーパークラスのように見せたい場合。becomes
をAction Packのレコードidと併用すると次のようなことができる。たとえばClient < Company
のときに@client.becomes(Company)
とすると、パーシャルのレンダリングでclients/client
パーシャルの代わりにcompanies/company
パーシャルを用いてそのインスタンスをレンダリングできるようになる。注: 新しいインスタンスは元のクラスと同じ属性へのリンクを共有するので、STIカラムの値も同じになる。いずれかのインスタンス属性を変更すると両方のインスタンスに影響する。STIカラムも変更したい場合は、代わりに
becomes!
を使う。
Rails APIbecomes
--ActiveRecord::Persistence
より
🔗Rails
🔗 RailsのAttributes APIでステートレスなフォームを作る(Ruby Weeklyより)
Stateless Forms with the Rails Attributes API - https://t.co/SRImkD6a68
— Karol Galanciak (@Azdaroth) September 12, 2022
つっつきボイス:「Active Recordを使わないステートレスなフォームをAttributes API↓で作るのは、Railsでよく行われますね」
「以下は検索のフォームをActiveModel::Attributes
でやっている↓」「Active Modelをinclude
すれば例のform_with
でRailsらしく書けて便利」「Railsに慣れている人なら普通に使っている方法ですね」
# 同記事より
class Filter
include ActiveModel::Model
include ActiveModel::Attributes
attribute :available_on, :date
end
<-- 同記事より -->
<%= form_with model: @filter, url: items_path, method: :get do |f| %>
「この記事ではカスタム型も追加していますね: そのままではActive Modelに新しい型を追加できないので、以下のように書いてActiveModel::Type.register(:cents, Cents)
とすることで使えるようになる↓」「ActiveModel::Type.register
という書き方ができるって知らなかった」
# 同記事より
class Cents < ActiveRecord::Type::Integer
def cast(value)
return super if value.is_a?(Numeric)
price_in_dollars = value.to_s.delete("$").to_d
price_in_cents = (price_in_dollars * 100).to_i
super(price_in_cents)
end
end
参考: register
-- ActiveModel::Type
「register
しておくと、attribute :min_price, :float, default: 0.00
のように型をシンボルで:float
のように指定できるようになる↓」「なるほど」
# 同記事より
module Criteria
def self.type_for(name)
all.find { |criteria_type|
criteria_type.type_name.to_s == name.to_s
}
end
def self.all
[
Price,
AvailableOn,
Colors
]
end
class Base
include ActiveModel::Model
include ActiveModel::Attributes
attribute :id, :integer
end
class Price < Base
attribute :min_price, :float, default: 0.00
attribute :max_price, :float, default: 100.00
def self.type_name
:price
end
end
class AvailableOn < Base
attribute :date, :date, default: Time.zone.today
def self.type_name
:available_on
end
end
class Colors < Base
attribute :colors, array: true, default: ["royal_blue"]
def self.type_name
:colors
end
def self.supported_colors
[
["blue", "Blue"],
["royal_blue", "Royal Blue"],
["navy_blue", "Navy Blue"],
["raspberry_blue", "Blue Raspberry"]
]
end
def colors=(colors)
super(colors.select(&:present?))
end
end
end
「この記事のようにここまでみっちり書くかどうかは状況次第かなとは思いますが、とてもRailsらしい書き方なので知っておくとよいと思います👍」「ところで、ガイドにはActiveModel::AttributeMethods
のことは書かれているけどカスタム型やregister
の話までは書かれていないみたいですね↓」「Attributes APIドキュメントの翻訳にありました↓」
参考: Active Model の基礎 - Railsガイド
🔗 GibLab CI/CDキャッシュのビジュアル解説ガイド
つっつきボイス:「"cache vs artifacts"はビルドエンジンを使うときなどによく見かける言い回し」「artifactは"成果物"みたいなニュアンスで使われますね」「そうそう、ビルドで生成されたものをbuild artifactsと言ったりしますね」
「ジョブが直前のジョブの結果に依存していない場合はキャッシュを使い、依存する場合はartifactsと依存関係を使う、なるほど」「記事はGitLab CIのキャッシュに関する解説だけど、GitHub Actionsのキャッシュと似たような手法も載っていて参考になりそう👍」
🔗 packwerk: Railsアプリケーション内の境界を定めてモジュール化
We’ve just started trialing packwerk and stimpack for a service in our monolith. Looking forward to seeing how it pans out.
— Richard MacCaw (@rmaccaw) September 13, 2022
つっつきボイス:「TechRacho翻訳記事でもお世話になっているNate Berkopecさんが、型付けよりもShopifyのpackwerkの方が好きだと書いていたのが気になって拾いました」「Railsのコードが名前空間を超えられないように境界を敷くgemのようですね↓」「なるほど、コードの越境を防ぐんですね」「似たようなgemが他にもあったかも」「あった気がするけど思い出せない...」
- ファイルをグループ化してパッケージにまとめる
- パッケージレベルの定数可視性を定義する(publicアクセス可能な定数を持たせる)
- パッケージ間のプライバシー(inbound)や依存関係(outbound)の境界を強制する
- 開発を妨げずに既存コードベースのモジュール化を支援する
同リポジトリより
「packwerkはzeitwerkに依存しているそうなのでパックベルクと読むのかなと想像しました」「zeitwerkに依存するということはローダーレベルで境界をチェックしているのかも🤔」
「ドキュメントはUSAGE.mdに載っていますね」「なお、この概念図↓はパッケージ同士が接するpublic APIはなるべく少なくて疎結合なのが望ましいというよくある説明ですが、ここでむしろ重要なのは1個のパッケージ内部は密結合していても構わないという点を理解しておくことですね」「たしかに」
「packwerkはpackage.ymlファイルで境界を定義するのか、なるほど↓」「enforce_privacy
やenforce_dependencies
みたいな指定もできるんですね」
# 同リポジトリより
# components/sales/package.yml
metadata:
stewards:
- "@Shopify/sales"
slack_channels:
- "#sales"
「packwerkでRailsエンジンの越境を防ぐこともできるでしょうか?」「Sidekiqのジョブ画面あたりなら問題なくできそうですけど、admin専用の管理画面のようにRailsエンジン自体がマウントする側に依存していたりすると、そのままだと動かないかも」「あ、そうか」「packwerkを使えるかどうかは状況や用途にもよるでしょうね」
「packwerkを眺めた限りでは事前定義されているクラスが対象のようなので、動的に定義されるクラスまではチェックできなさそうかな?」「あ、そうかも」「越境を実行時に警告する機能があればそうした部分もCIでカバーできますけど、見た限りではまだなさそうですね」
「メンバーの出入りが多い大規模なプロジェクトでは何らかの形でこうやって制約を与えられるツールが欲しくなるので、時間があるときに調べてみよう👍」「CIでチェックできるとよさそうですね」「この種のツールを後から導入するとつらくなりそうなので、可能ならプロジェクト立ち上げ時に導入したい」「ですよね」
🔗 graphql-rubyのvalidate_max_errors
つっつきボイス:「ruby-jp Slackで見かけたんですが、graphql-rubyでDoS攻撃防止のためにvalidate_max_errors
が追加されていました」「たしかにGraphQLで入れ子が深くなったときのバリデーションエラーが大量に出力されるのは十分ありえますね: 大事な修正👍」
# lib/generators/graphql/templates/schema.erb#L26
+ # Stop validating when it encounters this many errors:
+ validate_max_errors(100)
end
<% end -%>
参考: Method: GraphQL::Schema.validate_max_errors
— Documentation for rmosolgo/graphql-ruby (master)
🔗Ruby
🔗 Ruby 3.2.0 Preview 2(Ruby公式ニュースより)
さっきmatzが言ってた、なんかいろいろ入ったRuby 3.2の最新のpreviewが今出たよ! https://t.co/cPulQeqNI2#rubykaigi
— usa (@unak) September 9, 2022
つっつきボイス:「RubyKaigi 2022の会期中に3.2.0 Preview 2がリリースされました🎉」「Preview 1のリリースノートにも書かれていますが、WASIベースのWebAssemblyサポートがRubyのビルドに含まれるようになったんですね、凄い」「そういえばRubyKaigi 2022のキーノートスピーチでも言及していました」
「breaking changeも少し入ってますね: double splat **
を含むproc
の扱いが修正された↓」「あまり使わない書き方だと思うけど」「私は使ってます😆」「3.2では注意しないといけませんね」
# 同リリースノートより
proc{|a, **k| a}.call([1, 2])
# Ruby 3.1 and before
# => 1
# Ruby 3.2 and after
# => [1, 2]
「以下のような定数への代入が左から右に評価されていなかった↓というバグの修正は、Jeremy EvansさんがRubyKaigi 2022のDay 3で発表していました」「多重代入で複雑な渡し方をしたときの挙動はやってみないとわからないところがありますよね」
# 同リリースノートより
foo1::BAR1, foo2::BAR2 = baz1, baz2
参考: Bug #15928: Constant declaration does not conform to JIS 3017:2013 - Ruby master - Ruby Issue Tracking System
参考: Fix evaluation of order of constant assignment by jeremyevans · Pull Request #4450 · ruby/ruby
🔗 Rack 3.0リリース(Ruby Weeklyより)
Rack 3.0 has been shipped! Let us know if you run in to any issues! 🙏🙇🏻♀️ https://t.co/JQaxEtSvxp
— Aaron Patterson (@tenderlove) September 6, 2022
つっつきボイス:「Rack 3.0も少し前にリリースされました🎉」「ちょうど今日gem install rack
を実行したら知らないバージョンのRackがインストールされたんですが、よく見たら3.0でしたね」「3.0.0.beta1の時点でbreaking changeが少し入っている...」「メジャーバージョンアップだとbreaking changeがあってもおかしくないでしょうね」「ミドルウェア系のgemの中にはRackを直接使っているものもあるので、3.0にアップグレードする前に念のためチェックが必要かな」
🔗 書籍『why's (poignant) Guide to Ruby』
- 元記事: ホワイの(感動的)Rubyガイド
つっつきボイス:「数か月前に知り合ったロンドン在住のRuby開発者の方とRubyKaigiの場でお会いしたときに、以下と同じ書籍をお土産にいただきました🙇」「これは知らない本...」「私もいただくまで知らなかったんですが、_whyさんという方がかなり昔に書いたRubyに関する読み物が最近表紙を改めて書籍化されたらしくて、調べているうちにプロ翻訳者の青木靖さんによる日本語訳をネット上で見つけたのが上のリンクです」
why's (poignant) Guide to Ruby (new book!! #rubykaigi pic.twitter.com/FY4GefGgIF
— Koji Shimada / 島田浩二 (@snoozer05) September 9, 2022
「Rubyを知らない人に向けた、異色のRuby読み物という感じですね❤️」「長くて自分はまだ読み終えていませんが、日本語版は翻訳のクォリティが本当に見事でした」
参考: why's (poignant) Guide to Ruby - Wikipedia
参考: 「だから、作れ」と_whyは言った:Rails Hub情報局:エンジニアライフ
今週は以上です。
バックナンバー(2022年度第3四半期)
週刊Railsウォッチ: syntax_suggestがRuby標準ライブラリに追加、RubyのVisitorパターンほか(20220906後編)
- 20220905前編 Herokuが無料プラン廃止を発表、Hotwire日本語コミュニティほか
- 20220830後編 RubyKaigi 2022タイムテーブル公開、viewport-extraほか
- 20220829前編 MinitestとRSpecの比較、商用版NGINXの重要機能がオープンソース化ほか
- 20220823後編 byebugからruby/debugへの移行ガイド、YJIT解説記事ほ
- 20220822前編 ビューテンプレートに渡せるローカル変数をマジックコメントでチェック可能にほか
- 20220802後編 RubyのGVLトレーサーgvl-tracing、casting gemでオブジェクトに振る舞いを追加ほか
- 20220801前編 “リーダブルテストコードについて考えよう”スライド公開、Evil Martiansが日本上陸ほか
- 20220726後編 中高生国際Rubyプログラミングコンテスト2022、W3Cの分散型識別子仕様が勧告にほか
- 20220725前編 RailsConf 2022の動画が公開、マイクロサービスのテスト戦略ほか(
- 20220719 RubyのGCが高速化、RuboCopのストレスを減らす4つの方法、Defensive CSSほか
- 20220711前編 AR::RelationにCTEを利用できるwithメソッドが追加、Propshaftアップグレードガイドほか
- 20220705後編 6月のRubyコア動向、Stack Overflowアンケート結果ほか
- 20220704前編 マイグレーションをStrategyパターンで拡張可能にほか
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。
週刊Railsウォッチについて
TechRachoではRubyやRailsなどの最新情報記事を平日に公開しています。TechRacho記事をいち早くお読みになりたい方はTwitterにて@techrachoのフォローをお願いします。また、タグやカテゴリごとにRSSフィードを購読することもできます(例:週刊Railsウォッチタグ)