rails_best_practices 0.2.4 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
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