- Ruby / Rails関連
週刊Railsウォッチ: rails-ujs->Turboアップグレードガイド、RubyとWeb Componentsほか(2022017前編)
こんにちは、hachi8833です。
🔗Rails: 先週の改修(Rails公式ニュースより)
今回は以下の差分のうちChangelogが変更されているものを見繕いました。
🔗 Active Jobのシリアライズテストの失敗時にdiscard_onとretry_onを利用できるよう修正
従来の
perform_enqueued_jobsでは、deserialize_arguments_if_neededがperform_nowを呼び出す前に呼び出されていた。レコードが既に存在せず、GlobalIDでシリアライズされている場合だと、perform_nowを呼び出す前にActiveJob::DeserializationErrorエラーが発生していた。このため、ジョブでdiscard_onやretry_onのロジックがテストしにくくなっていた。
この修正では、deserialize_arguments_if_needed呼び出しがperform_now呼び出しまで延期されるようになる。
class UpdateUserJob < ActiveJob::Base
discard_on ActiveJob::DeserializationError
def perform(user)
# ...
end
end
# テストコード
User.destroy_all
assert_nothing_raised do
perform_enqueued_jobs only: UpdateUserJob
end
assert_no_enqueued_jobs
従来は上のテストコードが失敗したが、修正によってパスするようになる。
同PRより
つっつきボイス:「Active Jobでデシリアライズ時にGlobalIDを解決できないとエラーが発生してリトライや破棄ができなかった問題が修正されたんですね」「本来こうあるべき挙動という感じですね」
# activejob/lib/active_job/test_helper.rb#L663
def flush_enqueued_jobs(only: nil, except: nil, queue: nil, at: nil)
enqueued_jobs_with(only: only, except: except, queue: queue, at: at) do |payload|
queue_adapter.enqueued_jobs.delete(payload)
queue_adapter.performed_jobs << payload
- instantiate_job(payload).perform_now
+ instantiate_job(payload, skip_deserialize_arguments: true).perform_now
end.count
end
# ...
- def instantiate_job(payload)
+ def instantiate_job(payload, skip_deserialize_arguments: false)
job = payload[:job].deserialize(payload)
job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
- job.send(:deserialize_arguments_if_needed)
+ job.send(:deserialize_arguments_if_needed) unless skip_deserialize_arguments
job
end
🔗 update_attribute!が追加
新しい
ActiveRecord::Persistence#update_attribute!メソッドを追加する。update_attributeと似ているが、saveではなくsave!を呼び出す。update_attribute!メソッドはbefore_*コールバックが:abortをスローしたときにActiveRecord::RecordNotSavedエラーをraiseする。
class Topic < ActiveRecord::Base
before_save :check_title
def check_title
throw(:abort) if title == "abort"
end
end
topic = Topic.create(title: "Test Title")
# #=> #<Topic title: "Test Title">
topic.update_attribute!(:title, "Another Title")
# #=> #<Topic title: "Another Title">
topic.update_attribute!(:title, "abort")
# raises ActiveRecord::RecordNotSaved
同PRより
つっつきボイス:「!付きのActiveRecord::Persistence#update_attribute!メソッドが追加された」「!なしのupdate_attributeはエラーをraiseしないのか」「たしかそうですね」「それなら整合性の面でも!ありのメソッドはあっていいですね」「ちなみにsaveとsave!の違いもエラーをraiseするかどうかですね」「普段updateしか使ってなかったのでupdate_attributeの挙動を知らなかった」「そういえば私もupdateしか使ってなかった」
🔗 Relation#pretty_printのeager loadingを回避
#inspectの振る舞いにならって、レコードが読み込み済みでない場合は要素を最大11個までしかフェッチしないようにした。
同PRより
つっつきボイス:「Relation#pretty_printで出力が大量にならないように11個までしかフェッチしないようにしたみたいですね」「お〜、これはいい👍」「あ、なるほど、自分もppをよく使うので助かります」「rails consoleで不用意にppして大量の出力でコンソールが固まらずに済む」
# activerecord/lib/active_record/relation.rb#L776
- def pretty_print(q)
- q.pp(records)
+ def pretty_print(pp)
+ subject = loaded? ? records : annotate("loading for pp")
+ entries = subject.take([limit_value, 11].compact.min)
+
+ entries[10] = "..." if entries.size == 11
+
+ pp.pp(entries)
end
🔗 QueryMethods#in_order_ofの挙動をEnumerableに合わせた
- PR:
QueryMethods#in_order_ofdrop records not listed by kddnewton · Pull Request #44097 · rails/rails
in_order_ofが、Enumerable版のin_order_ofの挙動に合わせて、指定された値のリストでフィルタされるようになった。
これは#43916のコメントに対応したもの。振る舞いはEnumerable版に合うようになったが、リリース後の移行パスをどうすればよいかまだわからない。バグとみなしてパッチリリースの対象にする?
同PRより
つっつきボイス:「Enumerableのin_order_ofに合わせて、リストの値にない値を含めないようになったようですね」
# activerecord/lib/active_record/relation/query_methods.rb#L439
def in_order_of(column, values)
klass.disallow_raw_sql!([column], permit: connection.column_name_with_order_matcher)
return spawn.none! if values.empty?
references = column_references([column])
self.references_values |= references unless references.empty?
- if values.empty?
- spawn.order!(column)
- else
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
+ values = values.map { |value| type_caster.type_cast_for_database(column, value) }
- arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
- arel_column = column.is_a?(Symbol) ? order_column(column.to_s) : column
- spawn.order!(connection.field_ordered_value(arel_column, values), column)
- end
+ spawn
+ .order!(connection.field_ordered_value(arel_column, values))
+ .where!(arel_column.in(values))
end
- Rails API:
in_order_of--ActiveRecord::QueryMethods - Rails API:
in_order_of--Enumerable
🔗 Rails 7.1からconfig.add_autoload_paths_to_load_pathがデフォルトで無効になる
参照: 668673f
現在のオートローダーはZeitwerkのみとなったので、オートロード済みのパスを$LOAD_PATHに追加する理由がなくなった(7.0までにやれればよかったのだけど)。
@fxn 問題がなさそうならメンションくれればマージできます。
同PRより
つっつきボイス:「config.add_autoload_paths_to_load_path、長い設定名だな〜」「Railsガイドを7に更新翻訳したときにこの設定を見かけました」「今まではオートロードしたパスを$LOAD_PATHにしこしこコピーしていたけど、Rails 7がZeitwerkのみになって不要になったので、このプルリクでそれをデフォルトで無効にしようということか、なるほど」
参考: 3.1.5 config.add_autoload_paths_to_load_path
-- Rails アプリケーションを設定する - Railsガイド
$LOAD_PATHにオートロードパスを足すべきかどうかを指定します。このフラグはデフォルトでtrueですが、
:zeitwerkモードでは早い段階でconfig/application.rbでfalseに設定することをおすすめします。Zeitwerkは内部で絶対パスが使われ、:zeitwerkモードで動作するアプリケーションではrequire_dependencyが不要なので、モデルやコントローラやジョブなどが$LOAD_PATHに存在する必要はありません。これをfalseに設定すると、requireの解決が相対パスで呼び出されるときにRubyがそれらのディレクトリのチェックを削減でき、それらのインデックスの構築が不要になるのでBootsnapの動作やメモリも節約できます。
Railsガイドより
「昔の記事によくあった『まずオートロードパスを追加しましょう』みたいなことはもうしなくていいんだ」「Zeitwerkになってから、appディレクトリの下に置いたものは何もしなくてもオートロードされるようになりましたよね」「そうそう」
参考: 定数の自動読み込みと再読み込み (Zeitwerk) - Railsガイド
🔗 6.1リリースノートにAction Mailboxの非推奨事項を追加
6.1リリースノートを更新して、Action Mailboxの非推奨項目を追加した。6.1のChangelogには以下が記載されているが、Railsguidesのリリースノートに含まれていなかったので。
Deprecate Rails.application.credentials.action_mailbox.api_key and MAILGUN_INGRESS_API_KEY in favor of Rails.application.credentials.action_mailbox.signing_key and MAILGUN_INGRESS_SIGNING_KEY.
同PRより
つっつきボイス:「過去のリリースノートに追記するのが珍しいと思ったので取り上げてみました」「よくぞ抜けに気づきましたよね」「もしかして自分で踏んだからプルリクあげたのかも?」
🔗 デフォルトのヘッダーからX-Download-Optionsを削除
X-Download-Optionsは、deprecation間近のInternet Explorerでフィッシング攻撃を防ぐためにしか使われていないので、デフォルトのヘッダーから削除するのが筋。
解決されるissue: #43948
新しいデフォルトヘッダーはRails 7.1からフレームワークのデフォルトになる。
同PRより
つっつきボイス:「X-Download-OptionsがIEでしか使われないヘッダーだから消しましょうということだそうです」「IEだけなら消すのが順当でしょうね」
# guides/source/security.md#L1038
config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '0',
'X-Content-Type-Options' => 'nosniff',
- 'X-Download-Options' => 'noopen',
'X-Permitted-Cross-Domain-Policies' => 'none',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
「X-Download-Optionsの解説↓を見ると、アプリケーションからダウンロードしたファイルをブラウザのファイルオープンダイアログに表示しないようにするためのヘッダーなのか」
X-Download-Options
TheX-Download-OptionsHTTP header indicates that the browser (Internet Explorer) should not display the option to "Open" a file that has been downloaded from an application, to prevent phishing attacks as the file otherwise would gain access to execute in the context of the application. (Note: related MS Edge bug )
developer.mozilla.orgより
🔗Rails
🔗 RubyとWeb Componentsを連携させる(Ruby Weeklyより)
つっつきボイス:「Bridgetownというstatic site generatorにRubyコードベースのテンプレートをWeb Componentsにmappingする機能があって、記事中前半のコードはこのBridgetownのWeb Components連携機能のようですね」「なるほど」「Star Rating Componentから先がViewCompoment gemを使ったRailsでの実装の話のようです: ViewComponentを素で使うだけじゃなく、Ruby2JSを使ってRubyでWeb ComponentsのJS classを定義している感じ」
参考: Ruby Components | Bridgetown
参考: Web Components | MDN
参考: Ruby2JS: an extensible Ruby to modern JavaScript transpiler
# 同記事より
# src/_components/note.rb
class Note < Bridgetown::Component
def initialize(type: :primary, icon: nil)
@type, @icon = type.to_sym, icon
end
def icon
return @icon if @icon
case @type
when :primary
"system/information"
when :warning
"system/alert"
end
end
end
「拡張子はserbなのか」「SerbeaというERBのオルタナらしい↓」「Serbea自体はテンプレートエンジンのようですね」
参考: Serbea: Similar to ERB, Except Awesomer
「記事の後半でViewComponent(ウォッチ20200330)にも触れていますが↓、ViewComponent gem自体にはWeb Componentsの統合の機能はなくて、この記事ではLit&Ruby2JSを使ってViewComponentの吐き出すHTMLをRubyで書いてRuby2JSに変換したJS classでうまいことWeb Componentsとして連携させている、という感じですね」「なるほど」
参考: Overview - ViewComponent
参考: Lit
参考: 今後Railsで話題になるかもしれないViewComponentを試した | Webuilder240.com
参考: View Componentのすすめ①
# viewcomponent.orgより
# app/components/message_component.rb
class MessageComponent < ViewComponent::Base
def initialize(name:)
@name = name
end
def call
@output_buffer.safe_append='<h1>Hello, '.freeze
@output_buffer.append=( @name )
@output_buffer.safe_append='!</h1>'.freeze
@output_buffer.to_s
end
end
🔗 Turbo関連記事
つっつきボイス:「Turboについて調べていて、いくつか目についた記事です: 1つめは上のアップグレードガイドです」「rails-ujsやTurbolinksからTurboへのアップグレードガイドか」「ドキュメントの量はそんなに多くない感じです」
「rails-ujsが担当していた機能はそんなに多くないので、考慮するものの種類は少ないんですが、rails-ujsが使われているUIの数は少なくないんですよね」「たしかに」「あって当然の機能だっただけに、完全に移行したことを確認する工数はかかりそう」
「2つめは、この間のjnchitoさんの記事でも扱われていたstatus: :unprocessable_entityを付ける話や、turbo-frameを制御する方法などの記事ですね」
- 元記事: Hotwireとは何なのか?
「3つ目は1年前の記事ですが、今になって気づきました↓」「お、これは読んだことあったかも」「Hotwireを開発のプログレッシブエンハンスメントという観点から説明してくれているのが自分にとってありがたかったです」
参考: Progressive Enhancement (プログレッシブエンハンスメント) - MDN Web Docs 用語集: ウェブ関連用語の定義 | MDN
「RailsとHotwire関連はこの半年ぐらいで大きく進みましたよね」「そうそう」「記事はその前に書かれているので、変更点に注意して読めば大丈夫そう👍」
🔗 DeviseのRails 7対応状況
つっつきボイス:「DeviseにRails 7のサポートが入りましたけど↓、上のChangelogではまだTurboの対応は完全ではないとありました」「自分はDeviseのデフォルトビューをほとんど使ってませんけどね」
「ちなみにDeviseに5-rcブランチがあったのを見かけたんですが、2019年からそのままでした」「Deviseの開発体制はわからないけど、とりあえず作ったものっぽいのでまた作り直すんじゃないかな」「そうかもしれませんね」
🔗 その他Rails
ブログ書きました。今月は2本登壇予定があります。どちらもオンライン開催なので、日本全国どこからでも参加可能です。気軽にポチっと参加登録してやってください〜!
2022年1月の伊藤さんの登壇予定(Qiita Advent Calendar Online Meetupと銀座Rails#41) https://t.co/dhUTVO8hIG
— Junichi Ito (伊藤淳一) (@jnchito) January 11, 2022
つっつきボイス:「jnchitoさんがQiitaのAdvent Calendar 2021 Online Meetupと銀座Rails#41に登壇するそうです」「そうそう、例の点字メーカープログラム↓に参加した人は銀座Railsでjnchitoさんが公開レビューしてくれますよ」
参考: Qiita Advent Calendar Online Meetup - connpass
参考: 【オンライン開催】銀座Rails#41 - connpass
以下はつっつき後のツイートです。おめでとうございます🎉
Everyday Railsをアップデートしたのでブログ書きました🎉 期間限定で割引セールもやってます。すでに購入済みの方もたくさん拡散してもらえると嬉しいです😄
【期間限定の割引あり】Rails 7.0に対応した「Everyday Rails - RSpecによるRailsテスト入門」をリリースしました!https://t.co/hZBx4vOL06
— Junichi Ito (伊藤淳一) (@jnchito) January 17, 2022
前編は以上です。
バックナンバー(2022年度第1四半期)
週刊Railsウォッチ: Rails 7をRuby 3.1で動かす、クックパッドのRuby 3.1解説記事、Rails 6->7更新ほか(20220112)
今週の主なニュースソース
ソースの表記されていない項目は独自ルート(TwitterやはてブやRSSやruby-jp SlackやRedditなど)です。



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