【Rails】db:migrateコマンドで、annotateが実行されない件の深掘り
背景
db:migrate
コマンドを実行した際、annotateというgemをインストールしていると、modelファイルにスキーマ情報を書き出してくれます。
カラム追加したのに、変更内容のスキーマ情報が上手く反映されない問題が発生し、gemのコードを読みながら原因について深掘りしてみました。
環境
Rails 6.1.X
ruby 3.1.X
annotate3.2.X
結論
対象テーブルのカラムにコメント情報を持つカラムを追加した際に発生する可能性がある。
対象テーブルのすでにあるスキーマ情報に手を加えている場合発生する可能性がある。
(詳しくは後述)
対処としては、一度該当ファイルのannotateにより出力されているスキーマ情報を一度削除してから再度db:migrate
することで、スキーマ情報は再生成される。
※以下記事にも対処法の記載がありました。
もしくは、forceオプションをつかう。(方法については後述)
深掘り
ReadMeを読む
関連しそうな箇所が以下でした。
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
メソッド
対象ファイルの文字列(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等で管理されていることは必須
Discussion