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

週刊Railsウォッチ: 複合主キーの実装が進む、Rails公式のバグ再現用テンプレートほか(20230412前編)

こんにちは、hachi8833です。昨日からTechRachoのコードブロックのスタイルをリニューアルしました。

週刊Railsウォッチについて

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

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

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

🔗 複合主キー向けの"タプル"構文をwhereに導入

whereを用いるクエリで新しいタプル(tuple)構文が使えるようになった。これは、キーに「カラムの配列」、値に「それに対応するタプルの配列」をそれぞれ渡せる。
このキーではカラムのリストを指定するが、値はそのカラムリストに適合する順序付きタプルの配列となる。
例:

# Cpk::Book => Cpk::Book(author_id: integer, number: integer, title: string, revision: integer)
# Cpk::Book.primary_key => ["author_id", "number"]
book = Cpk::Book.create!(author_id: 1, number: 1)
Cpk::Book.where(Cpk::Book.primary_key => [[1, 2]]) # => [book]
# Topic => Topic(id: integer, title: string, author_name: string...)
Topic.where([:title, :author_name] => [["The Alchemist", "Paul Coelho"], ["Harry Potter", "J.K Rowling"]])

Paarth Madan
同Changelogより


動機/背景
このプルリクは、現在進行中の複合主キー導入に向けた取り組みに関連している。
Railsの抽象化パターンとして、主キーを指定して検索を実行することが非常に一般的である。Rails全体で以下のようなコードをよく見かける。

モデルで複数カラムを複合した主キーが使えるようになったので、一般的なクエリパターンの1つとして、それらの複合キーを指定してレコードセットを検索するというものがある。

このプルリクは"タプル構文"を用いたクエリをサポートする(他の命名案も歓迎)。

book_one = Cpk::Book.create!(author_id: 1, number: 2)
book_two = Cpk::Book.create!(author_id: 3, number: 4)

# カラムのリストと、対応するタプルの配列との間のマッピングを提供する
Cpk::Book.where([:author_id, :number] => [[1, 2]])         # Relation(book_one)
Cpk::Book.where(Cpk::Book.primary_key => [[1, 2], [3, 4]]) # Relation(book_one, book_two)

詳細
このプルリクは、#whereに渡されたHash内のキーが配列の場合に対応する目的で、述語ビルダーを変更する。この場合、タプルのセットをイテレートしてクエリを個別に算出することになる。従来は、キーも値も単一であることが前提だったが、今回このメソッドが配列を受け取れるようになった。
配列の1つにさしかかると、各タプルをイテレートして、各サブタスクを元の実装に(再帰的に)委ねる形でクエリを算出し、最終的にクエリを再グループ化(コレクション)する。

grouping_queriesの実装では、サブクエリの間にORを挿入する。

注: このプルリクは、生成されるSQLのパフォーマンスやフォーマットまではあまり配慮していない。Active Recordがこのクエリを実行するためにどんなSQLをどう生成するかについては、ユーザー定義のコンフィグ(active_record.config.bulk_query_strategyあたりを考えている)を作って今後改良を重ねていくことを検討し始めているところ。
これにより、行コンストラクタSQLのビルドや、shardedコンテキストでのヒント追加、あるいはシンプルに現在のOR区切りにフォールバックすることを検討可能になるだろう。

いずれにしろ、このプルリクではタプルによるクエリAPI設計のみに関心を寄せており、クエリの実行方法についてはさほど重点を置いていない。

このプルリクは改修作業が枝分かれしてしまうことを避けるのに役立つだろう。枝分かれした多くの改修作業がこの新しい構文に置き換えられれば、26994cbのようなコミットもすぐに当たり前になるだろう。

また、この新構文は複合主キー機能をリリースするときのリスク軽減にも役立つだろう。単一の場合にも複合の場合にも抽象化が機能することで、この機能が使われる場所の多くが"自動的に"機能するようになるからだ。
同PRより


つっつきボイス:「このタプルってどれを指しているんでしょうか?」「タプルは言語や文脈によって違うし、SQLにもタプルという概念があったりするけど、ここでは=>の右にある[[1, 2]][[1, 2], [3, 4]]を指しているということでよいと思います」「なるほど」

# 同PRより
Cpk::Book.where([:author_id, :number] => [[1, 2]])         # Relation(book_one)
Cpk::Book.where(Cpk::Book.primary_key => [[1, 2], [3, 4]]) # Relation(book_one, book_two)

