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

週刊Railsウォッチ: 英国政府サイトで使われるRailsアプリ、pg-oscとPercona Toolkitほか(20220308前編)

こんにちは、hachi8833です。

週刊Railsウォッチについて

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

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

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

以下のコミットログのうち、Changelogに反映されているものから見繕いました。

🔗 CSPの修正

content_security_policyが無効なディレクティブを返すことがある問題を修正。
lambda呼び出しの結果によるディレクティブで、selfunsafe-evalなどが一重引用符で囲まれないことがあった。

content_security_policy do |policy|
  policy.frame_ancestors lambda { [:self, "https://example.com"] }
end

この修正によって、上で生成されるポリシーが有効になるようになった。
同Changelogより


つっつきボイス:「CSPのヘッダーで'self'selfになってしまうことがあったのを修正したんですね: 明らかにバグ」「frame_ancestorsという属性初めて知りました」

参考: CSP: frame-ancestors - HTTP | MDN

🔗 skip_forgery_protectionの挙動を修正。

フォージェリー保護が有効になってない状態でskip_forgery_protectionを実行してもエラーにならないよう修正。
この修正により、Rails 7.0でdefault_protect_from_forgeryをfalseにした場合にWelcomeページ(/)でArgumentErrorが発生しなくなる。
Brad Trick
同Changelogより


つっつきボイス:「Action Mailboxに修正が入っていますね↓」「ほんとだ」「でもそれ以外にも関連していそう: たまたまActionController::Baseを継承してかつskip_forgery_protectionを使っていたのがここだけだったんでしょうね」「Welcome画面でArgumentErrorが出たらびっくり」

# actionmailbox/app/controllers/action_mailbox/base_controller.rb#L3
module ActionMailbox
  # The base class for all Action Mailbox ingress controllers.
  class BaseController < ActionController::Base
-   skip_forgery_protection if default_protect_from_forgery
+   skip_forgery_protection

    before_action :ensure_configured

🔗 リダイレクトのレスポンスからbodyコンテンツを削除


つっつきボイス:「リダイレクトでbodyコンテンツが返されてたんですか?」「言われてみれば"You are being redirected"みたいなbodyはありましたね↓: テストでrequest specを書いたりするとこういうのが入ってくる」「そういえばあったかも」

# actionpack/lib/action_controller/metal/redirecting.rb#L88
      self.status        = _extract_redirect_to_status(options, response_options)
      self.location      = _enforce_open_redirect_protection(_compute_redirect_to_location(request, options), allow_other_host: allow_other_host)
-     self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.unwrapped_html_escape(response.location)}\">redirected</a>.</body></html>"
+     self.response_body = ""
    end

「これが削除されると表示が変わるんでしょうか?」「リダイレクトレスポンスなので、ブラウザの挙動上ユーザーに表示されることはありませんね: curlとかでアクセスしたりすると表示される」「たしかに」「テストの修正は必要になるのかな?」「テストでリダイレクトのbodyをチェックしていない限り大丈夫だと思います: リダイレクトのテストでチェックするのは基本的にステータスコードとロケーションですね」「なるほど」「むしろこの修正でテストが落ちるとしたら、そのテストが的はずれな部分をチェックしている可能性があるかも」

「プルリクによると、リダイレクトのレスポンスにbodyがあるとW3Cバリデータ↓でエラーになるんですって」「今のままでも問題は起きなさそうですけど、W3Cバリデータに怒られるなら削除しようという流れなんでしょうね」

# 同PRより
Warning: Consider adding a lang attribute to the html start tag to declare the language of this document.

Error: Start tag seen without seeing a doctype first. Expected <!DOCTYPE html>.

Error: Element head is missing a required instance of child element title.

参考: The W3C Markup Validation Service

🔗 ActionContoller::LiveIsolatedExecutionStateを修正

短命なスレッドでIsolatedExecutionStateをコピーするようActionContoller::Liveを修正。
ActionContoller::Liveは当初から、ミドルウェアで設定されたCurrentAttributesなどをコントローラのアクションで保持するためにスレッドのローカル変数をコピーするようになっていた。
7.0でIsolatedExecutionStateが導入され、ActionContoller::Liveコントローラで一部のグローバルステートが失われていた。
Jean Boussier
同Changelogより


