rails_best_practices 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.textile CHANGED
@@ -45,6 +45,7 @@ KeepFindersOnTheirOwnModelCheck: { }
45
45
  LawOfDemeterCheck: { }
46
46
  UseObserverCheck: { }
47
47
  IsolateSeedDataCheck: { }
48
+ AlwaysAddDbIndexCheck: { }
48
49
  </code></pre>
49
50
 
50
51
  *************************************************
@@ -79,7 +80,7 @@ h2. Progress
79
80
 
80
81
  * Lesson 4. Migration
81
82
  ## [-Isolating Seed Data-]
82
- ## Always add DB index
83
+ ## [-Always add DB index-]
83
84
 
84
85
  * Lesson 5. Controller
85
86
  ## Use before_filter
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.2.5
@@ -14,3 +14,4 @@ require 'rails_best_practices/checks/keep_finders_on_their_own_model_check'
14
14
  require 'rails_best_practices/checks/law_of_demeter_check'
15
15
  require 'rails_best_practices/checks/use_observer_check'
16
16
  require 'rails_best_practices/checks/isolate_seed_data_check'
17
+ require 'rails_best_practices/checks/always_add_db_index_check'
@@ -0,0 +1,83 @@
1
+ require 'rails_best_practices/checks/check'
2
+
3
+ module RailsBestPractices
4
+ module Checks
5
+ class AlwaysAddDbIndexCheck < Check
6
+
7
+ def interesting_nodes
8
+ [:defs]
9
+ end
10
+
11
+ def interesting_files
12
+ /db\/migrate\/.*rb/
13
+ end
14
+
15
+ def initialize
16
+ super
17
+ @files = []
18
+ @references = {}
19
+ @indexes = {}
20
+ @parse = false
21
+ end
22
+
23
+ def evaluate_start(node)
24
+ if @files.include? node.file
25
+ @parse = true if :up == node.message
26
+ else
27
+ @files << node.file
28
+ end
29
+
30
+ if @parse and :up == node.message
31
+ @references.each do |table_name, column_names|
32
+ differences = column_names - (@indexes[table_name] || [])
33
+ hint = differences.collect {|column_name| "#{table_name} => #{column_name}"}.join(', ')
34
+ add_error "always add db index (#{hint})" unless differences.empty?
35
+ end
36
+ else
37
+ remember_references(node.body)
38
+ remember_indexes(node.body)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def remember_references(node)
45
+ create_table_node = node.grep_nodes({:node_type => :call, :message => :create_table}).first
46
+ if create_table_node
47
+ table_name = eval(create_table_node.arguments[1].to_ruby).to_s
48
+ node.grep_nodes({:node_type => :call, :message => :integer}).each do |integer_node|
49
+ column_name = eval(integer_node.arguments[1].to_ruby).to_s
50
+ if column_name =~ /_id$/
51
+ @references[table_name] ||= []
52
+ @references[table_name] << column_name
53
+ end
54
+ end
55
+ node.grep_nodes({:node_type => :call, :message => :references}).each do |references_node|
56
+ column_name = eval(references_node.arguments[1].to_ruby).to_s + "_id"
57
+ @references[table_name] ||= []
58
+ @references[table_name] << column_name
59
+ end
60
+ node.grep_nodes({:node_type => :call, :message => :column}).each do |column_node|
61
+ if 'integer' == eval(column_node.arguments[2].to_ruby).to_s
62
+ column_name = eval(column_node.arguments[1].to_ruby).to_s
63
+ if column_name =~ /_id$/
64
+ @references[table_name] ||= []
65
+ @references[table_name] << column_name
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def remember_indexes(node)
73
+ add_index_nodes = node.grep_nodes({:node_type => :call, :message => :add_index})
74
+ add_index_nodes.each do |add_index_node|
75
+ table_name = eval(add_index_node.arguments[1].to_ruby).to_s
76
+ column_name = eval(add_index_node.arguments[2].to_ruby).to_s
77
+ @indexes[table_name] ||= []
78
+ @indexes[table_name] << column_name
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -3,7 +3,7 @@ require 'rails_best_practices/core/error'
3
3
  module RailsBestPractices