参考: 組 (データベース) - Wikipedia -- タプル

「特に[1, 2], [3, 4]のような形でタプルを複数渡せるのはたしかに便利そうだし、これができないと構文が複雑になって不便」「慣れないとちょっと戸惑いそうですね」「こういう書式だということをわかって使えば大丈夫だと思います👍」

🔗 仮想的なid=に複合主キーを渡せるようになった

動機/背景
このプルリクは、現在進行中の複合主キー導入に向けた取り組みに関連している。

(主にテストで)大量に発生するコードパスは、アクセサメソッドを用いてモデルの主キーを設定する部分になる。このプルリクは、複合主キーのコンテキストで配列を受け取れるようにid=のAPIを変更する。

実装
分岐を用いて、値を単一の属性に書き込むか、主キーに対応する属性リストに書き込む。
Arrayを用いてこれらの分岐を組み合わせると、@primary_keyがnilの場合の処理は削除される(Array(nil)[]に解決されるため)。このようなシナリオではテストが失敗するので、分岐を使うことにした。
同PRより


つっつきボイス:「こちらも複合主キー対応です」「お、テストコードを見るとbook.id = [1, 2]という渡し方をしている↓」

# activerecord/test/cases/primary_keys_test.rb#376
  def test_assigning_a_composite_primary_key
    book = Cpk::Book.new
    book.id = [1, 2]
    book.save!

    assert_equal [1, 2], book.id
  end

  def test_assigning_a_non_array_value_to_model_with_composite_primary_key_raises
    book = Cpk::Book.new

    assert_raises(TypeError) do
      book.id = 1
    end
  end

「これはidカラムに[1, 2]という複合主キーを代入しているということでしょうか?」「このbookにはidという名前のカラムは実際にはDBに存在しないはずなので、このidはある種の仮想的なid属性で、それに複合主キーを渡すと内部で展開するようになったようですね」「あ、そういうことなのか」

「実際のidがDBにないのにbook.id = [1, 2]と書くのってなかなか大胆」「慣れてないとちょっと違和感ありそうだけど、こう書きたい気持ちはとてもわかる👍」「文脈をわかっていれば理解できるけど、これだけ見たらidというカラムが存在しているかと思っちゃいそうですね」「初見殺し感ある」

「考えてみれば、複合主キーでは[1, 2]というような組み合わせがidになるわけだから、その意味ではbook.id =で渡せるのはそれなりに筋が通っているとも言えそう: もしかするとリリースまでに記法が変わるかもしれませんが」

「ところで、複合主キー関連の機能がこの調子で増えていくと、複合主キーを解説するためのRailsガイドが追加されるかもしれませんね」「ページを増やすか既存のガイドに追記するかはわかりませんが、複合主キーのちゃんとした解説は必要でしょうね」

参考: Ruby on Rails ガイド:体系的に Rails を学ぼう

🔗 ActiveStorage::Analyzer::AudioAnalyzersample_rateも取得できるようになった


つっつきボイス:「Active Storageの音声アナライザ機能の改修です」「これまでmetadataからdurationbit_rateは取れていたんだから、sample_rateでサンプリングレートを取りたいのもわかる」

# activestorage/test/analyzer/audio_analyzer_test.rb#L9
  test "analyzing an audio" do
    blob = create_file_blob(filename: "audio.mp3", content_type: "audio/mp3")
    metadata = extract_metadata_from(blob)

    assert_equal 0.914286, metadata[:duration]
    assert_equal 128000, metadata[:bit_rate]
+   assert_equal 44100, metadata[:sample_rate]
  end

参考: Rails API ActiveStorage::Analyzer::AudioAnalyzer
参考: サンプリング周波数 - Wikipedia

🔗 TestFixturesincludeするたびにloadフックを実行するように修正

動機/背景
このプルリクは、#47675の続きとして作成された。#47675の例ではactive_model_test_caseのloadフックを定義してshovel演算子<<fixture_pathsに追加した。
しかし、TestFixtures自身がloadフックからActiveSupport::TestCaseincludeされるので、実際には動かないことがわかった。フックの実行順序を制御する方法がないので、フックが実行された時点でfixture_pathsメソッドが未定義になっている可能性がある。

