# frozen_string_literal: true require 'spec_helper' RSpec.describe "Database" do excluded_models = ["Gateway", "PayPalExpress", "Bogus", "BogusSimple"].freeze it "should have foreign keys for models with a belongs_to relationship" do pending "Consider adding foreign keys" 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 { |file| File.basename(file, '.rb').camelize } .reject { |model| excluded_models.include?(model) } end def generate_migrations(model_classes) migrations = [] previous_models = {} ActiveRecord::Base.descendants.each do |model_class| next unless model_classes.include?(model_class.name.demodulize) model_class.reflect_on_all_associations(:belongs_to).each do |association| migration = process_association(model_class, association, previous_models) migrations << migration unless migration.nil? end end if migrations 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 "To disable this warning, add the class name(s) of the model(s) to EXCLUDED_MODELS " \ "in /spec/models/missing_foreign_keys_spec.rb" end migrations end def process_association(model_class, association, previous_models) return if association.options[:polymorphic] foreign_key_table_name = determine_foreign_key_table_name(model_class, association) foreign_key_column = "#{association.options[:foreign_key] || association.name}_id" # Filter out duplicate migrations return if duplicate_migration?(model_class, foreign_key_table_name, previous_models) previous_models[model_class.table_name] ||= [] previous_models[model_class.table_name] << foreign_key_table_name generate_migration(model_class, association, 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, _association, foreign_key_table_name, foreign_key_column) migration_name = "add_foreign_key_to_#{model_class.table_name}_#{foreign_key_table_name}" migration_class_name = migration_name.camelize <<~MIGRATION 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 duplicate_migration?(model_class, foreign_key_table_name, previous_models) model_class.connection.foreign_key_exists?(model_class.table_name, foreign_key_table_name) || previous_models[model_class.table_name]&.include?(foreign_key_table_name) end end