Class: RuboCop::Cop::DevDoc::Migration::AvoidColumnDefault
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Migration::AvoidColumnDefault
- 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
Forms covered
The cop catches default: set at column-creation time AND default:
set later via change_column_default(..., to: <non-nil>). Both are
the same anti-pattern — a permanent default living in the schema.
change_column_default(..., to: nil) (removing a default) is not
flagged; that is the cleanup form.
Exception (auto-detected)
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. The cop suppresses the
offense when it detects a matching change_column_default ..., to: nil
for the same table and column anywhere in the same method body (def change / def up), including inside reversible do |dir| dir.up.
The exception applies symmetrically:
-
add_column ... default: Xpaired withchange_column_default ... to: nil→ no offense -
change_column_default ... to: Xpaired withchange_column_default ... to: nil→ no offense✔ (no offense — two-step pattern auto-detected) add_column :users, :profile_completion_rate, :float, default: 0.0 change_column_default :users, :profile_completion_rate, from: 0.0, to: nil
Constant Summary collapse
- MSG =
'Avoid setting `default:` in migrations. Keep business logic defaults in the application layer.'.freeze
- MSG_CHANGE =
'Avoid setting a non-nil default via `change_column_default`. ' \ '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
111 112 113 114 115 116 117 |
# File 'lib/rubocop/cop/dev_doc/migration/avoid_column_default.rb', line 111 def on_send(node) if node.method?(:change_column_default) check_change_column_default(node) elsif column_method?(node) check_column_method(node) end end |