Class: RailsBestPractices::Reviews::AlwaysAddDbIndexReview

Inherits:
Review show all
Defined in:
lib/rails_best_practices/reviews/always_add_db_index_review.rb

Overview

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/21-always-add-db-index

Implementation:

Review process:

only check the call nodes and at the end of iter node in db/schema file,
if the subject of call node is :create_table, then remember the table names
if the subject of call node is :integer, then remember it as foreign key
if the sujbect of call node is :string, the name of it is _type suffixed and there is an integer column _id suffixed, then remember it as polymorphic foreign key
if the subject of call node is :add_index, then remember the index columns
after all of these, at the end of iter node

    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.

Constant Summary

Constants inherited from Core::Check

Core::Check::CONTROLLER_FILES, Core::Check::HELPER_FILES, Core::Check::MAILER_FILES, Core::Check::MIGRATION_FILES, Core::Check::MODEL_FILES, Core::Check::NODE_TYPES, Core::Check::PARTIAL_VIEW_FILES, Core::Check::ROUTE_FILE, Core::Check::SCHEMA_FILE, Core::Check::VIEW_FILES

Instance Attribute Summary

Attributes inherited from Core::Check

#errors

Instance Method Summary collapse

Methods inherited from Review

#equal?, #model_associations, #model_attributes, #models, #remember_variable_use_count, #reset_variable_use_count, #variable, #variable_use_count

Methods inherited from Core::Check

#add_error, #method_missing, #node_end, #node_start

Constructor Details

#initializeAlwaysAddDbIndexReview

Returns a new instance of AlwaysAddDbIndexReview.



39
40
41
42
43
44
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 39

def initialize
  super
  @index_columns = {}
  @foreign_keys = {}
  @table_nodes = {}
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class RailsBestPractices::Core::Check

Instance Method Details

#end_iter(node) ⇒ Object

check at the end of iter node, like

s(:iter,
  s(:call,
    s(:colon2, s(:const, :ActiveRecord), :Schema),
    :define,
    s(:arglist, s(:hash, s(:lit, :version), s(:lit, 20100603080629)))
  ),
  nil,
  s(:iter,
    s(:call, nil, :create_table,
      s(:arglist, s(:str, "comments"), s(:hash, s(:lit, :force), s(:true)))
    ),
    s(:lasgn, :t),
    s(:block,
      s(:call, s(:lvar, :t), :string, s(:arglist, s(:str, "content")))
    )
  )
)

if the subject of iter node is with subject ActiveRecord::Schema, it means we have completed the foreign keys and index columns parsing, then we compare foreign keys and index columns.

if there are any foreign keys not existed in index columns, then we should add db index for that foreign keys.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 113

def end_iter(node)
  first_node = node.subject
  if :call == first_node.node_type && s(:colon2, s(:const, :ActiveRecord), :Schema) == first_node.subject
    remove_only_type_foreign_keys
    @foreign_keys.each do |table, foreign_key|
      table_node = @table_nodes[table]
      foreign_key.each do |column|
        if indexed?(table, column)
          add_error "always add db index (#{table} => [#{Array(column).join(', ')}])", table_node.file, table_node.line
        end
      end
    end
  end
end

#interesting_filesObject



35
36
37
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 35

def interesting_files
  SCHEMA_FILE
end

#interesting_nodesObject



31
32
33
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 31

def interesting_nodes
  [:call, :iter]
end

#start_call(node) ⇒ Object

check call node.

if the message of call node is :create_table, then remember the table name (@table_nodes) like

{
  "comments" =>
    s(:call, nil, :create_table, s(:arglist, s(:str, "comments"), s(:hash, s(:lit, :force), s(:true))))
}

if the message of call node is :integer, then remember it as a foreign key of last create table name.

if the message of call node is :type and the name of argument is _type suffixed, then remember it with _id suffixed column as polymorphic foreign key.

the remember foreign keys (@foreign_keys) like

{
  "taggings" =>
    ["tag_id", ["taggable_id", "taggable_type"]]
}

if the message of call node is :add_index, then remember it as index columns (@index_columns) like

{
  "comments" =>
    ["post_id", "user_id"]
}


75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 75

def start_call(node)
  case node.message
  when :create_table
    remember_table_nodes(node)
  when :integer, :string
    remember_foreign_key_columns(node)
  when :add_index
    remember_index_columns(node)
  else
  end
end

#urlObject



27
28
29
# File 'lib/rails_best_practices/reviews/always_add_db_index_review.rb', line 27

def url
  "http://rails-bestpractices.com/posts/21-always-add-db-index"
end