つっつきボイス:「ACってAction Controllerだったのか」「Liveはストリーミング系のコントローラで、例のCurrentAttributesとも関連しているらしい」「IsolatedExecutionStateの一部が共有されていたのをちゃんと分けるようにしたようですね」

Railsの`CurrentAttributes`は有害である(翻訳)

後で調べると、IsolatedExecutionStateはpublicではないようです↓。

参考: rails/isolated_execution_state.rb at 7-0-stable · rails/rails

🔗 フィクスチャのメモリフットプリントを削減

フィクスチャアクセサのメモリフットプリントを削減。
従来のフィクスチャアクセサはdefine_methodでeagerに定義されていたため、メモリ使用量がフィクスチャやテストスイートの個数に直接依存していた。
フィクスチャアクセサをmethod_missingで実装したことで、メモリやCPUのオーバーヘッドを大幅に削減できた。
Jean Boussier
同Changelogより


つっつきボイス:「これはメモリ使用量の最適化か」「コードもだいぶ減りましたね↓」「チリも積もればというヤツで、こういう改修が積み重なって全体のパフォーマンスがよくなる👍」

# activerecord/lib/active_record/test_fixtures.rb#L57
      def setup_fixture_accessors(fixture_set_names = nil)
        fixture_set_names = Array(fixture_set_names || fixture_table_names)
-       methods = Module.new do
+       unless fixture_set_names.empty?
+         self.fixture_sets = fixture_sets.dup
          fixture_set_names.each do |fs_name|
-           fs_name = fs_name.to_s
-           accessor_name = fs_name.tr("/", "_").to_sym
-
-           define_method(accessor_name) do |*fixture_names|
-             force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-             return_single_record = fixture_names.size == 1
-             fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
-
-             @fixture_cache[fs_name] ||= {}
-
-             instances = fixture_names.map do |f_name|
-               f_name = f_name.to_s if f_name.is_a?(Symbol)
-               @fixture_cache[fs_name].delete(f_name) if force_reload
-
-               if @loaded_fixtures[fs_name][f_name]
-                 @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-               else
-                 raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-               end
-             end
-
-             return_single_record ? instances.first : instances
-           end
-
-           private accessor_name
+           key = fs_name.match?(%r{/}) ? -fs_name.to_s.tr("/", "_") : fs_name
+           key = -key.to_s if key.is_a?(Symbol)
+           fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
+           fixture_sets[key] = fs_name
          end
        end
-       include methods
      end

🔗 #attachした添付ファイルの保存に成功するとblobを返すようになった

添付ファイルをレコードに保存するとblobオブジェクトを返すようになる。
従来は、添付ファイルを保存してもblobオブジェクトは返されなかった。
現在は、#attachメソッドで添付ファイルを追加してレコードを保存すると、レコードにアタッチされたblobまたはblobの配列を返すようになった。添付ファイルの保存に失敗した場合はfalseを返す。
Ghouse Mohamed
同Changelogより


つっつきボイス:「これはActive Storageですね」「今まではrecord.saveの結果を返していたのでtrueかfalseしか返さなかったけど、改修後は成功時にblob(binary large object)を返すようになったんですね: この方が扱いやすくてよさそう👍」「Rubyの条件式はnilfalse以外はtrueになるので、既存のロジックも変わらずに済みますね」

# activestorage/lib/active_storage/attached/one.rb#L57
    def attach(attachable)
      if record.persisted? && !record.changed?
        record.public_send("#{name}=", attachable)
-       record.save
+       if record.save
+         record.public_send("#{name}")
+       else
+         false
+       end
      else
        record.public_send("#{name}=", attachable)
      end
    end

🔗 パス名が空("")の場合にのみPathname.blank?がtrueを返すよう修正


つっつきボイス:「言われてみれば、スペース文字だけのファイル名は作ろうと思えば作れますね」「ファイル名にスペースを混ぜるのがありなんだから、スペースだけのファイル名もありということになりますね」「で、今までは" """Pathname.blank?がfalseになっていたけど、" "ならfalseを返して""ならtrueを返すのが正しい、たしかに」「これはバグ修正ですね」

