mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
129 lines
4.7 KiB
Ruby
129 lines
4.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spec_helper'
|
|
|
|
RSpec.describe "Database" do
|
|
let(:models_todo) {
|
|
["Spree::CreditCard", "Spree::Adjustment",
|
|
"StripeAccount", "ColumnPreference",
|
|
"Spree::LineItem", "Spree::ShippingMethod",
|
|
"Spree::ShippingRate"].freeze
|
|
}
|
|
|
|
it "should have foreign keys for models with a belongs_to relationship" do
|
|
Rails.application.eager_load!
|
|
model_classes = filter_model_classes
|
|
|
|
migrations = generate_migrations(model_classes)
|
|
|
|
expect(migrations.length).to eq(0)
|
|
end
|
|
|
|
def filter_model_classes
|
|
Dir.glob(Rails.root.join('app/models/**/*.rb').to_s)
|
|
.map do |file|
|
|
relative_path = Pathname.new(file).relative_path_from(Rails.root.join('app/models')).to_s
|
|
subdirectory = File.dirname(relative_path)
|
|
base_name = File.basename(file, '.rb').camelize
|
|
subdirectory == "." ? base_name : "#{subdirectory.camelize}::#{base_name}"
|
|
end
|
|
end
|
|
|
|
def generate_migrations(model_classes)
|
|
migrations = []
|
|
filter = lambda { |model| models_todo.include?(model) }
|
|
pending_models = model_classes.select(&filter)
|
|
model_classes.reject!(&filter)
|
|
|
|
ActiveRecord::Base.descendants.each do |model_class|
|
|
next unless model_classes.include?(model_class.name)
|
|
|
|
model_class.reflect_on_all_associations(:belongs_to).each do |association|
|
|
migration = process_association(model_class, association)
|
|
migrations << migration unless migration.nil?
|
|
end
|
|
end
|
|
|
|
print_missing_foreign_key_warnings(migrations)
|
|
|
|
puts "The following models are marked as todo in #{__FILE__}:"
|
|
puts pending_models.join(", ")
|
|
|
|
migrations
|
|
end
|
|
|
|
def print_missing_foreign_key_warnings(migrations)
|
|
return if migrations.empty?
|
|
|
|
puts "Foreign key(s) appear to be absent from the database. " \
|
|
"You can add it/them using the following migration(s):"
|
|
puts migrations.join("\n")
|
|
puts "\nTo disable this warning, add the class name(s) of the model(s) to models_todo " \
|
|
"in #{__FILE__}"
|
|
end
|
|
|
|
def process_association(model_class, association)
|
|
return if association.options[:polymorphic] || association.options[:optional]
|
|
|
|
foreign_key_table_name = determine_foreign_key_table_name(model_class, association)
|
|
foreign_key_column = association.options[:foreign_key] || "#{association.name}_id"
|
|
foreign_keys = model_class.connection.foreign_keys(model_class.table_name)
|
|
|
|
# Check if there is a foreign key that already exists for the column
|
|
return if foreign_keys.any? { |fk|
|
|
fk.column == foreign_key_column &&
|
|
fk.to_table == foreign_key_table_name
|
|
}
|
|
|
|
generate_migration(model_class, foreign_key_table_name, foreign_key_column)
|
|
end
|
|
|
|
def determine_foreign_key_table_name(model_class, association)
|
|
if association.options[:class_name]
|
|
class_name = association.options[:class_name].underscore.parameterize
|
|
foreign_key_table_name = class_name.tableize
|
|
else
|
|
foreign_key_table_name = association.class_name.underscore.parameterize.tableize
|
|
namespace = model_class.name.deconstantize
|
|
|
|
unless association.class_name.deconstantize == namespace || namespace == "" ||
|
|
ActiveRecord::Base.connection.table_exists?(foreign_key_table_name)
|
|
foreign_key_table_name = "#{namespace.underscore}_#{foreign_key_table_name}"
|
|
end
|
|
end
|
|
|
|
foreign_key_table_name
|
|
end
|
|
|
|
def generate_migration(model_class, foreign_key_table_name, foreign_key_column)
|
|
migration_name = "add_foreign_key_to_#{model_class.table_name}_" \
|
|
"#{foreign_key_table_name}_#{foreign_key_column}"
|
|
migration_class_name = migration_name.camelize
|
|
orphaned_records_query = generate_orphaned_records_query(model_class, foreign_key_table_name,
|
|
foreign_key_column)
|
|
|
|
<<~MIGRATION
|
|
# Orphaned records can be found before running this migration with the following SQL:
|
|
|
|
#{orphaned_records_query}
|
|
|
|
class #{migration_class_name} < ActiveRecord::Migration[6.0]
|
|
def change
|
|
add_foreign_key :#{model_class.table_name}, :#{foreign_key_table_name}, column: :#{foreign_key_column}
|
|
end
|
|
end
|
|
MIGRATION
|
|
end
|
|
|
|
def generate_orphaned_records_query(model_class, foreign_key_table_name, foreign_key_column)
|
|
<<~SQL # rubocop:disable Rails/SquishedSQLHeredocs # Using squish deletes the newlines
|
|
# SELECT COUNT(*)
|
|
# FROM #{model_class.table_name}
|
|
# LEFT JOIN #{foreign_key_table_name}
|
|
# ON #{model_class.table_name}.#{foreign_key_column} = #{foreign_key_table_name}.id
|
|
# WHERE #{foreign_key_table_name}.id IS NULL
|
|
# AND #{model_class.table_name}.#{foreign_key_column} IS NOT NULL
|
|
SQL
|
|
end
|
|
end
|