Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom checkers #239

Merged
merged 2 commits into from
Dec 25, 2024
Merged

Conversation

toydestroyer
Copy link
Contributor

Hello 👋

I want to revisit the idea we discussed in #196, as I really want to have some custom checkers in my project that are not ready to be upstreamed yet (e.g., #192).

Currently, I’m not sure how I can do that (reopening a processor class and redefining the CHECKERS constant maybe?). So I propose an approach that is heavily inspired by the RuboCop gem.

What changed?

  1. Processors don’t have to explicitly define the list of checkers anymore; instead, checkers will register themselves automatically upon inheritance. For example, if a checker inherits from ColumnChecker, it will be added to ColumnsProcessor; from IndexChecker to IndexesProcessor, and so on.
  2. A simple writer class name is inferred automatically from the error’s slug and falls back to the DefaultMessage if the class is not found. This allows us to get rid of SLUG_TO_WRITER (something I attempted before in Dry simple writer #149). Most importantly, we can have simple writers for our custom checkers.
  3. A new configuration directive has been added: require. It works similarly to the one from RuboCop — you can require a checker or a whole gem (I haven’t tested the gem case).

How does it work?

Here’s an example of integrating the checker from #192 into a dummy project:

# Gemfile

gem 'database_consistency', group: :development, require: false
# .database_consistency.yml

require:
  - ./lib/database_consistency/checkers/enum_column_checker
# lib/database_consistency/checkers/enum_column_checker.rb

require_relative "../writers/simple/enum_column_type_mismatch"

module DatabaseConsistency
  module Checkers
    # This class checks that ActiveRecord enum is backed by enum column
    class EnumColumnChecker < EnumChecker
      Report = ReportBuilder.define(
        DatabaseConsistency::Report,
        :table_name,
        :column_name
      )

      private

      # ActiveRecord supports native enum type since version 7 and only for PostgreSQL
      def preconditions
        Helper.postgresql? && ActiveRecord::VERSION::MAJOR >= 7 && column.present?
      end

      def check
        if valid?
          report_template(:ok)
        else
          report_template(:fail, error_slug: :enum_column_type_mismatch)
        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

      def column
        @column ||= model.columns.find { |c| c.name.to_s == enum.to_s }
      end

      # @return [Boolean]
      def valid?
        column.type == :enum
      end
    end
  end
end
# lib/database_consistency/writers/simple/enum_column_type_mismatch.rb

module DatabaseConsistency
  module Writers
    module Simple
      class EnumColumnTypeMismatch < Base # :nodoc:
        private

        def template
          "column should be enum type"
        end

        def unique_attributes
          {
            table_name: report.table_name,
            column_name: report.column_name
          }
        end
      end
    end
  end
end
# db/migrate/20240914001835_create_things.rb

class CreateThings < ActiveRecord::Migration[7.2]
  def change
    create_table :things do |t|
      t.string :name, null: false
      t.string :status, default: 'active', null: false

      t.timestamps
    end
  end
end
# app/models/thing.rb

class Thing < ApplicationRecord
  enum status: { active: "active", inactive: "inactive" }
end
$ bundle exec database_consistency 
Loaded configurations: .database_consistency.yml
NullConstraintChecker fail Thing name column is required in the database but does not have a validator disallowing nil values
EnumColumnChecker fail Thing status column should be enum type

That’s it; as you can see, the project-specific checker works as expected and prints our custom error message.

Final thoughts

I would love to hear everyone’s thoughts on this. I hope I can start having custom checkers in my project soon, whether with my proposed approach or something else.

If the proposed approach is acceptable, I can work on the documentation and extend the Rails examples in the repo to demonstrate custom checkers.

Thank you.

@toydestroyer toydestroyer force-pushed the allow-custom-checkers branch 2 times, most recently from 0fdeec1 to 456e97f Compare September 14, 2024 10:29
Copy link
Owner

@djezzzl djezzzl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @toydestroyer!

This is awesome! I'm sorry it took me too long to review it.

Could you please update Rails examples and documentation? I will merge and release it right after that.

@djezzzl djezzzl force-pushed the allow-custom-checkers branch from 456e97f to 45be8e2 Compare December 25, 2024 08:28
@djezzzl djezzzl merged commit 1940867 into djezzzl:master Dec 25, 2024
13 checks passed
@djezzzl
Copy link
Owner

djezzzl commented Dec 25, 2024

Released in 2.0.0! Thank you for such a great work!

Happy Christmas and New Year!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants