[ActiveRecord] blocklist : ignored_columns :: allowlist : feature_idea

ActiveRecord has the ActiveRecord::Base.ignored_columns = %w(some columns to ignore) method to blocklist columns that ActiveRecord loads from the database. Spelunking the ActiveRecord source, I found no corresponding method that acts as an allowlist for columns that will be loaded.I found myself reaching for an allowlist as I started to take advantage of Rails 6’s multi-database support. In my context, data from another database that used to be wrapped in a REST endpoint can now be accessed directly. When making this direct access, I want to ignore every attribute except the few I am specifically interested in. Because we’re in a multiple-app environment, we don’t necessarily have control over columns being added or dropped from the secondary database during runtime. We don’t want our application to go down because another team removed an experimental column from their database.To solve this problem with ignored_columns, we had to enumerate all the existing columns.

class Dogs < AnimalsBase
  self.ignored_columns = %w(some list of columns that can never be exhaustive because new columns could be added all the time)

This suffers from the problem of new columns could be added at any time. The list of ignored columns has to be constantly tended to ensure we’re robust against a runtime error of columns going away.What I wanted to reach for was something like

class Dogs < AnimalsBase
  self.allowed_columns = %w(id and only the exact columns needed)

With this approach, we’re robust against the other database adding and removing columns. We still run the risk of encountering runtime errors if one of our exactly-requested columns goes away. I can’t come up with a way to mitigate that risk aside from improving communications between teams.I’m not solid on the name allowed_columns but it was the first thing I reached for.For the time being, I implemented this with a monkey patch to load_schema!

ActiveRecord::Base.concerning "AllowedColumns" do
  included do
    self.allowed_columns = [].freeze

  module ClassMethods
    def allowed_columns
      if defined?(@allowed_columns)

    def allowed_columns=(columns)
      @allowed_columns = columns.map(&:to_s)

    # copied from from [https://github.com/rails/rails/blob/2dea8c29c794bec564a2e69ad715d25024e93932/activerecord/lib/active_record/model_schema.rb#L484-L497](https://github.com/rails/rails/blob/2dea8c29c794bec564a2e69ad715d25024e93932/activerecord/lib/active_record/model_schema.rb#L484-L497)
    def load_schema!
      unless table_name
        raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
      @columns_hash = connection.schema_cache.columns_hash(table_name).except(*ignored_columns)
      @columns_hash = @columns_hash.slice(*allowed_columns) if allowed_columns.present? # This is the additional line
      @columns_hash.each do |name, column|
          default: column.default,
          user_provided_default: false

Is this a feature that others would find useful now that we’re working with first-class multi-database support?

I’m not from the core but in my experience the chances are better opening a PR with the feature, specially if the implementation is simple.

I can even review the code, I’ve made a few PRs before with the implementation of the ignored columns and I’m familiar with the code.