ナガモト の blog

Full Cycle Developerを目指すエンジニアが有用そうな技術記事や、ポエムのようなよしなしごとを投稿するブログです。

Ruby on Rails でDB構造を扱うRidgepole gemにコントリビュートしました

業務でよくお世話になっているRidgepoleというgemにPRを出してマージされたので、その経緯を書き残しておきます。

コントリビュータの証

Distinguish same foreign key constraint on multiple columns by nagamoto · Pull Request #278 · winebarrel/ridgepole · GitHub f:id:ngmt83:20190718161748p:plain Contributors to winebarrel/ridgepole · GitHub

どや!(なおdiffはたった1行)

Ridgepoleとは?

Rails標準のマイグレーションの代替となるgemです。

github.com

複数人・チームで並列して開発を行うとデータ変更が競合したり、内容は競合していなくともschema.rbのversion部分(後述)でやたらとコンフリクトが発生します。そういった問題を解消してくれます。

ActiveRecord::Schema.define(version: 2019_07_18_0000000)

詳しくはこちらのクッ社開発ブログをご覧ください。

techlife.cookpad.com

コントリビュートに至る経緯

現職で扱うRailsアプリが巨大になり、並列する開発でDB周りのコンフリクト発生が増えてきたため、私が利用経験のあるRidgepole導入を試しはじめました。

最初に次のコマンドでDBからSchemafile作成を行いました。

bundle exec ridgepole -c config/database.yml --export --output db/Schemafile

そして作成されたSchemafileを次のコマンドでapplyしました。

bundle exec ridgepole -c config/database.yml --apply -f db/Schemafile

すると次のようなエラーが発生しました。

[ERROR] Foreign Key `xxx(["xxx", "yyy"])` already defined

このエラーを解消するために調査した結果、コントリビュートするのがシンプルで良い解決方法だと判断し、PRを作成しました。

(Schemafileに外部キー制約が重複して宣言されているというエラーだったため、デフォルト外部キー制約名を付与するオプション--dump-with-default-fk-nameを利用してもエラーが発生しました。)

エラーの再現

わかりやすくエラーを再現したほうがPRの有効性を説明しやすいと考え、シンプルなRailsアプリでエラーとなるケースを再現しました。

ユーザ間でダイレクトメッセージを送信できるアプリケーションを想定します。ERDは次の次の通り。

再現から分析

詳細はPRに書いてありますが、いずれもusersテーブルからdirect_messagesテーブルへの複数の外部キー制約を正しく区別できずに、重複した定義だとしてエラーになっていました。

外部キー制約が重複していないかチェックしている箇所はこちらです。

idx = options[:name] || [from_table, to_table]

raise "Foreign Key `#{from_table}(#{idx})` already defined" if @__definition[from_table][:foreign_keys][idx]

見た限りでは外部キー制約はその名前、もしくは制約を設ける2つテーブル名によってユニークであるか否かを判定しているようです。実際の動かしてもそのように挙動であることが確認できました。

変更を加える

コードリーディングとエラーの再現から、ほぼ間違いないレベルで改修する箇所を特定できたため、変更を加えて次のようにしました。

idx = options[:name] || [from_table, to_table, options[:column]]

raise "Foreign Key `#{from_table}(#{idx})` already defined" if @__definition[from_table][:foreign_keys][idx]

[from_table, to_table, options[:column]]により、同じ2テーブル間に複数の制約があってもカラムが指定されていればそれらが異なる制約だと区別できるようになりました。

PRを作る

英語で作成しました。正直この英文にかなり時間がかかりました。

Distinguish same foreign key constraint on multiple columns by nagamoto · Pull Request #278 · winebarrel/ridgepole · GitHub

失礼のないように、わかりやすいようにと丁寧に書いたおかげかスムーズにマージしてもらえたので非常に嬉しかったです。winebarrelさんありがとうございます。

最後に

自分には難しいと思っているかもしれませんが、やってみたらOSSのオーナーは優しいし、サクサクことが進むこともあります。 ぜひみなさんもOSSにコントリビュートしましょう!