🐈

【Rails】db:migrateコマンドで、annotateが実行されない件の深掘り

2024/12/24に公開

背景

db:migrateコマンドを実行した際、annotateというgemをインストールしていると、modelファイルにスキーマ情報を書き出してくれます。

https://github.com/ctran/annotate_models/tree/main

カラム追加したのに、変更内容のスキーマ情報が上手く反映されない問題が発生し、gemのコードを読みながら原因について深掘りしてみました。

環境

Rails 6.1.X
ruby 3.1.X
annotate3.2.X

結論

対象テーブルのカラムにコメント情報を持つカラムを追加した際に発生する可能性がある。
対象テーブルのすでにあるスキーマ情報に手を加えている場合発生する可能性がある。
(詳しくは後述)

対処としては、一度該当ファイルのannotateにより出力されているスキーマ情報を一度削除してから再度db:migrateすることで、スキーマ情報は再生成される。

※以下記事にも対処法の記載がありました。
https://qiita.com/shungo_m/items/0f8b556808210a0e4843

もしくは、forceオプションをつかう。(方法については後述)

深掘り

ReadMeを読む

https://github.com/ctran/annotate_models

関連しそうな箇所が以下でした。

WARNING
Don't add text after an automatically-created comment block. This tool will blow away the initial/final comment block in your models if it looks like it was previously added by this gem.

自動的に作成されるコメント ブロックの後にテキストを追加しないでください。
コメントブロック内に付け足したコメントは消えてしまう。

という注意喚起をしてくれています。
ので、コメントブロックの文字列は置換をおこなっていそうと推測。

コードを読む

db:migrate コマンドが行われてからの処理を追い、
スキーマ情報を置換していそうな部分を以下に特定しました。

annotate_models/lib/annotate/annotate_models.rb ファイルの annotate_one_file メソッド
https://github.com/ctran/annotate_models/blob/develop/lib/annotate/annotate_models.rb#L426-L477

対象ファイルの文字列(old_content 変数)とmigrateが行われた後のスキーマ情報の文字列(info_block 変数)を比較します。スキーマ情報に差分があると判断されればtrueを返し、後続の処理でnew_contentの内容でスキーマ情報を書き換えます。
逆に差分がないと判断されれば、falseを返し、スキーマ情報は変更されません。

今回の原因となったのは、以下の処理でした。

header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
old_header = old_content.match(header_pattern).to_s
new_header = info_block.match(header_pattern).to_s

column_pattern = /^#[\t ]+[\w\*\.`]+[\t ]+.+$/
old_columns = old_header && old_header.scan(column_pattern).sort
new_columns = new_header && new_header.scan(column_pattern).sort

return false if old_columns == new_columns && !options[:force]

column_pattern の正規表現を確認してみます。

  • ^#[\t ]+ 行の先頭に文字 # があり、タブ or スペースが1回以上
  • [\w\*\.]+ 英数字およびアンダーバー「*」「.」の文字集合が1文字以上
  • [\t ]+ 再度タブ or スペースが1回以上
  • .+$ 任意の文字列が続き、行末となる

つまり、スキーマ情報にカラムの説明コメントを加えた時に出力される以下のようなカラムは、[\w\*\.]+によりscanメソッドでマッチしないこととなります。(括弧があることでマッチしない。)
※スキーマ情報に手を加えて、column_patternにマッチしない場合もありそうです。

#  id(ID)                           :uuid             not null, primary key

そこで、以下により差分がないと判断され、早期リターンされてしまいます。
old_columns == new_columns

こちらで早期リターンされないように、
old_columnsにあたる現状のスキーマ情報を消してしまう。もしくは、options[:force]をtrueとすることで、後続の処理で、
migrateが行われた後のスキーマ情報の文字列(info_block 変数)
を元にした新しいスキーマ情報で文字列置換がされるようになります。

forceオプションをtrueにする

annotateをインストールすると、作成されるlib/tasks/auto_annotate_models.rakeファイル中の

'force' => 'false',

をtrueにすることで差分に関わらず、全てのテーブルのスキーマ情報ブロックを再生成します。

原因と対策について腑に落ちたのでここでの深掘りは終了します。

所感

カラムへのコメント追加フォーマットの()がはいると、column_pattern にマッチしないのはバグなのか、PRを送って確認してみたいところでした。()の部分が解消しても、日本語でコメント追加した場合は()関係なくマッチしないはずですが。。

カラムに説明コメントをつけることが多い場合は、forceオプションをtrueにして運用したほうがよいかもしれないと思いました。差分に関わらず再生成されますが、そこまで重い処理ではないので大きな問題はなさそうです。
※readmeにある通り、意図しない部分の文字列を置換しても問題ないようにgit等で管理されていることは必須

Goals Tech Blog

Discussion