class RuboCop::Cop::Rails::BulkChangeTable

This Cop checks whether alter queries are combinable. If combinable queries are detected, it suggests to you to use `change_table` with `bulk: true` instead. This option causes the migration to generate a single ALTER TABLE statement combining multiple column alterations.

The `bulk` option is only supported on the MySQL and the PostgreSQL (5.2 later) adapter; thus it will automatically detect an adapter from `development` environment in `config/database.yml` when the `Database` option is not set. If the adapter is not `mysql2` or `postgresql`, this Cop ignores offenses.

@example

# bad
def change
  add_column :users, :name, :string, null: false
  add_column :users, :nickname, :string

  # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL
  # ALTER TABLE `users` ADD `nickname` varchar(255)
end

# good
def change
  change_table :users, bulk: true do |t|
    t.string :name, null: false
    t.string :nickname
  end

  # ALTER TABLE `users` ADD `name` varchar(255) NOT NULL,
  #                     ADD `nickname` varchar(255)
end

@example

# bad
def change
  change_table :users do |t|
    t.string :name, null: false
    t.string :nickname
  end
end

# good
def change
  change_table :users, bulk: true do |t|
    t.string :name, null: false
    t.string :nickname
  end
end

# good
# When you don't want to combine alter queries.
def change
  change_table :users, bulk: false do |t|
    t.string :name, null: false
    t.string :nickname
  end
end

@see api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-change_table @see api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html

Constants

COMBINABLE_ALTER_METHODS
COMBINABLE_TRANSFORMATIONS
MIGRATION_METHODS
MSG_FOR_ALTER_METHODS
MSG_FOR_CHANGE_TABLE
MYSQL
MYSQL_COMBINABLE_ALTER_METHODS
MYSQL_COMBINABLE_TRANSFORMATIONS
POSTGRESQL
POSTGRESQL_COMBINABLE_ALTER_METHODS
POSTGRESQL_COMBINABLE_TRANSFORMATIONS

Public Instance Methods

on_def(node) click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 134
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
on_send(node) click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 152
def on_send(node)
  return unless support_bulk_alter?
  return unless node.command?(:change_table)
  return if include_bulk_options?(node)
  return unless node.block_node

  send_nodes = node.block_node.body.each_child_node(:send).to_a

  transformations = send_nodes.select do |send_node|
    combinable_transformations.include?(send_node.method_name)
  end

  add_offense_for_change_table(node) if transformations.size > 1
end

Private Instance Methods

add_offense_for_alter_methods(node) click to toggle source

@param node [RuboCop::AST::SendNode]

# File lib/rubocop/cop/rails/bulk_change_table.rb, line 249
def add_offense_for_alter_methods(node)
  # arguments: [{(sym :table)(str "table")} ...]
  table_node = node.arguments[0]
  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
add_offense_for_change_table(node) click to toggle source

@param node [RuboCop::AST::SendNode]

# File lib/rubocop/cop/rails/bulk_change_table.rb, line 259
def add_offense_for_change_table(node)
  add_offense(node, message: MSG_FOR_CHANGE_TABLE)
end
call_to_combinable_alter_method?(child_node) click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 225
def call_to_combinable_alter_method?(child_node)
  child_node.send_type? &&
    combinable_alter_methods.include?(child_node.method_name)
end
combinable_alter_methods() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 230
def combinable_alter_methods
  case database
  when MYSQL
    COMBINABLE_ALTER_METHODS + MYSQL_COMBINABLE_ALTER_METHODS
  when POSTGRESQL
    COMBINABLE_ALTER_METHODS + POSTGRESQL_COMBINABLE_ALTER_METHODS
  end
end
combinable_transformations() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 239
def combinable_transformations
  case database
  when MYSQL
    COMBINABLE_TRANSFORMATIONS + MYSQL_COMBINABLE_TRANSFORMATIONS
  when POSTGRESQL
    COMBINABLE_TRANSFORMATIONS + POSTGRESQL_COMBINABLE_TRANSFORMATIONS
  end
end
database() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 179
def database
  cop_config['Database'] || database_from_yaml
end
database_from_yaml() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 183
def database_from_yaml
  return nil unless database_yaml

  case database_yaml['adapter']
  when 'mysql2'
    MYSQL
  when 'postgresql'
    POSTGRESQL
  end
end
database_yaml() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 194
def database_yaml
  return nil unless File.exist?('config/database.yml')

  yaml = if YAML.respond_to?(:unsafe_load_file)
           YAML.unsafe_load_file('config/database.yml')
         else
           YAML.load_file('config/database.yml')
         end
  return nil unless yaml.is_a? Hash

  config = yaml['development']
  return nil unless config.is_a?(Hash)

  config
rescue Psych::SyntaxError
  nil
end
include_bulk_options?(node) click to toggle source

@param node [RuboCop::AST::SendNode] (send nil? :change_table …)

# File lib/rubocop/cop/rails/bulk_change_table.rb, line 170
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
support_bulk_alter?() click to toggle source
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 212
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