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

週刊Railsウォッチ: Rails向けLanguage Server "refreshing"開発中、JetBrains Fleetほか(20221018前編)

こんにちは、hachi8833です。今週はKaigi on Rails 2022ですね。

週刊Railsウォッチについて

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

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

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

🔗 QueryLogsでtags_formatオプションを指定可能になった

概要
このプルリクはActiveRecord::QueryLogstags_formatという新しいオプションを追加する。キーバリューペアの区切りに:を使いたくない場合がある(sqlcommenterのように区切り文字を=にして値を一重引用符''で囲みたい)。そこでtag_formatオプションを追加して、:legacy(訳注: 原文は:default)機能と:sqlcommenterのどちらを使うかをユーザーが指定できるようにする。
その他情報
これはMarginaliaへのプルリク(#130)を文字通りまるごとコピーした。オリジナルを実装した@modulitosに感謝。
同PRより


つっつきボイス:「そういえばQueryLogsは以前Marginaliaという名前のライブラリがRailsに取り入れられたものでしたね↓(ウォッチ20210906)」「クエリの呼び出し元情報を追加できるヤツですね」「そのクエリログのフォーマットをtags_formatオプションで変更可能になった、いいですね👍」

参考: Add Marginalia to Rails, via QueryLogs by keeran · Pull Request #42240 · rails/rails

🔗 database.ymlのYAMLキーに任意のERBを書けるようになった

コミット37d1429#35497)で、rake -T実行時に環境の読み込みを回避するためにDummyERBが導入された。

DummyCompiler<%=の出力をシンプルに固定文字列に置き換えてそれ以外をすべて削除していた。これはYAMLの値で使われる場合は問題なかったが、YAMLキー内部で<%=が使われるとYAMLパーサーでエラーが発生して期待通りのERBを利用できなくなる。たとえば以下を含むdatabase.ymlでこの問題が起きるはず。

  development:
    <% 5.times do |i| %>
    shard_<%= i %>:
      database: db/development_shard_<%= i %>.sqlite3
      adapter: sqlite3
    <% end %>

壊れたERBコンパイラを使う代わりに、#35468の記載通りでない設定にアクセスしてもエラーを発生しないようにするRails.application.configを一時的に利用できるようになった。

この変更によってDummyCompilerが削除されて標準のERB::Compilerが使われるようになる。これはDummyConfigを導入して既知のすべてのコンフィグを実際のRails::Application::Configurationインスタンスに委譲してからそれ以外のすべてについてダミー文字列を返す。これによって、マルチプルデータベースでrakeタスクを生成するときに速度を犠牲にせずにERBとの完全互換が復元される。

config.active_record.suppress_multiple_database_warningは非推奨化される。
同PRより


つっつきボイス:「database.ymlにERBで任意のRubyコードを書けるようになったんですか?」「今までできなかったのが意外」「すごいけどやりすぎたら怖そう」「fixtureのyamlファイルにコードを書くことはよくありますよ」

Rails 7 API: ActiveRecord::FixtureSet(翻訳)

🔗 エラーの二重出力回避のためRails.error.reportを修正

  • エラーの二重出力回避のためにRails.error.reportがエラーをreportedとマーキングするようになった

エラー発生前に、ユーザーが特定のコンテキストで明示的にエラーを出力したいことがある。
このプルリクによって、エラーを安全にキャッチして実行コンテキストの外でエラーを出力できるようにもなる。
Jean Boussier
同Changelogより


つっつきボイス:「エラー出力の重複を回避したそうです」「シンプルなバグ修正のようですね」

# activesupport/lib/active_support/error_reporter.rb#L169
    def report(error, handled: true, severity: handled ? :warning : :error, context: {}, source: DEFAULT_SOURCE)
+     return if error.instance_variable_get(:@__rails_error_reported)
+
      unless SEVERITIES.include?(severity)
        raise ArgumentError, "severity must be one of #{SEVERITIES.map(&:inspect).join(", ")}, got: #{severity.inspect}"
      end
      full_context = ActiveSupport::ExecutionContext.to_h.merge(context)
      disabled_subscribers = ActiveSupport::IsolatedExecutionState[self]
      @subscribers.each do |subscriber|
        unless disabled_subscribers&.any? { |s| s === subscriber }
          subscriber.report(error, handled: handled, severity: severity, context: full_context, source: source)
        end
      rescue => subscriber_error
        if logger
          logger.fatal(
            "Error subscriber raised an error: #{subscriber_error.message} (#{subscriber_error.class})n" +
            subscriber_error.backtrace.join("n")
          )
        else
          raise
        end
      end

+     unless error.frozen?
+       error.instance_variable_set(:@__rails_error_reported, true)
+     end

      nil
    end
  end

🔗 Feature-Policyヘッダーのリストを更新した

概要
利用可能なPermission Policyリストが進化した。
このプルリクは、安定したfeatureをActionDispatch::PermissionsPolicyに追加する。

このプルリクによってChromeでサポートされるfeatureのリストは以下のとおり(かっこ内はバージョン)。

  • hid (Chrome 89)
  • idle-detection (Chrome 94)
  • screen-wake-lock (Chrome 84)
  • serial (Chrome 89)
  • sync-xhr (Chrome 65)
  • web-share (Chrome 86)

その他情報
W3Cのリストにない以下のfeatureをどうすればいいか自分にはわからない。

  • speaker
  • vibrate
  • vr

#45427より


speakervibratevr#33439が最初に書かれた2018-07-25頃には"policy-controlled features"としてリストに掲載されていた。
しかしvibratew3c/webappsec-permissions-policy@b7271acで削除され、vrw3c/webappsec-permissions-policy@bec5ce6xrに変更され、speakerw3c/webappsec-permissions-policy@18707d3で削除された(さらに言えばxrxr-spatial-trackingに変更され、現在も実験的サポートのみとなっている)。
そこで、このコミットでこれらのpermission policyディレクティブを非推奨化する。
#46199より


つっつきボイス:「Feature-Policyって何だろうと思ったら、Edgeガイドに記載されていました↓」「たしかWeb APIなどで利用を許可する機能を指定するときに使うヘッダーですね」

参考: §9.4 Feature-Policy Header -- Securing Rails Applications — Ruby on Rails Guides

「Railsだとこういうふうに書けるのか↓」

# Edgeガイドより
# config/initializers/permissions_policy.rb
Rails.application.config.permissions_policy do |policy|
  policy.camera      :none
  policy.gyroscope   :none
  policy.microphone  :none
  policy.usb         :none
  policy.fullscreen  :self
  policy.payment     :self, "https://secure.example.com"
end

「プルリクはこのW3Cの定義↓の更新に対応したんですね」「cameraとかmicrophoneとかusbとかいろいろある」「お〜、なつかしのmidiまであるとは」「Webでは見かけなくなったけど通信規格としては今も現役ですね」(しばらくMIDI談義)

参考: webappsec-permissions-policy/features.md at main · w3c/webappsec-permissions-policy
参考: MIDI - Wikipedia

🔗 エンジン内でダイレクトアップロードが利用できるようになった

動機/背景
このプルリクを作成した理由は、Railsエンジン内からのダイレクトアップロード呼び出しが使えないから。

詳細
このプルリクは、アップロードURLをdata属性として追加すべきかどうかを決定するconvert_direct_upload_option_to_urlの条件を変更する。これにより、rails_direct_uploads_urlが存在するかどうかをmain_appで検証するようになる。
この変更前は、エンジン内のテンプレートでこのメソッドを呼び出すとメインアプリのルーティングではなくエンジンのルーティングを探索していたため見つけられなかった。
テストはリグレッション防止用に追加した。
同PRより


つっつきボイス:「エンジンからダイレクトアップロードを呼べなかったのを修正したんですね」

参考: §11 ダイレクトアップロード -- Active Storage の概要 - Railsガイド

🔗 error_highlightの改良: ERBテンプレート内のエラー位置もハイライトするようになった

このプルリクは#45818に改良を加える。ERBテンプレート内で発生した例外の位置もハイライトするようになる。以下は改修前後のスクリーンショット。
改修前:

改修後:

これを行うために、エラーハンドリングの詳細を変更して常にbacktrace locationsで扱うようにした。このプルリク前は、例外のラッパークラスでbacktrace_locationsを使う場合やbacktraceを使う場合があった。ErrorHighlightはbacktrace locationsオブジェクトでないと使えないので、コードをリファクタリングしてこれだけを使うようにした。

このリファクタリングは残念ながら、SyntaxError例外の扱い方が原因で割としんどくなってしまった。SyntaxError例外はバックトレースで構文エラーの位置情報を含んでいないので、バックトレース改変することになる。SyntaxErrorの振る舞いは維持する必要があるが、バックトレースの改変は筋が悪そうだったので、SyntaxErrorのラッパーオブジェクトを導入してbacktracebacktrace_locationsを実装した。

次に困難だったのは、ErrorHighlightExceptionオブジェクトとThread::Backtrace::Locationオブジェクトでしか動かないこと。上で導入したbacktrace locations オブジェクトがThread::Backtrace::Locationのインスタンスではないことは明らか。例外ラッパーのコードでis_a?を書きたくなかったので、Thread::Backtrace::Locationにモンキーパッチを当てて、ErrorHighlightの位置情報があれば返すようにした。これでSyntaxErrorの場合に特殊な操作を行えるようになる(この時点ではそうなっていないが)。

これでbacktrace locationsオブジェクトが実際に返されるようになり、ERBでErrorHighlightが効くようになった。しかし表示されるのはテンプレートのソースコードではなくコンパイル済みERBのソースコードで、これは以下のような場合に位置が正しくならない。

これに対応するため、メソッド名をテンプレートオブジェクトに対応付けるグローバルハッシュを1個ここに追加した。例外ラッパーがバックトレースを取得すると、メソッドが"テンプレート"メソッドかどうかをチェックして特殊なバックトレースオブジェクトを返す。この特殊なバックトレースオブジェクトは、単にErrorHighlightのソースを実際のソースに対応付けるようテンプレートに指示する。このテンプレートオブジェクトは、その対応付けを行うようハンドラーに指示しているだけ。このハンドラーが実際のテンプレートストラテジー(ERBやHamlなど)になる。
ERB変換はここで行っている。ある種のハックではあるが、動いているようだ。
同PRより


つっつきボイス:「これだけ、Twitterで見つけた@tenderloveさんによる改修です」「@mameさんが実装したerror_highlight↓をさらに改良してERBテンプレート内のエラーにも対応したのか」「修正大変そう」「この改良もありがたいです🙏」

Rails 7: エラー発生位置をわかりやすく示すerror_highlightが導入された(翻訳)

🔗Rails

🔗 WIP: RailsでLanguage Serverをサポート(Rails公式ニュースより)


つっつきボイス:「まだまだWIPですが、これも@tenderloveさんがRails用のLanguage ServerをRailsエンジンとして作っているところだそうです」「エンジン名はrefreshingか」「READMEにはまだFalconのthreadedモードでしか動かないと書かれているので、キャッシュのステート管理のような深い部分まで扱っていそう」

参考: socketry/falcon - GitHub

「Railsに慣れていない人ほどこういう機能があると助かるし、慣れている人の邪魔にもならないのでいいことしかないですね」「Language Serverはもっと普及して欲しいので、こういうのが登場するのはいいと思います👍」

tenderlove/refreshing - GitHub

参考: Official page for Language Server Protocol

🔗 Evil MartialsのViewComponent記事


つっつきボイス:「ちょっと長い記事です」「ViewComponent記事をよく見かけるようになってきた感じなので、ViewComponent好きな人が増えているのかも」「こういうサードパーティのコンポーネントフレームワークをサポートするようになったのはRails 6.1からなんですね(ウォッチ20200330)」「あくまでサードパーティによる拡張としてですけどね」

参考: Introduce support for 3rd-party component frameworks by joelhawksley · Pull Request #36388 · rails/rails

「前回のウォッチでも話しましたが(ウォッチ20221011)、ViewComponentのような機能はプレゼンテーション層をサーバーサイドで完結させるという意味ではRailsの正当な進化だと思っています」「サーバー側ですべてを掌握する感じですね」「自分は割と好きな方なんですが、現代のフロントエンドと方向が真逆で提案しづらい面もあったりはしますね」

「ところで記事でAMラジオのしくみの図が使われているのが懐かしい↓」「アンテナ、同調、検波、増幅でしたっけ」


同記事より

参考: 受信機 - Wikipedia

🔗 JetBrainsの新しいIDE "Fleet"がpublic previewに


つっつきボイス:「そうそう、Fleetがpublic previewになりましたね: JetBrains信者なので速攻動かしてみました」「軽量にするためにゼロから作り直したそうですが、どうでした?」「残念ながら今の時点ではめちゃめちゃクラッシュしますね」「あら〜」「ローカルファイルの編集は問題なくできますが、ssh接続だとプラグインのダウンロードのあたりで動かなくなりました: まあpublic previewはEAPよりも前の状態ですけどね」

参考: 早期アクセスプログラム (EAP)-JetBrains

「JetBrains IDEはこれまでJavaベースでしたけど、Fleetもそうですか?」「自分が見たエラーメッセージはJavaのエラーそのものでしたね」

「VSCodeをインストールするのが億劫な方なので、そのうちJetBrains Toolboxで管理できるFleetでやれるようになるといいなと思って期待しています: 今のままだとVSCodeにはまだまだ敵いませんが信者なので更新を待つつもり」

🔗 gemを使わないシンプルなPresenterパターン(Ruby Weeklyより)


つっつきボイス:「PresenterパターンもしくはDecoratorパターンをgemなしで書く記事: モジュールで書けばいいのではと思ったら案の定concernsで書いてますね」

# 同記事より
# app/models/concerns/presentable.rb
module Presentable
  def decorate
    "#{self.class}Presenter".constantize.new(self)
  end
end
# 同記事より
# app/models/user.rb
class User < ApplicationRecord
  include Presentable

  # ...
end
# 同記事より
# app/presenters/user_presenter.rb
class UserPresenter < SimpleDelegator
  include ActionView::Helpers::TextHelper

  def unread_notifications_text
    unread_count = notifications.unread.count

    if unread_count == 0
      return "You don't have unread notifications."
    end

    "You have %{unread_count} unread %{pluralize(unread_count, 'notification')}".
  end
end

参考: class SimpleDelegator (Ruby 3.1 リファレンスマニュアル)

「MVCの初期の頃から使われている伝統的なDecoratorの書き方ですね: 10年前ぐらいにこういう記事をよく見かけました」「なるほど」「今はDecorator的なことはフロントエンドやビュー寄りのところでやることが多いかな」「今だと他の方法がいろいろありそうですね」「モデルにDecoratorを付与する方法は、個人的にはちょっと自由度が高すぎるというかモデルとあまり分離されていなくて強すぎる感じもしますが、こういう記事で伝統的な書き方を知っておくのもよいと思います👍」


前編は以上です。

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

週刊Railsウォッチ: RailsとPostgreSQLで列挙型を作成する6つの方法、Ubuntu Proほか(20221012後編)

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

Rails公式ニュース

Ruby Weekly


CONTACT

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