# activesupport/test/core_ext/pathname/blank_test.rb
# frozen_string_literal: true

require_relative "../../abstract_unit"
require "active_support/core_ext/pathname/blank"

class PathnameBlankTest < ActiveSupport::TestCase
  def test_blank
    assert_predicate Pathname.new(""), :blank?
    assert_not_predicate Pathname.new("test"), :blank?
    assert_not_predicate Pathname.new(" "), :blank?
  end
end

「それにしても、名前がスペースだけのファイルを作るという発想が今までなかった」「それに気づくようなコードを書いていた人がいるということなのかな?」「issue #44452を見ると、Pathname.presenceの挙動で驚いたのが修正のきっかけだったらしい」「なるほど、名前がスペースだけのファイルを作りたかったわけではなかった」

🔗Rails

🔗 英国政府のサイトの多くはRailsで構築されている(Ruby Weeklyより)


同記事より(緑はテストでカバーされている部分)


つっつきボイス:「英国政府の公式サイトは70個ほどあって、そのうちRailsアプリがかなりたくさん使われているそうです」「へー、知りませんでした」「上の記事では政府がアプリを採用するときにどういうテストを行うかという基準を示しているそうです」「なるほど、こういうふうに事前に割と細かく仕様を示すところはありますね」「Railsが使われていると聞くと何となくイギリスに親近感を抱いちゃいました」「アプリをオープンソースベースで作っている公的機関は世界的に見れば結構あります」

🔗 pg-osc: PostgreSQLスキーマ変更ツール(Ruby Weeklyより)

shayonj/pg-osc - GitHub


つっつきボイス:「PostgreSQLのスキーマをゼロダウンタイムで変更する、よく話題になるヤツですね」「このpg-online-schema-changeというコマンドでやれるらしい↓」「略してoscなんですね」

# 同記事より
pg-online-schema-change perform \
  --alter-statement 'ALTER TABLE books ADD COLUMN "purchased" BOOLEAN DEFAULT FALSE; ALTER TABLE books RENAME COLUMN email TO new_email;' \
  --dbname "production_db" \
  --host "localhost" \
  --username "jamesbond" \
  --password "" \
  --drop

「このpg-oscは、pt-online-schema-changeにインスパイアされたと記事にあります」「お、pt-online-schema-changeってPercona Toolkitじゃないですか↓」「コマンド名のptをpgに変えたということなのかな」

参考: pt-online-schema-change -- Percona Toolkit

「Percona Toolkitといえば、これまでもよく話題に出てきた優秀なツールですね(ウォッチ20201020)」「元々MySQL向けでしたが今はPostgreSQL向けにもなりつつある、歴史も実績もあるツールです: なお昔はmaatkitという名前でした」「2008年からあるみたいなので歴史長いんですね」「Percona Toolkitにはかなり信頼を寄せています👍」

参考: Percona Toolkit (ペルコナツールキット) | MySQLチューニング/保守サポート/コンサルティングのスマートスタイル

「このツールにはMySQLのチューニングでかなりお世話になりました: かつてのMySQLはスロークエリを秒でしか指定できなかった時代があったんですよ」「秒だけだとキツい...」「あの頃Percona Toolkitがなかったらmsecで指定するのは無理でしたね」

🔗 その他Rails

つっつきボイス:「ツイートのスレッドを追うと、Railsのデプロイにどんなものを使っているか人それぞれで楽しい」「Herokuだったり、GitHub Actionsだったり、DockerコンテナからFargateだったり、ssh経由でgit pullしたり、いろいろ」「いったんCDを設定したら後は気にしなくなりますけどね」「そうそう」

参考: CD(継続的デリバリー)とは? » CloudBees|テクマトリックス


前編は以上です。

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

週刊Railsウォッチ: Ruby標準のCSVライブラリは優秀、if代入のコーディングスタイル、rambulanceほか(20220301後編)

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

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

Rails公式ニュース

Ruby Weekly


CONTACT

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