class RailsBestPractices::Reviews::AlwaysAddDbIndexReview
Review
db/schema.rb file to make sure every reference key has a database index.
See the best practice details here rails-bestpractices.com/posts/2010/07/24/always-add-db-index/
Implementation:
Review
process:
only check the command and command_calls nodes and at the end of review process, if the receiver of command node is "create_table", then remember the table names if the receiver of command_call node is "integer" or "string" or "bigint" and suffix with _id, then remember it as foreign key if the receiver of command_call node is "string", the name of it is _type suffixed and there is an integer or string column _id suffixed, then remember it as polymorphic foreign key if the receiver of command_call node is remembered as foreign key and it have argument non-false "index", then remember the index columns if the receiver of command node is "add_index", then remember the index columns after all of these, at the end of review process ActiveRecord::Schema.define(version: 20101201111111) do ...... end if there are any foreign keys not existed in index columns, then the foreign keys should add db index.
Public Class Methods
new(options = {})
click to toggle source
Calls superclass method
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 31 def initialize(options = {}) super(options) @index_columns = {} @foreign_keys = {} @table_nodes = {} end
Private Instance Methods
combine_polymorphic_foreign_keys()
click to toggle source
combine polymorphic foreign keys, e.g.
[tagger_id], [tagger_type] => [tagger_id, tagger_type]
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 170 def combine_polymorphic_foreign_keys @index_columns.each do |_table, foreign_keys| foreign_id_keys = foreign_keys.select { |key| key.size == 1 && key.first =~ /_id/ } foreign_type_keys = foreign_keys.select { |key| key.size == 1 && key.first =~ /_type/ } foreign_id_keys.each do |id_key| next unless type_key = foreign_type_keys.detect { |type_key| type_key.first == id_key.first.sub(/_id/, '') + '_type' } foreign_keys.delete(id_key) foreign_keys.delete(type_key) foreign_keys << id_key + type_key end end end
greater_than_or_equal(more_array, less_array)
click to toggle source
check if more_array is greater than less_array or equal to less_array.
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 192 def greater_than_or_equal(more_array, less_array) more_size = more_array.size less_size = less_array.size (more_array - less_array).size == more_size - less_size end
not_indexed?(table, column)
click to toggle source
check if the table's column is indexed.
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 186 def not_indexed?(table, column) index_columns = @index_columns[table] !index_columns || index_columns.none? { |e| greater_than_or_equal(Array(e), Array(column)) } end
remember_foreign_key_columns(node)
click to toggle source
remember foreign key columns
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 118 def remember_foreign_key_columns(node) table_name = @table_name foreign_key_column = node.arguments.all.first.to_s @foreign_keys[table_name] ||= [] if foreign_key_column =~ /(.*?)_id$/ @foreign_keys[table_name] << if @foreign_keys[table_name].delete("#{Regexp.last_match(1)}_type") ["#{Regexp.last_match(1)}_id", "#{Regexp.last_match(1)}_type"] else foreign_key_column end foreign_id_column = foreign_key_column elsif foreign_key_column =~ /(.*?)_type$/ @foreign_keys[table_name] << if @foreign_keys[table_name].delete("#{Regexp.last_match(1)}_id") ["#{Regexp.last_match(1)}_id", "#{Regexp.last_match(1)}_type"] else foreign_key_column end foreign_id_column = "#{Regexp.last_match(1)}_id" end if foreign_id_column index_node = node.arguments.all.last.hash_value('index') if index_node.present? && (index_node.to_s != 'false') @index_columns[table_name] ||= [] @index_columns[table_name] << foreign_id_column end end end
remember_index_columns_inside_table(node)
click to toggle source
remember the node as index columns, when used inside a table block, i.e.
t.index [:column_name, ...]
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 103 def remember_index_columns_inside_table(node) table_name = @table_name index_column = node.arguments.all.first.to_object @index_columns[table_name] ||= [] @index_columns[table_name] << index_column end
remember_index_columns_outside_table(node)
click to toggle source
remember the node as index columns, when used outside a table block, i.e.
add_index :table_name, :column_name
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 92 def remember_index_columns_outside_table(node) table_name = node.arguments.all.first.to_s index_column = node.arguments.all[1].to_object @index_columns[table_name] ||= [] @index_columns[table_name] << index_column end
remember_table_nodes(node)
click to toggle source
remember table nodes
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 112 def remember_table_nodes(node) @table_name = node.arguments.all.first.to_s @table_nodes[@table_name] = node end
remove_only_type_foreign_keys()
click to toggle source
remove the non foreign keys with only _type column.
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 162 def remove_only_type_foreign_keys @foreign_keys.each do |_table, foreign_keys| foreign_keys.delete_if { |key| key.is_a?(String) && key =~ /_type$/ } end end
remove_table_not_exist_foreign_keys()
click to toggle source
remove the non foreign keys without corresponding tables.
# File lib/rails_best_practices/reviews/always_add_db_index_review.rb, line 150 def remove_table_not_exist_foreign_keys @foreign_keys.each do |table, foreign_keys| foreign_keys.delete_if do |key| if key.is_a?(String) && key =~ /_id$/ class_name = Prepares.model_associations.get_association_class_name(table, key[0..-4]) class_name ? !@table_nodes[class_name.gsub('::', '').tableize] : !@table_nodes[key[0..-4].pluralize] end end end end