4
4
  module Checks
5
5
  class Check
6
- NODE_TYPES = [:call, :defn, :if, :unless, :class, :lasgn]
6
+ NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn]
7
7
 
8
8
  def initialize
9
9
  @errors = []
@@ -29,7 +29,7 @@ module RailsBestPractices
29
29
  private
30
30
 
31
31
  def remember_belongs_to(node)
32
- node.class_body.grep_nodes(:message => :belongs_to).collect do |body_node|
32
+ node.body.grep_nodes(:message => :belongs_to).collect do |body_node|
33
33
  class_name = node.subject.to_s.underscore
34
34
  @associations[class_name] ||= []
35
35
  @associations[class_name] << eval(body_node.arguments[1].to_ruby).to_s
@@ -26,7 +26,7 @@ module RailsBestPractices
26
26
 
27
27
  condition_node.message == :== and
28
28
  (current_user?(condition_node.arguments.call) or current_user?(condition_node.subject)) and
29
- (node.false_node.method_body.any? {|n| n.message == :redirect_to} or node.true_node.method_body.any? {|n| n.message == :redirect_to})
29
+ (node.false_node.body.any? {|n| n.message == :redirect_to} or node.true_node.method_body.any? {|n| n.message == :redirect_to})
30
30
  end
31
31
 
32
32
  def current_user?(call_node)
@@ -35,4 +35,4 @@ module RailsBestPractices
35
35
 
36
36
  end
37
37
  end
38
- end
38
+ end
@@ -9,7 +9,22 @@ def expand_dirs_to_files *dirs
9
9
  else
10
10
  p
11
11
  end
12
- }.flatten.sort
12
+ }.flatten.sort { |a, b|
13
+ # for law_of_demeter_check
14
+ if a =~ /models\/.*rb/
15
+ -1
16
+ elsif b =~ /models\/.*rb/
17
+ 1
18
+ else
19
+ a <=> b
20
+ end
21
+ }
22
+ end
23
+
24
+ # for always_add_db_index_check
25
+ def add_duplicate_migration_files files
26
+ migration_files = files.select { |file| file.index("db/migrate") }
27
+ (files << migration_files).flatten
13
28
  end
14
29
 
15
30
  options = {}
@@ -25,7 +40,7 @@ OptionParser.new do |opts|
25
40
  end
26
41
 
27
42
  runner = RailsBestPractices::Core::Runner.new
28
- expand_dirs_to_files(ARGV).each.each { |file| runner.check_file(file) }
43
+ add_duplicate_migration_files(expand_dirs_to_files(ARGV)).each { |file| runner.check_file(file) }
29
44
  runner.errors.each {|error| puts error}
30
45
  puts "\nFound #{runner.errors.size} errors."
31
46
 
@@ -53,7 +53,7 @@ class Sexp
53
53
  end
54
54
 
55
55
  def message
56
- if [:attrasgn, :call].include? node_type
56
+ if [:attrasgn, :call, :defs].include? node_type
57
57
  self[2]
58
58
  end
59
59
  end
@@ -88,15 +88,13 @@ class Sexp
88
88
  end
89
89
  end
90
90
 
91
- def method_body
91
+ def body
92
92
  if :block == node_type
93
93
  self[1..-1]
94
- end
95
- end
96
-
97
- def class_body
98
- if :class == node_type
94
+ elsif :class == node_type
99
95
  self[3]
96
+ elsif :defs == node_type
97
+ self[4]
100
98
  end
101
99
  end
102
100
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rails_best_practices}
8
- s.version = "0.2.4"
8
+ s.version = "0.2.5"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Richard Huang"]
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "lib/rails_best_practices.rb",
29
29
  "lib/rails_best_practices/checks.rb",
30
30
  "lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb",
