database_consistency
Advanced tools
| # frozen_string_literal: true | ||
| module DatabaseConsistency | ||
| module Checkers | ||
| # This class checks for models that need a dependent destroy association | ||
| class MissingDependentDestroyChecker < AssociationChecker | ||
| Report = ReportBuilder.define( | ||
| DatabaseConsistency::Report, | ||
| :model_name, | ||
| :attribute_name | ||
| ) | ||
| DEPENDENT_OPTIONS = %i[destroy delete delete_all nullify].freeze | ||
| private | ||
| def preconditions | ||
| association.belongs_to? && foreign_key_exists? | ||
| end | ||
| def check | ||
| if dependent_destroy_exists? || cascade? | ||
| report_template(:ok) | ||
| else | ||
| report_template(:fail, error_slug: :missing_dependent_destroy) | ||
| end | ||
| end | ||
| def dependent_destroy_exists? | ||
| association.class_name.constantize.reflect_on_all_associations.any? do |association| | ||
| %i[has_many has_one].include?(association.macro) && | ||
| DEPENDENT_OPTIONS.include?(association.options[:dependent]) && | ||
| association.table_name == model.table_name | ||
| end | ||
| end | ||
| def foreign_key | ||
| association.klass | ||
| .connection | ||
| .foreign_keys(model.table_name) | ||
| .find { |fk| fk.column == association.foreign_key.to_s } | ||
| end | ||
| def cascade? | ||
| %i[cascade nullify].include? foreign_key.options[:on_delete] | ||
| end | ||
| def report_template(status, error_slug: nil) | ||
| Report.new( | ||
| status: status, | ||
| error_message: nil, | ||
| error_slug: error_slug, | ||
| model_name: association.class_name, | ||
| attribute_name: model.table_name, | ||
| **report_attributes | ||
| ) | ||
| end | ||
| end | ||
| end | ||
| end |
| # frozen_string_literal: true | ||
| module DatabaseConsistency | ||
| module Writers | ||
| module Simple | ||
| class MissingDependentDestroy < Base # :nodoc: | ||
| private | ||
| def template | ||
| 'should have a corresponding has_one/has_many association with dependent option (destroy, delete, delete_all, nullify) or a foreign key with on_delete (cascade, nullify)' # rubocop:disable Layout/LineLength | ||
| end | ||
| def unique_attributes | ||
| { | ||
| model_name: report.model_name, | ||
| attribute_name: report.attribute_name | ||
| } | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
@@ -46,2 +46,3 @@ # frozen_string_literal: true | ||
| require 'database_consistency/writers/simple/implicit_order_column_missing' | ||
| require 'database_consistency/writers/simple/missing_dependent_destroy' | ||
| require 'database_consistency/writers/simple_writer' | ||
@@ -87,2 +88,3 @@ | ||
| require 'database_consistency/checkers/association_checkers/missing_association_class_checker' | ||
| require 'database_consistency/checkers/association_checkers/missing_dependent_destroy_checker' | ||
@@ -89,0 +91,0 @@ require 'database_consistency/checkers/column_checkers/column_checker' |
@@ -26,4 +26,11 @@ # frozen_string_literal: true | ||
| end | ||
| def foreign_key_exists? # rubocop:disable Metrics/AbcSize | ||
| model.connection.foreign_keys(model.table_name).any? do |foreign_key| | ||
| (Helper.extract_columns(association.foreign_key.to_s) - Array.wrap(foreign_key.column)).empty? && | ||
| foreign_key.to_table == association.klass.table_name | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end |
@@ -45,9 +45,4 @@ # frozen_string_literal: true | ||
| # | missing | fail | | ||
| def check # rubocop:disable Metrics/AbcSize | ||
| fk_exist = model.connection.foreign_keys(model.table_name).any? do |fk| | ||
| (Helper.extract_columns(association.foreign_key.to_s) - Array.wrap(fk.column)).empty? && | ||
| fk.to_table == association.klass.table_name | ||
| end | ||
| if fk_exist | ||
| def check | ||
| if foreign_key_exists? | ||
| report_template(:ok) | ||
@@ -54,0 +49,0 @@ else |
@@ -31,3 +31,3 @@ # frozen_string_literal: true | ||
| def check | ||
| if unique_index | ||
| if unique_index || primary_key_covers_validation? | ||
| report_template(:ok) | ||
@@ -56,2 +56,10 @@ else | ||
| def primary_key_covers_validation? | ||
| primary_key = model.connection.primary_key(model.table_name) | ||
| return false if primary_key.blank? | ||
| sorted_uniqueness_validator_columns == Helper.extract_columns(primary_key).sort | ||
| end | ||
| def sorted_uniqueness_validator_columns | ||
@@ -58,0 +66,0 @@ @sorted_uniqueness_validator_columns ||= Helper.sorted_uniqueness_validator_columns(attribute, validator, model) |
| # frozen_string_literal: true | ||
| module DatabaseConsistency | ||
| VERSION = '2.0.8' | ||
| VERSION = '2.1.0' | ||
| end |