詳細
このプルリクは、:active_record_fixtures loadフックを追加する。このloadフックはTestFixturesincludeされるたびに実行されるので、以下のようにこのフックからfixtureパスが追加されるようになる。

module UserManagement
  class Engine < Rails::Engine

    initializer("user_management.fixture_path) do
      ActiveSupport.on_load(:active_record_fixtures) do
        self.fixture_paths << "#{Rails.root}/user_management/test/fixtures}"
      end
    end
  end
end

同PRより


つっつきボイス:「前回見たプルリク#47675ウォッチ20230405)でfixtureパスを複数対応したときの追加修正ですね」「shovelって何のことかと思ったら<<演算子のことだった」

🔗 SQL警告メッセージをエラーコードでフィルタできるようになった

特定の警告コードを無視するActive Recordコンフィグ:

# 常に無視すべき警告の許可リストを設定する
config.active_record.db_warnings_ignore = [
  "1062", # MySQL Error 1062: Duplicate entry
]

この機能はMySQLアダプタとPostgreSQLアダプタでサポートされる
Nick Borromeo
同Changelogより


動機/背景
#46690db_warnings_actiondb_warnings_ignoreというコンフィグが追加された。db_warnings_ignoreは、マッチさせる警告メッセージのリストを渡せる。
GitHubにはこういう機能を持つサブスクライバがあるが、エラーコードをフィルタすることも可能になっている。エラーコードをフィルタしたい応用例は他にもありそうだし、明示的なメッセージだけでなくこういうのもフィルタできてもよいかもしれない。
詳細
このプルリクは、AbstractAdapterで警告メッセージに加えて警告コードにもマッチするロジックをwarning_ignored?メソッドに追加する
同PRより


つっつきボイス:「"1062"のように警告コードだけ書けばSQL警告メッセージをフィルタできるようにした、なるほど」「警告メッセージをすべて無視してしまうと、本来拾いたい想定外のエラーコードをキャッチできなくなるので、こういった機能はwhiltelistで管理できるのが望ましいですよね」「#46690でdb_warnings_actiondb_warnings_ignoreというコンフィグが追加されていたんですね↓」

参考: Allow SQL warnings to be reported. by adrianna-chang-shopify · Pull Request #46690 · rails/rails

🔗 OWASP勧告に沿ってレスポンスヘッダーのContent-Typeにcharsetを設定するようになった

動機/背景
pentest(ペネトレーションテスト)中に一部のレスポンスでContent-Typeヘッダーのcharsetが見つからないという問題が発生した。OWASPは、あらゆるHTTPレスポンスは安全な文字セットを指定したContent-Typeヘッダーを含めることを勧告している。

14.4.1
すべてのHTTPレスポンスにはContent-Typeヘッダーを含めるようにすること。また、Content-Typetext/*/+xmlapplication/xmlの場合は、安全な文字セット(例: UTF-8, ISO-8859-1)を指定すること。コンテンツは、提供されたContent-Typeヘッダーと一致しなければならない。
https://github.com/OWASP/ASVS/blob/v4.0.3/4.0/en/0x22-V14-Config.md#v144-http-security-headersより

詳細
コードベースをざっと見て適切な文字コードを指定したが、もしかすると見落としがあるかもしれない。
同PRより


つっつきボイス:「セキュリティ関連の修正かな」「レスポンスヘッダーにcharset付きのContent-Typeヘッダーも含めるべきとOWASPが推奨しているのか↓」

参考: ASVS/0x22-V14-Config.md at v4.0.3 · OWASP/ASVS
参考: OWASP Japan | OWASP Foundation

OWASP - Open Worldwide Application Security Project とは、Webをはじめとするソフトウェアのセキュリティ環境の現状、またセキュアなソフトウェア開発を促進する技術・プロセスに関する情報共有と普及啓発を目的としたプロフェッショナルの集まる、オープンソース・ソフトウェアコミュニティです。The OWASP Foundationは、NPO団体として全世界のOWASPの活動を支えています。
https://owasp.org/www-chapter-japan/より

「修正ではsslやリダイレクトなどでContent-Typecharset=utf-8"を追加している↓」

# actionpack/lib/action_dispatch/middleware/ssl.rb#L130
      def redirect_to_https(request)
        [ @redirect.fetch(:status, redirection_status(request)),
-         { "Content-Type" => "text/html",
+         { "Content-Type" => "text/html; charset=utf-8",
            "Location" => https_location_for(request) },
          (@redirect[:body] || []) ]
      end

charsetで指定するエンコーディングは、HTMLの<head>のメタタグで<meta charset="utf-8">のようにも書けるんですよ」「普通はそうしますよね」

参考: <meta>: メタデータ要素 - HTML: HyperText Markup Language | MDN

「charset情報がレスポンスbodyの<header><meta>タグにしかないと、ブラウザが<meta>タグを読むまでcharsetが確定できないので、パーサーの実装やcharsetの組み合わせなどによっては思わぬ挙動をする可能性がないとは言い切れない」「レスポンスヘッダーにContent-Typeがないと、特に<meta charset>がない場合の挙動がどうしてもブラウザ依存になってしまいますよね」「そういった危険性を考えると、レスポンスbodyを読み込む時点でcharsetを確定させるためには、レスポンスヘッダにcharset情報を含めるのが良いですね」

「RailsでEUCやShift_JISとかのコンテンツを返すことってあるんだろうか」「したくないです〜」「Railsでは普通しないと思いますが、そういう案件がないとは言い切れなさそう」「そうなんですよね...」

🔗 ActiveRecord::Basemarshal_dumpmarshal_loadを実装

修正: #47704
置き換え: #47722

インスタンス変数の並び順のバグはRuby 3.2.2で修正される予定だが、このような壊れやすい実装詳細に依存するのはあまりよろしくない。
さらに、現在のActive Recordインスタンスのマーシャリングは非常に効率が低く、キャッシュに含めるべきでない冗長なデータがペイロードに大量に含まれている。
この新しいフォーマットでは、シリアライズ後のペイロードに基本的なRubyコアまたはstdlibオブジェクトだけが含まれ、Railsクラスの内部表現が変更されるリスクを軽減する。

ペイロードも劇的に小さくなった。

$ ls -lh test/support/marshal_compatibility_fixtures/PostgreSQL/
1.7K rails_6_1_topic.dump
4.1K rails_6_1_topic_associations.dump
579B rails_7_1_topic.dump
806B rails_7_1_topic_associations.dump

前回のプルリク(#47722)と比較すると、既に存在しないクラスに対してキャッシュミスとして振る舞う機能が失われたものの、実装は劇的にシンプルになったので、その価値はあると思う。

ベンチマーク

ペイロードのサイズ: 4015B -> 695B

ダンプ:

Comparison:
            dump 7.1:    34382.8 i/s
            dump 6.1:    14392.6 i/s - 2.39x  slower

読み込み:

Comparison:
            load 7.1:    29420.8 i/s
            load 6.1:    13827.2 i/s - 2.13x  slower

なお、実際の高速化はレコードのサイズや関連付けの個数などで大きく変わるが、全般に相当高速になったと言ってもよいだろう。
同PRより


つっつきボイス:「Active Recordに独自のmarshal_dumpmarshal_loadが入ったんですね」「効率よさそう」「Ruby 3.2のインスタンス変数の順序に関連するバグが見つかったのをきっかけに実装したらしい↓」

参考: Ruby 3.2.1 500 errors · Issue #23644 · mastodon/mastodon
参考: Bug #19535: Instance variables order is unpredictable on objects with OBJ_TOO_COMPLEX_SHAPE_ID - Ruby master - Ruby Issue Tracking System -- issue
参考: Use an st table for "too complex" objects by tenderlove · Pull Request #7560 · ruby/ruby -- masterブランチでの修正(Ruby 3.2.2には入っていませんでした)

🔗 has_onehas_manyinverse_ofが存在する場合にforeign_keyを推論するようになった

has_many :citations, foreign_key: "book1_id", inverse_of: :book

上を以下のようにシンプルに書けるようになった。

has_many :citations, inverse_of: :book

foreign_keyは対応するbelongs_to関連付けから読み取るようになる。
Daniel Whitney
同PRより


動機/背景
このプルリクは、has_one関連付けやhas_many関連付けの振る舞いを使いやすくする。新しい動作では、Railsがドキュメントに基づいて行っているだろうと自分が思っていた動作と一致するようになる(inverse_ofオプションがforeign_keyオプションに依存するかどうかについては明示されていなかった)。

これにより、has_one関連付けとhas_many関連付けにinverse_ofオプションが存在する場合は、自動的にforeign_keyオプションを推論するようになる。

詳細
inverse_ofが存在する場合、関連付けられているモデルのforeign_keyがどうあるべきかを知るために必要な情報は、Railsがすべて持っている。デフォルトの振る舞いは変更しないので、このプルリクで何かが壊れるとは思えない。このプルリクがなければ、推論するforeign_keyが変わるような場所はどっちみち壊れていただろう。
同PRより


つっつきボイス:「inverse_ofを付けたときに自動推論する機能は少し前にRailsに入ったけど(ウォッチ20211018)、同じことをforeign_keyでもできるようにしたようですね: 推論できるものは推論して欲しいというのはわかる👍」「inverse_ofって付け忘れがち...」

参考: § 3.8.30 config.active_record.automatic_scope_inversing
-- Rails アプリケーションを設定する - Railsガイド

🔗Rails

🔗 良いコントローラ、悪いコントローラ


つっつきボイス:「jnchitoさんの記事です」「Railsを始めて間もない人向けにとてもよくまとまった内容だと思います👍」「"fatコントローラはアンチパターン"のような定番の話を記事でまとめてくれているのもありがたいですね」

「これはコントローラで複雑なアクションを書かず、なるべく薄くRESTfulに保つというDHH的な教えですね↓: こうすることでドメインモデルの構築も多少促進されやすくなる」

良いコントローラとはズバリ、「scaffoldと全く同じ、もしくはscaffoldに限りなく近いコントローラ」です。
同記事より

参考: ドメインモデル - Wikipedia

「自分が小規模なシステムを作るときは必ずしもこの原理に沿わないこともありますが、そういうのは十分慣れてきてからの話なので、慣れるまではこの教えに従うのがいいと思います」

「"コントローラで例外処理が登場するのもアンチパターン"、現実には使わざるを得ないこともあったりしますが、それでも初心者がコントローラに例外処理を書くときはだいたい何か問題があることが多いので、初心者向けにはこう教えるのがいいと思います」

「これも現場でどうバランスさせるかが悩ましい問題↓ですが、それでも初心者に教えるときはこれでいいと思います」

viewで必要なデータを全部コントローラで用意しようとするコードをよく見かけますが、個人的には「どうしても」というとき以外はやらない方がいいと考えています。
(中略)
「じゃあ、@commentsみたいなデータはどこで用意すればいいの?」と思うかもしれませんが、簡単に取得できるデータであればviewの中で取得してしまえばOKです。
同記事より

「ビュー内でRubyコードを書いてデータ取得する方法はコードとしては直感的で読みやすいんですが、Rubyコード部分のnilチェックが甘かったりするとビューのレンダリング段階でエラーになるとか、パフォーマンス計測中にモデルの処理がビューのレンダリング処理の一部として計測されてしまうなどの問題もあるので、あえてやらないポリシーを選ぶというのもありだと思います」「なるほど」

「"メールは原則としてコントローラで送信する"は、"メール送信はモデルでやってはいけない"ことを強調したいですね: モデルからメール送信するとだいたいおかしなことになる」

「メール送信の中ではUrlHelperなどを使ってURLを埋め込んだりしてると思うんですが、メール送信ロジックをモデルに実装したうえで、コントローラ経由ではない呼び出しでそのメソッドを呼ぶと、ActionController経由であれば設定されるはずのホスト名やscheme部に設定値が入らないままメール送信ロジックが呼ばれてしまってバグったりすることもありますね」

参考: Rails API ActionView::Helpers::UrlHelper

「このDHHの言葉↓もそのとおり: Action Mailerはほぼコントローラと同じ構造で、Action Mailerのdeliverはコントローラのrenderに相当します」

DHH氏は以下のブログで「EメールはSMTPを通じて届けられるviewだと考えよう」と語っています。
実際、Mailerもメール送信時は本文の作成にはERBで書かれたviewを利用するので、「メール=view」と考えるのは理にかなっています。
同記事より

参考: Action Mailer の基礎 - Railsガイド

「モデルからメールを送信したい場合に使えるdevise_invitable、こういうのもありましたね↓」

scambra/devise_invitable - GitHub

🔗 レガシーコードをRuboCopで改善する(Ruby Weeklyより)


つっつきボイス:「Evil Martiansの記事です」「タイトルでもう想像が付く」「大変そうな作業」「.rubocop_todo.ymlが1000行超えたりしそう」

参考: Configuration :: RuboCop Docs

.rubocop_strict.ymlに必須のパターンを登録してinherit_from:に追加しているのはEvil Martiansがよく行っていますね」「なるほど」

# 同記事より
Lint/Debugger: # binding.pryやdebuggerの痕跡を残してはいけない
  Enabled: true
  Exclude: []

RSpec/Focus: # 全テストをCIで回すこと
  Enabled: true
  Exclude: []

Rails/Output: # "puts"デバッグを残してはいけない
  Enabled: true
  Exclude: []

Rails/FindEach: # eachはパフォーマンスが著しく悪化するのでfind_eachを使うこと
  Enabled: true
  Exclude: []

Rails/UniqBeforePluck: # pluck.uniqではなくuniq.pluckを使うこと
  Enabled: true
  Exclude: []
# 同記事より
 inherit_from:
   - .rubocop_todo.yml
+  - .rubocop_strict.yml

「RuboCopはプロジェクト開始時点で整備しておきたいとつくづく思いました」「そうしないとコードレビューとかで時間を大量に吸われちゃいますよね」「プロジェクトの士気を下げないためにも早期にやっておきたいですね」

🔗 inline_svg: SVG画像をサーバーサイドレンダリング(Ruby Weeklyより)

jamesmartin/inline_svg - GitHub


つっつきボイス:「inline_svgというgem名のとおり、アセットに置いたSVGファイルをインラインでレンダリングするgemですね↓」

<!-- 同リポジトリより -->
<%=
  inline_svg_tag('iconmonstr-glasses-12-icon.svg',
    aria: true, title: 'An SVG',
    desc: 'This is my SVG. There are many like it. You get the picture')
%>
<!-- 同リポジトリより -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" \
  role="img" aria-labelledby="bx6wix4t9pxpwxnohrhrmms3wexsw2o m439lk7mopdzmouktv2o689pl59wmd2">
  <title id="bx6wix4t9pxpwxnohrhrmms3wexsw2o">An SVG</title>
  <desc id="m439lk7mopdzmouktv2o689pl59wmd2">This is my SVG. There are many like it. You get the picture</desc>
</svg>

「こういうふうにオプションも追加できる、なるほど」

# 同リポジトリより
inline_svg_tag(
  "some-document.svg",
  id: 'some-id',
  class: 'some-class',
  data: {some: "value"},
  size: '30% * 20%',
  title: 'Some Title',
  desc: 'Some description',
  nocomment: true,
  preserve_aspect_ratio: 'xMaxYMax meet',
  view_box: '0 0 100 100',
  aria: true,
  aria_hidden: true,
  fallback: 'fallback-document.svg'
)

「Rails本体にこの機能があってもよさそう👍」

🔗 Railsバグを単一ファイルで再現するのに便利なバグレポートテンプレート


つっつきボイス:「今日のWebチーム内発表で教えてもらった、Railsにあるバグレポートテンプレートです」「そうそう、Railsのプルリクでよく見かける、bundler/inlineなどを使うことで、1ファイルで実行できる局所的な再現コードを書くのに便利なやつですね」「こんなのあったとは知りませんでした」

参考: rubygems/inline.rb at master · rubygems/rubygems · GitHub

# https://github.com/rails/rails/blob/main/guides/bug_report_templates/active_record_main.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", github: "rails/rails", branch: "main"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

class BugTest < Minitest::Test
  def test_association_stuff
    post = Post.create!
    post.comments << Comment.create!

    assert_equal 1, post.comments.count
    assert_equal 1, Comment.count
    assert_equal post.id, Comment.first.post.id
  end
end

guides/ディレクトリに置かれているのが意外」「バグ再現のためにRailsアプリをわざわざ構築しなくても1ファイルで再現コードを書けるのはいいですね👍」「社内で質問するときとかにも便利そう」

後で調べると、Railsガイドでもこのバグ再現テンプレートが紹介されていました↓

参考: §1.2 実行可能なテストケースを作成する -- Ruby on Rails に貢献する方法 - Railsガイド


前編は以上です。

バックナンバー(2023年度第2四半期)

週刊Railsウォッチ: Rubyオブジェクトモデルクイズの最難問ほか(20230406後編)

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

Rails公式ニュース

Ruby Weekly


CONTACT

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