Class: RuboCop::Cop::Discourse::NoAddReferenceOrAliasesActiveRecordMigration

Inherits:
RuboCop::Cop
  • Object
show all
Defined in:
lib/rubocop/cop/discourse/no_add_reference_active_record_migrations.rb

Overview

The methods:

  • add_reference

  • add_belongs_to

  • t.references

  • t.belongs_to

in ActiveRecord migrations are magic, and they all do some unexpected things in the background. For example, by default add_reference adds an index at the same time, but not concurrently, which is a nightmare for large tables.

Instead, inside a disable_ddl_transaction! migration we should create the new column (with any defaults and options required) and the index concurrently using hand-written SQL. This also allows us to handle IF NOT EXISTS cases, which enable re-runnable migrations. Also we can pick a better name for the index at the same time.

# bad def up

add_reference :posts, :image_upload
# or add_belongs_to :posts, :image_upload
# or t.references :image_upload when doing create_table do |t|
# or t.belongs_to :image_upload when doing create_table do |t|

end

# good disable_ddl_transaction! def up

execute <<~SQL
  ALTER TABLE posts
  ADD COLUMN IF NOT EXISTS image_upload_id bigint
SQL

execute <<~SQL
  CREATE INDEX CONCURRENTLY IF NOT EXISTS
  index_posts_on_image_upload_id ON posts USING btree (image_upload_id)
SQL

end

Constant Summary collapse

MSG =
<<~MSG
  AR methods add_reference, add_belongs_to, t.references, and t.belongs_to are
  high-risk for large tables and have too many background magic operations.
  Instead, write a disable_ddl_transactions! migration and write custom SQL to
  add the new column and CREATE INDEX CONCURRENTLY. Use the IF NOT EXISTS clause
  to make the migration re-runnable if it fails partway through.
MSG

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



72
73
74
75
76
77
78
79
80
# File 'lib/rubocop/cop/discourse/no_add_reference_active_record_migrations.rb', line 72

def on_send(node)
  return if [
    using_add_reference?(node),
    using_add_belongs_to?(node),
    using_t_references?(node),
    using_t_belongs_to?(node)
  ].none?
  add_offense(node, message: MSG)
end