class RuboCop::Cop::Rails::BulkChangeTable

end
end
t.string :nickname
t.string :name, null: false
change_table :users, bulk: false do |t|
def change
# When you don’t want to combine alter queries.
# good
end
end
t.string :nickname
t.string :name, null: false
change_table :users, bulk: true do |t|
def change
# good
end
end
t.string :nickname
t.string :name, null: false
change_table :users do |t|
def change
# bad
@example
end
# ADD ‘nickname` varchar(255)
# ALTER TABLE `users` ADD `name` varchar(255) NOT NULL,
end
t.string :nickname
t.string :name, null: false
change_table :users, bulk: true do |t|
def change
# good
end
# ALTER TABLE `users` ADD `nickname` varchar(255)
# ALTER TABLE `users` ADD `name` varchar(255) NOT NULL
add_column :users, :nickname, :string
add_column :users, :name, :string, null: false
def change
# bad
@example
this Cop ignores offenses.
If the adapter is not `mysql2`, `trilogy`, `postgresql`, or `postgis`,
when the `Database` option is not set.
in `config/database.yml` or the environment variable `DATABASE_URL`
automatically detect an adapter from `development` environment
the PostgreSQL (5.2 later) adapter; thus it will
The `bulk` option is only supported on the MySQL and
ALTER TABLE statement combining multiple column alterations.
This option causes the migration to generate a single
to use `change_table` with `bulk: true` instead.
If combinable queries are detected, it suggests to you
Checks whether alter queries are combinable.

def add_offense_for_alter_methods(node)

Parameters:
  • node (RuboCop::AST::SendNode) --
def add_offense_for_alter_methods(node)
  # arguments: [{(sym :table)(str "table")} ...]
  table_node = node.first_argument
  return unless table_node.is_a? RuboCop::AST::BasicLiteralNode
  message = format(MSG_FOR_ALTER_METHODS, table: table_node.value)
  add_offense(node, message: message)
end

def add_offense_for_change_table(node)

Parameters:
  • node (RuboCop::AST::SendNode) --
def add_offense_for_change_table(node)
  add_offense(node, message: MSG_FOR_CHANGE_TABLE)
end

def call_to_combinable_alter_method?(child_node)

def call_to_combinable_alter_method?(child_node)
  child_node.send_type? && combinable_alter_methods.include?(child_node.method_name)
end

def combinable_alter_methods

def combinable_alter_methods
  case database
  when MYSQL
    COMBINABLE_ALTER_METHODS + MYSQL_COMBINABLE_ALTER_METHODS
  when POSTGRESQL
    result = COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
    result += POSTGRESQL_COMBINABLE_ALTER_METHODS_SINCE_6_1 if target_rails_version >= 6.1
    result
  end
end

def combinable_transformations

def combinable_transformations
  case database
  when MYSQL
    COMBINABLE_TRANSFORMATIONS + MYSQL_COMBINABLE_TRANSFORMATIONS
  when POSTGRESQL
    result = COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
    result += POSTGRESQL_COMBINABLE_TRANSFORMATIONS_SINCE_6_1 if target_rails_version >= 6.1
    result
  end
end

def count_transformations(send_nodes)

def count_transformations(send_nodes)
  send_nodes.sum do |node|
    if node.method?(:remove)
      node.arguments.count { |arg| !arg.hash_type? }
    else
      combinable_transformations.include?(node.method_name) ? 1 : 0
    end
  end
end

def include_bulk_options?(node)

Parameters:
  • node (RuboCop::AST::SendNode) -- (send nil? :change_table ...)
def include_bulk_options?(node)
  # arguments: [{(sym :table)(str "table")} (hash (pair (sym :bulk) _))]
  options = node.arguments[1]
  return false unless options
  options.hash_type? && options.keys.any? { |key| key.sym_type? && key.value == :bulk }
end

def on_def(node)

def on_def(node)
  return unless support_bulk_alter?
  return unless MIGRATION_METHODS.include?(node.method_name)
  return unless node.body
  recorder = AlterMethodsRecorder.new
  node.body.child_nodes.each do |child_node|
    if call_to_combinable_alter_method? child_node
      recorder.process(child_node)
    else
      recorder.flush
    end
  end
  recorder.offensive_nodes.each { |n| add_offense_for_alter_methods(n) }
end

def on_send(node)

def on_send(node)
  return unless support_bulk_alter?
  return unless node.command?(:change_table)
  return if include_bulk_options?(node)
  return unless (body = node.block_node&.body)
  send_nodes = send_nodes_from_change_table_block(body)
  add_offense_for_change_table(node) if count_transformations(send_nodes) > 1
end

def send_nodes_from_change_table_block(body)

def send_nodes_from_change_table_block(body)
  if body.send_type?
    [body]
  else
    body.each_child_node(:send).to_a
  end
end

def support_bulk_alter?

def support_bulk_alter?
  case database
  when MYSQL
    true
  when POSTGRESQL
    # Add bulk alter support for PostgreSQL in 5.2.0
    # See: https://github.com/rails/rails/pull/31331
    target_rails_version >= 5.2
  else
    false
  end
end