31
+ "lib/rails_best_practices/checks/always_add_db_index_check.rb",
31
32
  "lib/rails_best_practices/checks/check.rb",
32
33
  "lib/rails_best_practices/checks/isolate_seed_data_check.rb",
33
34
  "lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb",
@@ -54,6 +55,7 @@ Gem::Specification.new do |s|
54
55
  "rails_best_practices.gemspec",
55
56
  "rails_best_practices.yml",
56
57
  "spec/rails_best_practices/checks/add_model_virtual_attribute_check_spec.rb",
58
+ "spec/rails_best_practices/checks/always_add_db_index_check_spec.rb",
57
59
  "spec/rails_best_practices/checks/isolate_seed_data_check_spec.rb",
58
60
  "spec/rails_best_practices/checks/keep_finders_on_their_own_model_check_spec.rb",
59
61
  "spec/rails_best_practices/checks/law_of_demeter_check_spec.rb",
@@ -94,6 +96,7 @@ Gem::Specification.new do |s|
94
96
  "spec/rails_best_practices/checks/move_finder_to_named_scope_check_spec.rb",
95
97
  "spec/rails_best_practices/checks/needless_deep_nesting_check_spec.rb",
96
98
  "spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb",
99
+ "spec/rails_best_practices/checks/always_add_db_index_check_spec.rb",
97
100
  "spec/spec_helper.rb"
98
101
  ]
99
102
 
@@ -14,3 +14,4 @@ KeepFindersOnTheirOwnModelCheck: { }
14
14
  LawOfDemeterCheck: { }
15
15
  UseObserverCheck: { }
16
16
  IsolateSeedDataCheck: { }
