Skip to content

Instantly share code, notes, and snippets.

@alpaca-tc
Created May 23, 2021 12:42
Show Gist options
  • Save alpaca-tc/d53dee5977746256717c7522988b13d8 to your computer and use it in GitHub Desktop.
Save alpaca-tc/d53dee5977746256717c7522988b13d8 to your computer and use it in GitHub Desktop.
# frozen_string_literal: true
RSpec.describe 'ActiveRecord associations' do
let(:connection) { ActiveRecord::Base.connection }
let(:table_names) { connection.tables }
let(:models) { table_names.map { table_to_model(_1) } }
# テスト対象から除外する関連を定義
let(:ignored_reflections) do
# { table_names => [association_name, ...] }
# example: { users: %i[profile] }
{}
end
def filter_reflections(from_model, to_model)
from_model.reflections.values.reject(&:polymorphic?).select do |reflection|
klass = reflection.klass
klass == to_model || klass.base_class == to_model
end
end
def find_belongs_to_reflection(from_model, to_model)
from_model.reflect_on_all_associations(:belongs_to).reject(&:polymorphic?).find do
[_1.klass, _1.klass.base_class].include?(to_model)
end
end
def find_has_xxx_reflection(from_model, to_model)
reflections = (from_model.reflect_on_all_associations(:has_one) + from_model.reflect_on_all_associations(:has_many)).reject(&:through_reflection?).reject(&:polymorphic?)
reflections.find do
_1.klass == to_model && !_1.options.key?(:class_name)
end
end
def table_to_model(table_name)
table_name.singularize.classify.constantize
end
def unique_index_foreign_key?(foreign_key)
found_index = connection.indexes(foreign_key.from_table).find do
_1.columns == [foreign_key.options[:column]]
end
found_index&.unique
end
def dependent_options(foreign_key)
case foreign_key.options[:on_delete]
when :cascade
{ dependent: :destroy }
when :nullify
{ dependent: :nullify }
when nil, :restrict
{ dependent: :restrict_with_error }
else
raise "Unnknown dependent given. #{foreign_key.options[:on_delete]}"
end
end
it 'has no unindexed foreign keys' do
missing_belongs_to_reflections = []
missing_has_xxx_reflections = []
table_names.each do |table_name|
foreign_keys = connection.foreign_keys(table_name)
foreign_keys.each do |foreign_key|
# 無視するreflectionの対象はスキップする
next if (ignored_reflections[foreign_key.from_table.to_sym] || []).include?(foreign_key.to_table.to_sym)
from_model = table_to_model(foreign_key.from_table)
to_model = table_to_model(foreign_key.to_table)
belongs_to_reflection = find_belongs_to_reflection(from_model, to_model)
if belongs_to_reflection.nil?
# check belongs_to
missing_belongs_to_reflections.push([from_model, to_model, foreign_key])
else
# check has_many/has_one and dependent option
has_xxx_reflection = find_has_xxx_reflection(belongs_to_reflection.klass, from_model)
missing_has_xxx_reflections.push([to_model, from_model, foreign_key]) if has_xxx_reflection.nil? || !has_xxx_reflection.options.key?(:dependent)
end
end
end
expect(missing_belongs_to_reflections).to be_empty, -> {
missing_associations = missing_belongs_to_reflections.map do |(from_model, to_model, _foreign_key)|
%(#{from_model.name}.belongs_to :#{to_model.model_name.singular})
end
<<~EOS
Not found association.
#{missing_associations.join("\n")}
EOS
}
expect(missing_has_xxx_reflections).to be_empty, -> {
missing_associations = missing_has_xxx_reflections.map do |(from_model, to_model, foreign_key)|
association_type, association_name = if unique_index_foreign_key?(foreign_key)
[
'has_one',
to_model.model_name.singular
]
else
[
'has_many',
to_model.model_name.plural
]
end
options = dependent_options(foreign_key).to_a.first
%(#{from_model.name}.#{association_type} :#{association_name}#{", #{options[0]}: :#{options[1]}" if options})
end
<<~EOS
Missing associations.
#{missing_associations.join("\n")}
EOS
}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment