Created
May 23, 2021 12:42
-
-
Save alpaca-tc/d53dee5977746256717c7522988b13d8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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