17
+ AlwaysAddDbIndexCheck: { }
@@ -0,0 +1,153 @@
1
+ require File.join(File.dirname(__FILE__) + '/../../spec_helper')
2
+
3
+ describe RailsBestPractices::Checks::AlwaysAddDbIndexCheck do
4
+ before(:each) do
5
+ @runner = RailsBestPractices::Core::Runner.new(RailsBestPractices::Checks::AlwaysAddDbIndexCheck.new)
6
+ end
7
+
8
+ it "should always add db index" do
9
+ content = <<-EOF
10
+ class CreateComments < ActiveRecord::Migration
11
+ def self.up
12
+ create_table "comments", :force => true do |t|
13
+ t.string :content
14
+ t.integer :post_id
15
+ t.integer :user_id
16
+ end
17
+ end
18
+
19
+ def self.down
20
+ drop_table "comments"
21
+ end
22
+ end
23
+ EOF
24
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
25
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
26
+ errors = @runner.errors
27
+ errors.should_not be_empty
28
+ errors[0].to_s.should == "db/migrate/20090918130258_create_comments.rb:2 - always add db index (comments => post_id, comments => user_id)"
29
+ end
30
+
31
+ it "should always add db index with column has no id" do
32
+ content = <<-EOF
33
+ class CreateComments < ActiveRecord::Migration
34
+ def self.up
35
+ create_table "comments", :force => true do |t|
36
+ t.string :content
37
+ t.integer :position
38
+ end
39
+ end
40
+
41
+ def self.down
42
+ drop_table "comments"
43
+ end
44
+ end
45
+ EOF
46
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
47
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
48
+ errors = @runner.errors
49
+ errors.should be_empty
50
+ end
51
+
52
+ it "should always add db index with references" do
53
+ content = <<-EOF
54
+ class CreateComments < ActiveRecord::Migration
55
+ def self.up
56
+ create_table "comments", :force => true do |t|
57
+ t.string :content
58
+ t.references :post
59
+ t.references :user
60
+ end
61
+ end
62
+
63
+ def self.down
64
+ drop_table "comments"
65
+ end
66
+ end
67
+ EOF
68
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
69
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
70
+ errors = @runner.errors
71
+ errors.should_not be_empty
72
+ errors[0].to_s.should == "db/migrate/20090918130258_create_comments.rb:2 - always add db index (comments => post_id, comments => user_id)"
73
+ end
74
+
75
+ it "should always add db index with column" do
76
+ content = <<-EOF
77
+ class CreateComments < ActiveRecord::Migration
78
+ def self.up
79
+ create_table "comments", :force => true do |t|
80
+ t.string :content
81
+ t.column :post_id, :integer
82
+ t.column :user_id, :integer
83
+ end
84
+ end
85
+
86
+ def self.down
87
+ drop_table "comments"
88
+ end
89
+ end
90
+ EOF
91
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
92
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
93
+ errors = @runner.errors
94
+ errors.should_not be_empty
95
+ errors[0].to_s.should == "db/migrate/20090918130258_create_comments.rb:2 - always add db index (comments => post_id, comments => user_id)"
96
+ end
97
+
98
+ it "should not always add db index with add_index" do
99
+ content = <<-EOF
100
+ class CreateComments < ActiveRecord::Migration
101
+ def self.up
102
+ create_table "comments", :force => true do |t|
103
+ t.string :content
104
+ t.integer :post_id
105
+ t.integer :user_id
106
+ end
107
+
108
+ add_index :comments, :post_id
109
+ add_index :comments, :user_id
110
+ end
111
+
112
+ def self.down
113
+ drop_table "comments"
114
+ end
115
+ end
116
+ EOF
117
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
118
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
119
+ errors = @runner.errors
120
+ errors.should be_empty
121
+ end
122
+
123
+ it "should not always add db index with add_index in another migration file" do
124
+ content = <<-EOF
125
+ class CreateComments < ActiveRecord::Migration
126
+ def self.up
127
+ create_table "comments", :force => true do |t|
128
+ t.string :content
129
+ t.integer :post_id
130
+ t.integer :user_id
131
+ end
132
+ end
133
+
134
+ def self.down
135
+ drop_table "comments"
136
+ end
137
+ end
138
+ EOF
139
+ add_index_content = <<-EOF
140
+ class AddIndexesToComments < ActiveRecord::Migration
141
+ def self.up
142
+ add_index :comments, :post_id
143
+ add_index :comments, :user_id
144
+ end
145
+ end
146
+ EOF
147
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
148
+ @runner.check('db/migrate/20090919130258_add_indexes_to_comments.rb', content)
149
+ @runner.check('db/migrate/20090918130258_create_comments.rb', content)
150
+ @runner.check('db/migrate/20090919130258_add_indexes_to_comments.rb', content)
151
+ errors = @runner.errors
152
+ end
153
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_best_practices
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
@@ -51,6 +51,7 @@ files:
51
51
  - lib/rails_best_practices.rb
52
52
  - lib/rails_best_practices/checks.rb
53
53
  - lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb
54
+ - lib/rails_best_practices/checks/always_add_db_index_check.rb
54
55
  - lib/rails_best_practices/checks/check.rb
55
56
  - lib/rails_best_practices/checks/isolate_seed_data_check.rb
56
57
  - lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb
@@ -77,6 +78,7 @@ files:
77
78
  - rails_best_practices.gemspec
78
79
  - rails_best_practices.yml
79
80
  - spec/rails_best_practices/checks/add_model_virtual_attribute_check_spec.rb
81
+ - spec/rails_best_practices/checks/always_add_db_index_check_spec.rb
80
82
  - spec/rails_best_practices/checks/isolate_seed_data_check_spec.rb
81
83
  - spec/rails_best_practices/checks/keep_finders_on_their_own_model_check_spec.rb
82
84
  - spec/rails_best_practices/checks/law_of_demeter_check_spec.rb
@@ -139,4 +141,5 @@ test_files:
139
141
  - spec/rails_best_practices/checks/move_finder_to_named_scope_check_spec.rb
140
142
  - spec/rails_best_practices/checks/needless_deep_nesting_check_spec.rb
141
143
  - spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb
144
+ - spec/rails_best_practices/checks/always_add_db_index_check_spec.rb
142
145
  - spec/spec_helper.rb