diff --git a/lib/database_consistency.rb b/lib/database_consistency.rb index a2f485c..4a82d8d 100644 --- a/lib/database_consistency.rb +++ b/lib/database_consistency.rb @@ -38,6 +38,7 @@ require 'database_consistency/writers/simple/enum_values_inconsistent_with_ar_enum' require 'database_consistency/writers/simple/enum_values_inconsistent_with_inclusion' require 'database_consistency/writers/simple/redundant_case_insensitive_option' +require 'database_consistency/writers/simple/three_state_boolean' require 'database_consistency/writers/simple_writer' require 'database_consistency/writers/autofix/helpers/migration' @@ -71,6 +72,7 @@ require 'database_consistency/checkers/column_checkers/length_constraint_checker' require 'database_consistency/checkers/column_checkers/primary_key_type_checker' require 'database_consistency/checkers/column_checkers/enum_value_checker' +require 'database_consistency/checkers/column_checkers/three_state_boolean_checker' require 'database_consistency/checkers/validator_checkers/validator_checker' require 'database_consistency/checkers/validator_checkers/missing_unique_index_checker' diff --git a/lib/database_consistency/checkers/column_checkers/three_state_boolean_checker.rb b/lib/database_consistency/checkers/column_checkers/three_state_boolean_checker.rb new file mode 100644 index 0000000..0615f58 --- /dev/null +++ b/lib/database_consistency/checkers/column_checkers/three_state_boolean_checker.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module DatabaseConsistency + module Checkers + # This class checks missing NOT NULL constraint for boolean columns + class ThreeStateBooleanChecker < ColumnChecker + Report = ReportBuilder.define( + DatabaseConsistency::Report, + :table_name, + :column_name + ) + + private + + def preconditions + column.type == :boolean + end + + def check + if valid? + report_template(:ok) + else + report_template(:fail, error_slug: :three_state_boolean) + end + end + + def report_template(status, error_slug: nil) + Report.new( + status: status, + error_slug: error_slug, + error_message: nil, + table_name: model.table_name, + column_name: column.name, + **report_attributes + ) + end + + # @return [Boolean] + def valid? + !column.null + end + end + end +end diff --git a/lib/database_consistency/processors/columns_processor.rb b/lib/database_consistency/processors/columns_processor.rb index e2b483a..e033384 100644 --- a/lib/database_consistency/processors/columns_processor.rb +++ b/lib/database_consistency/processors/columns_processor.rb @@ -8,7 +8,8 @@ class ColumnsProcessor < BaseProcessor Checkers::NullConstraintChecker, Checkers::LengthConstraintChecker, Checkers::PrimaryKeyTypeChecker, - Checkers::EnumValueChecker + Checkers::EnumValueChecker, + Checkers::ThreeStateBooleanChecker ].freeze private diff --git a/lib/database_consistency/writers/autofix_writer.rb b/lib/database_consistency/writers/autofix_writer.rb index 12a2089..dc54851 100644 --- a/lib/database_consistency/writers/autofix_writer.rb +++ b/lib/database_consistency/writers/autofix_writer.rb @@ -16,7 +16,8 @@ class AutofixWriter < BaseWriter null_constraint_missing: Autofix::NullConstraintMissing, redundant_index: Autofix::RedundantIndex, redundant_unique_index: Autofix::RedundantIndex, - small_primary_key: Autofix::InconsistentTypes + small_primary_key: Autofix::InconsistentTypes, + three_state_boolean: Autofix::NullConstraintMissing }.freeze def write diff --git a/lib/database_consistency/writers/simple/three_state_boolean.rb b/lib/database_consistency/writers/simple/three_state_boolean.rb new file mode 100644 index 0000000..895caf0 --- /dev/null +++ b/lib/database_consistency/writers/simple/three_state_boolean.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module DatabaseConsistency + module Writers + module Simple + class ThreeStateBoolean < Base # :nodoc: + private + + def template + 'boolean column should have NOT NULL constraint' + end + + def unique_attributes + { + table_name: report.table_name, + column_name: report.column_name + } + end + end + end + end +end diff --git a/lib/database_consistency/writers/simple_writer.rb b/lib/database_consistency/writers/simple_writer.rb index 091ef30..96dd783 100644 --- a/lib/database_consistency/writers/simple_writer.rb +++ b/lib/database_consistency/writers/simple_writer.rb @@ -28,7 +28,8 @@ class SimpleWriter < BaseWriter missing_foreign_key_cascade: Simple::MissingForeignKeyCascade, enum_values_inconsistent_with_ar_enum: Simple::EnumValuesInconsistentWithArEnum, enum_values_inconsistent_with_inclusion: Simple::EnumValuesInconsistentWithInclusion, - redundant_case_insensitive_option: Simple::RedundantCaseInsensitiveOption + redundant_case_insensitive_option: Simple::RedundantCaseInsensitiveOption, + three_state_boolean: Simple::ThreeStateBoolean }.freeze def write diff --git a/spec/checkers/three_state_boolean_checker_spec.rb b/spec/checkers/three_state_boolean_checker_spec.rb new file mode 100644 index 0000000..d47224f --- /dev/null +++ b/spec/checkers/three_state_boolean_checker_spec.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +RSpec.describe DatabaseConsistency::Checkers::ThreeStateBooleanChecker, :sqlite, :mysql, :postgresql do + subject(:checker) { described_class.new(model, column) } + + let(:klass) { define_class } + let(:model) { klass } + let(:column) { klass.columns.first } + + context 'when column is nullable and without default' do + before do + define_database_with_entity do |table| + table.boolean :active + end + end + + specify do + expect(checker.report).to have_attributes( + checker_name: 'ThreeStateBooleanChecker', + table_or_model_name: klass.name, + column_or_attribute_name: 'active', + status: :fail, + error_message: nil, + error_slug: :three_state_boolean, + table_name: klass.table_name, + column_name: 'active' + ) + end + end + + context 'when column is not nullable and without default' do + before do + define_database_with_entity do |table| + table.boolean :active, null: false + end + end + + specify do + expect(checker.report).to have_attributes( + checker_name: 'ThreeStateBooleanChecker', + table_or_model_name: klass.name, + column_or_attribute_name: 'active', + status: :ok, + error_message: nil, + error_slug: nil, + table_name: klass.table_name, + column_name: 'active' + ) + end + end + + context 'when column is nullable and with default' do + before do + define_database_with_entity do |table| + table.boolean :active, default: true + end + end + + specify do + expect(checker.report).to have_attributes( + checker_name: 'ThreeStateBooleanChecker', + table_or_model_name: klass.name, + column_or_attribute_name: 'active', + status: :fail, + error_message: nil, + error_slug: :three_state_boolean, + table_name: klass.table_name, + column_name: 'active' + ) + end + end + + context 'when column is not nullable and with default' do + before do + define_database_with_entity do |table| + table.boolean :active, null: false, default: true + end + end + + specify do + expect(checker.report).to have_attributes( + checker_name: 'ThreeStateBooleanChecker', + table_or_model_name: klass.name, + column_or_attribute_name: 'active', + status: :ok, + error_message: nil, + error_slug: nil, + table_name: klass.table_name, + column_name: 'active' + ) + end + end +end