管理画面でユーザーを削除しようとしたら ActiveRecord::InvalidForeignKey
エラーが発生!!
そうです、DBには外部キー制約が指定されているのに、モデルでは関連(has_many :emails, dependent: :destroy
)を定義していなかったのです。もしかしたら、他にも関連の定義漏れがあるかもしれません。
でも、何十個もあるモデル/テーブルを1組ずつ目で見て付き合わせるのは、非効率で、信頼できません。
ここは、動的にモデル/テーブルの内容を取得して比較するスクリプトが必要です。
モデルに定義された関連を取得する
Model.reflections
で取得できます。
.reflections
はハッシュで、キーが関連名(だと思う)で、値が関連を表すオブジェクトです。関連の種類(has_many
, belongs_to
など)は値のクラスで判別できます。
[16] pry(main)> User.reflections['emails']
=> #<ActiveRecord::Reflection::HasManyReflection:0x00007f8295db3e08
@active_record=
User(...),
@association_scope_cache={},
@automatic_inverse_of=nil,
@constructable=true,
@foreign_type="emails_type",
@klass=nil,
@name=:emails,
@options={:dependent=>:destroy},
@plural_name="emails",
@scope=nil,
@scope_lock=#<Thread::Mutex:0x00007f8295db3c00>,
@type=nil>
- https://github.com/rails/rails/blob/master//activerecord/lib/active_record/reflection.rb#L146
- https://api.rubyonrails.org/classes/ActiveRecord/Reflection/ClassMethods.html#method-i-reflections
外部キーを取得する
ApplicationRecord.connection.foreign_keys(table_name)
で取得できます。
[17] pry(main)> ApplicationRecord.connection.foreign_keys('emails')
=> [#<struct ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
from_table="emails",
to_table="users",
options=
{:column=>"user_id", :name=>"fk_rails_328da208df", :primary_key=>"id", :on_delete=>nil, :on_update=>nil}>]
.foreign_keys
は api.rubyonrails.org には載っていますが、ドキュメントはありません。
両者を比較するスクリプト
上2つのメソッドを組み合わせて、以下のようなスクリプトを作りました。なお、やっつけ仕事なので、ご自分で使うときには、例えば
- テーブル名とモデル名が異なるので、変換が必要
- DBが複数あるのでDBごとに比較しなければならない
-
dependent: :destroy
が付いているかのチェックも必要
といった改良も必要かもしれません。
conn = ApplicationRecord.connection
db_foreign_keys =
conn.tables.map(&:to_s).flat_map do |t|
conn.foreign_keys(t).map do |fk|
[fk.from_table, fk.to_table]
end
end.sort
Rails.application.eager_load!
model_relations =
ApplicationRecord.subclasses.flat_map do |m|
m.reflections.map do |name, r|
if r.is_a?(ActiveRecord::Reflection::HasManyReflection) || r.is_a?(ActiveRecord::Reflection::HasOneReflection)
[name, m.table_name]
else
nil
end
end.compact
end.sort
pp db_foreign_keys
pp model_relations
pp db_foreign_keys - model_relations