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
# 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
# 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
@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
@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
# 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
# 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
# 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
# File lib/rubocop/cop/rails/bulk_change_table.rb, line 179 def database cop_config['Database'] || database_from_yaml end
# 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
# 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
@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
# 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