Class: RuboCop::Cop::DevDoc::Migration::AvoidColumnDefault

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb

Overview

Avoid setting a ‘default:` value in migrations.

## Rationale Avoid adding business logic to the database. Keep it centralized in the application layer (controller or model) for easier maintenance and flexibility. A ‘default:` in a migration embeds a business-logic assumption into the database schema, which is harder to change later than code.

Instead of relying on a database default, set the value explicitly in the application — and for existing rows, backfill via a reversible migration that goes through model validations:


class AddProfileCompletionRateToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :profile_completion_rate, :float, default: 0.0
  end
end


class AddProfileCompletionRateToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :profile_completion_rate, :float

    reversible do |dir|
      dir.up do
        # Make sure Rails picks up the new column.
        User.reset_column_information

        User.where(profile_completion_rate: nil).find_each do |user|
          user.profile_completion_rate = 0.0
          # This may fail if existing records are invalid (e.g. nil required fields).
          # In that case, fix those records first rather than bypassing validation.
          user.save!
        end
      end
    end
  end
end

## Exception For performance reasons (large tables with millions of records) or when using ‘null: false`, you may temporarily set a default and then immediately remove it in the same migration:


add_column :users, :profile_completion_rate, :float, default: 0.0
change_column_default :users, :profile_completion_rate, from: 0.0, to: nil

NOTE: This cop currently flags the first line of the exception pattern. The follow-up ‘change_column_default …, to: nil` is not auto-detected.

Examples:

# bad
add_column :users, :score, :integer, default: 0

# bad
t.string :status, default: 'active'

# good
add_column :users, :score, :integer

# good (temporary default immediately removed)
add_column :users, :score, :integer, default: 0
change_column_default :users, :score, from: 0, to: nil

Constant Summary collapse

MSG =
'Avoid setting `default:` in migrations. Keep business logic defaults in the application layer.'.freeze
COLUMN_METHODS =
%i[
  string integer float boolean datetime date text binary decimal
  json jsonb bigint primary_key references belongs_to
].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



83
84
85
86
87
# File 'lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb', line 83

def on_send(node)
  return unless node.method?(:add_column) || COLUMN_METHODS.include?(node.method_name)

  check_options(node.arguments.find(&:hash_type?))
end