rails_best_practices 0.3.0 → 0.4.0
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 +72 -54
- data/Rakefile +5 -5
- data/VERSION +1 -1
- data/identifier +0 -0
- data/lib/rails_best_practices/checks/always_add_db_index_check.rb +27 -62
- data/lib/rails_best_practices/checks/check.rb +12 -13
- data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +7 -20
- data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +1 -1
- data/lib/rails_best_practices/checks/law_of_demeter_check.rb +6 -5
- data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +8 -3
- data/lib/rails_best_practices/checks/move_code_into_model_check.rb +53 -0
- data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +36 -4
- data/lib/rails_best_practices/checks/not_use_default_route_check.rb +3 -2
- data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +23 -10
- data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +1 -1
- data/lib/rails_best_practices/checks/{use_filter_check.rb → use_before_filter_check.rb} +9 -7
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +1 -1
- data/lib/rails_best_practices/checks.rb +2 -4
- data/lib/rails_best_practices/command.rb +7 -41
- data/lib/rails_best_practices/core/checking_visitor.rb +2 -2
- data/lib/rails_best_practices/core/runner.rb +8 -6
- data/lib/rails_best_practices/core/visitable_sexp.rb +8 -2
- data/lib/rails_best_practices.rb +41 -1
- data/rails_best_practices.gemspec +25 -30
- data/rails_best_practices.yml +3 -5
- data/spec/rails_best_practices/checks/always_add_db_index_check_spec.rb +32 -112
- data/spec/rails_best_practices/checks/isolate_seed_data_check_spec.rb +0 -3
- data/spec/rails_best_practices/checks/keep_finders_on_their_own_model_check_spec.rb +33 -1
- data/spec/rails_best_practices/checks/law_of_demeter_check_spec.rb +66 -27
- data/spec/rails_best_practices/checks/move_code_into_helper_check_spec.rb +2 -2
- data/spec/rails_best_practices/checks/move_code_into_model_check_spec.rb +55 -0
- data/spec/rails_best_practices/checks/needless_deep_nesting_check_spec.rb +55 -21
- data/spec/rails_best_practices/checks/not_use_default_route_check_spec.rb +51 -22
- data/spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb +134 -48
- data/spec/rails_best_practices/checks/replace_instance_variable_with_local_variable_check_spec.rb +11 -1
- data/spec/rails_best_practices/checks/{use_filter_check_spec.rb → use_before_filter_check_spec.rb} +6 -6
- data/spec/rails_best_practices/checks/use_scope_access_check_spec.rb +22 -0
- metadata +49 -39
- data/lib/rails_best_practices/checks/many_to_many_collection_check.rb +0 -12
- data/lib/rails_best_practices/checks/nested_model_forms_check.rb +0 -12
- data/lib/rails_best_practices/checks/use_model_callback_check.rb +0 -12
- data/spec/rails_best_practices/checks/many_to_many_collection_check_spec.rb +0 -11
- data/spec/rails_best_practices/checks/nested_model_forms_check_spec.rb +0 -11
- data/spec/rails_best_practices/checks/use_model_callback_check_spec.rb +0 -11
data/README.textile
CHANGED
|
@@ -1,28 +1,27 @@
|
|
|
1
1
|
h1. rails_best_practices
|
|
2
2
|
|
|
3
|
-
rails_best_practices is a
|
|
4
|
-
rails_best_practices is a code static parser tool.
|
|
3
|
+
rails_best_practices is a code metric tool to check the quality of rails codes.
|
|
5
4
|
|
|
6
5
|
*************************************************
|
|
7
6
|
|
|
8
7
|
h2. Resources
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Homepage: "http://rails-bestpractices.com":http://rails-bestpractices.com
|
|
10
|
+
Wiki: "http://github.com/flyerhzm/rails_best_practices/wiki":http://github.com/flyerhzm/rails_best_practices/wiki
|
|
12
11
|
|
|
13
12
|
*************************************************
|
|
14
13
|
|
|
15
14
|
h2. Install
|
|
16
15
|
|
|
17
16
|
<pre><code>
|
|
18
|
-
sudo gem install rails_best_practices
|
|
17
|
+
sudo gem install rails_best_practices
|
|
19
18
|
</code></pre>
|
|
20
19
|
|
|
21
20
|
*************************************************
|
|
22
21
|
|
|
23
22
|
h2. Usage
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
At the root directory of rails app
|
|
26
25
|
|
|
27
26
|
<pre><code>
|
|
28
27
|
rails_best_practices .
|
|
@@ -30,6 +29,31 @@ rails_best_practices .
|
|
|
30
29
|
|
|
31
30
|
notice the period at the end, it can be the relative or absolute path of your rails app.
|
|
32
31
|
|
|
32
|
+
And default rails_best_practices will do parse codes in vendor, spec, test and stories directories. If you need, see the command options:
|
|
33
|
+
|
|
34
|
+
<pre><code>
|
|
35
|
+
$ rails_best_practices -h
|
|
36
|
+
Usage: rails_best_practices [options]
|
|
37
|
+
-d, --debug Debug mode
|
|
38
|
+
--vendor include vendor files
|
|
39
|
+
--spec include spec files
|
|
40
|
+
--test include test files
|
|
41
|
+
--stories include stories files
|
|
42
|
+
-h, --help Show this message
|
|
43
|
+
</code></pre>
|
|
44
|
+
|
|
45
|
+
*************************************************
|
|
46
|
+
|
|
47
|
+
h2. Issue
|
|
48
|
+
|
|
49
|
+
If you got NoMethodError or any syntax error, you should use debug mode to detect which file rails_best_practices is parsing and getting the error.
|
|
50
|
+
|
|
51
|
+
<pre><code>
|
|
52
|
+
rails_best_practices -d .
|
|
53
|
+
</code></pre>
|
|
54
|
+
|
|
55
|
+
Then give me the error stack and the source code of the file that rails_best_practices is parsing error.
|
|
56
|
+
|
|
33
57
|
*************************************************
|
|
34
58
|
|
|
35
59
|
h2. Customize Configuration
|
|
@@ -42,11 +66,8 @@ MoveFinderToNamedScopeCheck: { }
|
|
|
42
66
|
UseModelAssociationCheck: { }
|
|
43
67
|
UseScopeAccessCheck: { }
|
|
44
68
|
AddModelVirtualAttributeCheck: { }
|
|
45
|
-
# UseModelCallbackCheck: { }
|
|
46
69
|
ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
|
|
47
70
|
MoveModelLogicIntoModelCheck: { called_count: 4 }
|
|
48
|
-
# ManyToManyCollectionCheck: { }
|
|
49
|
-
# NestedModelFormsCheck: { }
|
|
50
71
|
OveruseRouteCustomizationsCheck: { customize_count: 3 }
|
|
51
72
|
NeedlessDeepNestingCheck: { nested_count: 2 }
|
|
52
73
|
NotUseDefaultRouteCheck: { }
|
|
@@ -55,58 +76,55 @@ LawOfDemeterCheck: { }
|
|
|
55
76
|
UseObserverCheck: { }
|
|
56
77
|
IsolateSeedDataCheck: { }
|
|
57
78
|
AlwaysAddDbIndexCheck: { }
|
|
58
|
-
|
|
79
|
+
UseBeforeFilterCheck: { }
|
|
59
80
|
MoveCodeIntoControllerCheck: { }
|
|
60
|
-
|
|
81
|
+
MoveCodeIntoModelCheck: { }
|
|
82
|
+
MoveCodeIntoHelperCheck: { array_count: 3 }
|
|
61
83
|
ReplaceInstanceVariableWithLocalVariableCheck: { }
|
|
62
84
|
</code></pre>
|
|
63
85
|
|
|
64
86
|
*************************************************
|
|
65
87
|
|
|
66
|
-
h2.
|
|
67
|
-
|
|
68
|
-
*
|
|
69
|
-
##
|
|
70
|
-
##
|
|
71
|
-
##
|
|
72
|
-
##
|
|
73
|
-
##
|
|
74
|
-
##
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
##
|
|
78
|
-
##
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
##
|
|
83
|
-
##
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
##
|
|
88
|
-
##
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
##
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
## [-Isolating Seed Data-]
|
|
96
|
-
## [-Always add DB index-]
|
|
97
|
-
|
|
98
|
-
* Lesson 5. Controller
|
|
99
|
-
## [-Use before_filter-]
|
|
100
|
-
## DRY Controller
|
|
101
|
-
|
|
102
|
-
* Lesson 6. View
|
|
103
|
-
## [-Move code into controller-]
|
|
88
|
+
h2. Implementation
|
|
89
|
+
|
|
90
|
+
* Move code from Controller to Model
|
|
91
|
+
## Move finder to named_scope (rails2 only)
|
|
92
|
+
## Use model association
|
|
93
|
+
## Use scope access
|
|
94
|
+
## Add model virtual attribute
|
|
95
|
+
## Replace Complex Creation with Factory Method
|
|
96
|
+
## Move Model Logic into the Model
|
|
97
|
+
|
|
98
|
+
* RESTful Conventions
|
|
99
|
+
## Overuse route customizations
|
|
100
|
+
## Needless deep nesting
|
|
101
|
+
## Not use default route
|
|
102
|
+
|
|
103
|
+
* Model
|
|
104
|
+
## Keep Finders on Their Own Model (rails2 only)
|
|
105
|
+
## the Law of Demeter
|
|
106
|
+
## Use Observer
|
|
107
|
+
|
|
108
|
+
* Migration
|
|
109
|
+
## Isolating Seed Data
|
|
110
|
+
## Always add DB index
|
|
111
|
+
|
|
112
|
+
* Controller
|
|
113
|
+
## Use before_filter
|
|
114
|
+
|
|
115
|
+
* View
|
|
116
|
+
## Move code into controller
|
|
104
117
|
## Move code into model
|
|
105
|
-
##
|
|
106
|
-
##
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
## Move code into helper
|
|
119
|
+
## Replace instance variable with local variable
|
|
120
|
+
|
|
121
|
+
*************************************************
|
|
122
|
+
|
|
123
|
+
h2. Links
|
|
124
|
+
|
|
125
|
+
"http://railsenvy.com/2009/12/07/rails-envy-podcast-episode-102":http://railsenvy.com/2009/12/07/rails-envy-podcast-episode-102
|
|
126
|
+
"http://ruby5.envylabs.com/episodes/29-episode-28-november-17-2009":http://ruby5.envylabs.com/episodes/29-episode-28-november-17-2009
|
|
109
127
|
|
|
110
128
|
*************************************************
|
|
111
129
|
|
|
112
|
-
Copyright ©
|
|
130
|
+
Copyright © 2010 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
data/Rakefile
CHANGED
|
@@ -6,10 +6,10 @@ require 'jeweler'
|
|
|
6
6
|
desc 'Default: run unit tests.'
|
|
7
7
|
task :default => :spec
|
|
8
8
|
|
|
9
|
-
desc 'Generate documentation for the
|
|
9
|
+
desc 'Generate documentation for the rails_best_practices plugin.'
|
|
10
10
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
11
11
|
rdoc.rdoc_dir = 'rdoc'
|
|
12
|
-
rdoc.title = '
|
|
12
|
+
rdoc.title = 'rails_best_practices'
|
|
13
13
|
rdoc.options << '--line-numbers' << '--inline-source'
|
|
14
14
|
rdoc.rdoc_files.include('README')
|
|
15
15
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
@@ -22,10 +22,10 @@ end
|
|
|
22
22
|
|
|
23
23
|
Jeweler::Tasks.new do |gemspec|
|
|
24
24
|
gemspec.name = "rails_best_practices"
|
|
25
|
-
gemspec.summary = "
|
|
26
|
-
gemspec.description = "
|
|
25
|
+
gemspec.summary = "a code metric tool for rails codes."
|
|
26
|
+
gemspec.description = "a code metric tool for rails codes."
|
|
27
27
|
gemspec.email = "flyerhzm@gmail.com"
|
|
28
|
-
gemspec.homepage = "http://
|
|
28
|
+
gemspec.homepage = "http://rails-bestpractices.com"
|
|
29
29
|
gemspec.authors = ["Richard Huang"]
|
|
30
30
|
gemspec.add_dependency 'ruby_parser', '>= 2.0.4'
|
|
31
31
|
gemspec.add_dependency 'ruby2ruby', '>= 1.2.4'
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.4.0
|
data/identifier
ADDED
|
File without changes
|
|
@@ -2,89 +2,54 @@ require 'rails_best_practices/checks/check'
|
|
|
2
2
|
|
|
3
3
|
module RailsBestPractices
|
|
4
4
|
module Checks
|
|
5
|
-
# Check
|
|
5
|
+
# Check db/schema.rb file to make sure every reference key has a database index.
|
|
6
6
|
#
|
|
7
|
-
# Implementation:
|
|
8
|
-
# Parse migration files twice.
|
|
9
|
-
# First, remember all reference keys and index keys.
|
|
10
|
-
# Second, compare reference keys and index keys, and add error when reference keys are not in index keys.
|
|
7
|
+
# Implementation: read all add_index method calls to get the indexed columns in table, then read integer method call in create_table block to get the reference columns in tables, compare with indexed columns, if not in the indexed columns, then it violates always_add_db_index_check.
|
|
11
8
|
class AlwaysAddDbIndexCheck < Check
|
|
12
9
|
|
|
13
10
|
def interesting_nodes
|
|
14
|
-
[:
|
|
11
|
+
[:block, :call]
|
|
15
12
|
end
|
|
16
13
|
|
|
17
14
|
def interesting_files
|
|
18
|
-
|
|
15
|
+
/db\/schema.rb/
|
|
19
16
|
end
|
|
20
17
|
|
|
21
18
|
def initialize
|
|
22
19
|
super
|
|
23
|
-
@
|
|
24
|
-
@references = {}
|
|
25
|
-
@indexes = {}
|
|
26
|
-
@parse = false
|
|
20
|
+
@index_columns = []
|
|
27
21
|
end
|
|
28
22
|
|
|
29
23
|
def evaluate_start(node)
|
|
30
|
-
if
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
add_error "always add db index (#{hint})" unless differences.empty?
|
|
24
|
+
if :block == node.node_type
|
|
25
|
+
find_index_columns(node)
|
|
26
|
+
elsif :call == node.node_type
|
|
27
|
+
case node.message
|
|
28
|
+
when :create_table
|
|
29
|
+
@table_name = node.arguments[1].to_ruby_string
|
|
30
|
+
when :integer
|
|
31
|
+
column_name = node.arguments[1].to_ruby_string
|
|
32
|
+
if column_name =~ /_id$/ and !indexed?(@table_name, column_name)
|
|
33
|
+
add_error "always add db index (#@table_name => #{column_name})", node.file, node.line
|
|
34
|
+
end
|
|
42
35
|
end
|
|
43
|
-
else
|
|
44
|
-
remember_references(node.body)
|
|
45
|
-
remember_indexes(node.body)
|
|
46
36
|
end
|
|
47
37
|
end
|
|
48
|
-
|
|
38
|
+
|
|
49
39
|
private
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
node.grep_nodes({:node_type => :call, :message => :integer}).each do |integer_node|
|
|
56
|
-
column_name = integer_node.arguments[1].to_ruby_string
|
|
57
|
-
if column_name =~ /_id$/
|
|
58
|
-
@references[table_name] ||= []
|
|
59
|
-
@references[table_name] << column_name
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
node.grep_nodes({:node_type => :call, :message => :references}).each do |references_node|
|
|
63
|
-
column_name = references_node.arguments[1].to_ruby_string + "_id"
|
|
64
|
-
@references[table_name] ||= []
|
|
65
|
-
@references[table_name] << column_name
|
|
66
|
-
end
|
|
67
|
-
node.grep_nodes({:node_type => :call, :message => :column}).each do |column_node|
|
|
68
|
-
if 'integer' == column_node.arguments[2].to_ruby_string
|
|
69
|
-
column_name = column_node.arguments[1].to_ruby_string
|
|
70
|
-
if column_name =~ /_id$/
|
|
71
|
-
@references[table_name] ||= []
|
|
72
|
-
@references[table_name] << column_name
|
|
73
|
-
end
|
|
74
|
-
end
|
|
40
|
+
def find_index_columns(node)
|
|
41
|
+
node.grep_nodes({:node_type => :call, :message => :add_index}).each do |index_node|
|
|
42
|
+
table_name = index_node.arguments[1].to_ruby_string
|
|
43
|
+
reference_column = eval(index_node.arguments[2].to_ruby)
|
|
44
|
+
@index_columns << [table_name, reference_column]
|
|
75
45
|
end
|
|
76
46
|
end
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
table_name = add_index_node.arguments[1].to_ruby_string
|
|
83
|
-
column_name = add_index_node.arguments[2].to_ruby_string
|
|
84
|
-
@indexes[table_name] ||= []
|
|
85
|
-
@indexes[table_name] << column_name
|
|
47
|
+
|
|
48
|
+
def indexed?(table_name, column_name)
|
|
49
|
+
!!@index_columns.find do |reference|
|
|
50
|
+
reference[0] == table_name and reference[1].class == String ? reference[1] == column_name : reference[1].include?(column_name)
|
|
51
|
+
end
|
|
86
52
|
end
|
|
87
|
-
end
|
|
88
53
|
end
|
|
89
54
|
end
|
|
90
55
|
end
|
|
@@ -3,13 +3,15 @@ require 'rails_best_practices/core/error'
|
|
|
3
3
|
module RailsBestPractices
|
|
4
4
|
module Checks
|
|
5
5
|
class Check
|
|
6
|
-
NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar]
|
|
6
|
+
NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar, :block, :iter]
|
|
7
7
|
|
|
8
|
-
CONTROLLER_FILES = /_controller
|
|
9
|
-
MIGRATION_FILES = /db\/migrate
|
|
10
|
-
MODLE_FILES = /models
|
|
11
|
-
VIEW_FILES = /views
|
|
12
|
-
PARTIAL_VIEW_FILES = /views\/.*\/_
|
|
8
|
+
CONTROLLER_FILES = /_controller\.rb$/
|
|
9
|
+
MIGRATION_FILES = /db\/migrate\/.*\.rb/
|
|
10
|
+
MODLE_FILES = /models\/.*\.rb/
|
|
11
|
+
VIEW_FILES = /views\/.*\.(erb|haml)/
|
|
12
|
+
PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)/
|
|
13
|
+
|
|
14
|
+
attr_reader :errors
|
|
13
15
|
|
|
14
16
|
def initialize
|
|
15
17
|
@errors = []
|
|
@@ -52,13 +54,10 @@ module RailsBestPractices
|
|
|
52
54
|
evaluate_end(node)
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
def add_error(error, line = nil)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def errors
|
|
61
|
-
@errors
|
|
57
|
+
def add_error(error, file = nil, line = nil)
|
|
58
|
+
file ||= @node.file
|
|
59
|
+
line ||= @node.line
|
|
60
|
+
@errors << RailsBestPractices::Core::Error.new("#{file}", "#{line}", error)
|
|
62
61
|
end
|
|
63
62
|
end
|
|
64
63
|
end
|
|
@@ -8,7 +8,7 @@ module RailsBestPractices
|
|
|
8
8
|
class IsolateSeedDataCheck < Check
|
|
9
9
|
|
|
10
10
|
def interesting_nodes
|
|
11
|
-
[:
|
|
11
|
+
[:call, :lasgn]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def interesting_files
|
|
@@ -18,28 +18,15 @@ module RailsBestPractices
|
|
|
18
18
|
def initialize
|
|
19
19
|
super
|
|
20
20
|
@new_variables = []
|
|
21
|
-
@files = []
|
|
22
|
-
@parse = false
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
def evaluate_start(node)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
if @parse
|
|
36
|
-
if [:create, :create!].include? node.message
|
|
37
|
-
add_error("isolate seed data")
|
|
38
|
-
elsif :lasgn == node.node_type
|
|
39
|
-
remember_new_variable(node)
|
|
40
|
-
elsif [:save, :save!].include? node.message
|
|
41
|
-
add_error("isolate seed data") if new_record?(node)
|
|
42
|
-
end
|
|
24
|
+
if [:create, :create!].include? node.message
|
|
25
|
+
add_error("isolate seed data")
|
|
26
|
+
elsif :lasgn == node.node_type
|
|
27
|
+
remember_new_variable(node)
|
|
28
|
+
elsif [:save, :save!].include? node.message
|
|
29
|
+
add_error("isolate seed data") if new_record?(node)
|
|
43
30
|
end
|
|
44
31
|
end
|
|
45
32
|
|
|
@@ -22,7 +22,7 @@ module RailsBestPractices
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
24
|
def others_finder?(node)
|
|
25
|
-
node.message
|
|
25
|
+
[:find, :all, :first, :last].include? node.message and node.subject.node_type == :call and node.arguments.size > 1
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -5,8 +5,8 @@ module RailsBestPractices
|
|
|
5
5
|
# Check to make sure not avoid the law of demeter.
|
|
6
6
|
#
|
|
7
7
|
# Implementation:
|
|
8
|
-
# 1. check all models to record belongs_to associations
|
|
9
|
-
# 2. check if calling belongs_to association's method or attribute
|
|
8
|
+
# 1. check all models to record belongs_to and has_one associations
|
|
9
|
+
# 2. check if calling belongs_to and has_one association's method or attribute
|
|
10
10
|
class LawOfDemeterCheck < Check
|
|
11
11
|
|
|
12
12
|
def interesting_nodes
|
|
@@ -20,7 +20,7 @@ module RailsBestPractices
|
|
|
20
20
|
|
|
21
21
|
def evaluate_start(node)
|
|
22
22
|
if node.node_type == :class
|
|
23
|
-
|
|
23
|
+
remember_association(node)
|
|
24
24
|
elsif [:lvar, :ivar].include?(node.subject.node_type) and node.subject != s(:lvar, :_erbout)
|
|
25
25
|
add_error "law of demeter" if need_delegate?(node)
|
|
26
26
|
end
|
|
@@ -28,8 +28,9 @@ module RailsBestPractices
|
|
|
28
28
|
|
|
29
29
|
private
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
# remember belongs_to or has_one node
|
|
32
|
+
def remember_association(node)
|
|
33
|
+
(node.body.grep_nodes(:message => :belongs_to) + node.body.grep_nodes(:message => :has_one)).collect do |body_node|
|
|
33
34
|
class_name = node.subject.to_s.underscore
|
|
34
35
|
@associations[class_name] ||= []
|
|
35
36
|
@associations[class_name] << body_node.arguments[1].to_ruby_string
|
|
@@ -4,7 +4,7 @@ module RailsBestPractices
|
|
|
4
4
|
module Checks
|
|
5
5
|
# Check a view file to make sure there is no complex options_for_select message call.
|
|
6
6
|
#
|
|
7
|
-
# Implementation: Check if
|
|
7
|
+
# Implementation: Check if first argument of options_for_select is an array and contains more than two nodes, then it should be moved into helper.
|
|
8
8
|
class MoveCodeIntoHelperCheck < Check
|
|
9
9
|
|
|
10
10
|
def interesting_nodes
|
|
@@ -15,14 +15,19 @@ module RailsBestPractices
|
|
|
15
15
|
VIEW_FILES
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def initialize(options = {})
|
|
19
|
+
super()
|
|
20
|
+
@array_count = options['array_count'] || 3
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
def evaluate_start(node)
|
|
19
|
-
add_error "move code into helper" if complex_select_options?(node)
|
|
24
|
+
add_error "move code into helper (array_count >= #{@array_count})" if complex_select_options?(node)
|
|
20
25
|
end
|
|
21
26
|
|
|
22
27
|
private
|
|
23
28
|
|
|
24
29
|
def complex_select_options?(node)
|
|
25
|
-
:options_for_select == node.message and node.arguments.
|
|
30
|
+
:options_for_select == node.message and :array == node.arguments[1].node_type and node.arguments[1].size > @array_count
|
|
26
31
|
end
|
|
27
32
|
end
|
|
28
33
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a view file to make sure there is no complex logic call for model.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: Check if a local variable or instance variable called greater than 2 times in if or unless conditional statement, then it should more code into model.
|
|
8
|
+
class MoveCodeIntoModelCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:if, :unless]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
VIEW_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def evaluate_start(node)
|
|
19
|
+
@variables = {}
|
|
20
|
+
node.conditional_statement.grep_nodes(:node_type => :call).each { |call_node| remember_call(call_node) }
|
|
21
|
+
check_errors
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def check_errors
|
|
27
|
+
@variables.each do |node, count|
|
|
28
|
+
add_error "move code into model (#{node.to_ruby})" if count > 2
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def remember_call(call_node)
|
|
33
|
+
variable_node = variable(call_node)
|
|
34
|
+
if variable_node
|
|
35
|
+
@variables[variable_node] ||= 0
|
|
36
|
+
@variables[variable_node] += 1
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def variable(call_node)
|
|
41
|
+
while call_node.subject.node_type == :call
|
|
42
|
+
call_node = call_node.subject
|
|
43
|
+
end
|
|
44
|
+
subject_node = call_node.subject
|
|
45
|
+
if [:ivar, :lvar].include?(subject_node.node_type) and subject_node[1] != :_erbout
|
|
46
|
+
subject_node
|
|
47
|
+
else
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -8,7 +8,7 @@ module RailsBestPractices
|
|
|
8
8
|
class NeedlessDeepNestingCheck < Check
|
|
9
9
|
|
|
10
10
|
def interesting_nodes
|
|
11
|
-
[:call]
|
|
11
|
+
[:call, :iter]
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def interesting_files
|
|
@@ -17,11 +17,28 @@ module RailsBestPractices
|
|
|
17
17
|
|
|
18
18
|
def initialize(options = {})
|
|
19
19
|
super()
|
|
20
|
+
@counter = 0
|
|
20
21
|
@nested_count = options['nested_count'] || 2
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
def evaluate_start(node)
|
|
24
|
-
|
|
25
|
+
check_nested_count(node)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def check_nested_count(node)
|
|
30
|
+
if :iter == node.node_type
|
|
31
|
+
check_for_rails3(node)
|
|
32
|
+
elsif :resources == node.message and node.subject
|
|
33
|
+
check_for_rails2(node)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def check_for_rails3(node)
|
|
38
|
+
nested_count_for_rails3(node)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def check_for_rails2(node)
|
|
25
42
|
if node.subject == s(:call, nil, :map, s(:arglist))
|
|
26
43
|
@counter = 0
|
|
27
44
|
else
|
|
@@ -29,7 +46,22 @@ module RailsBestPractices
|
|
|
29
46
|
add_error "needless deep nesting (nested_count > #{@nested_count})" if @counter >= @nested_count
|
|
30
47
|
end
|
|
31
48
|
end
|
|
32
|
-
|
|
49
|
+
|
|
50
|
+
def nested_count_for_rails3(node)
|
|
51
|
+
if :iter == node.node_type and :resources == node.subject.message and !node.message
|
|
52
|
+
@counter += 1
|
|
53
|
+
nested_count_for_rails3(node[3])
|
|
54
|
+
@counter -= 1
|
|
55
|
+
elsif :block == node.node_type
|
|
56
|
+
node.children.each do |child_node|
|
|
57
|
+
if :resources == child_node.message and nil == child_node.subject and @counter + 1 > @nested_count
|
|
58
|
+
add_error "needless deep nesting (nested_count > #{@nested_count})", child_node.file, child_node.line
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
elsif :call == node.node_type and :resources == node.message
|
|
62
|
+
add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter + 1 > @nested_count
|
|
63
|
+
end
|
|
64
|
+
end
|
|
33
65
|
end
|
|
34
66
|
end
|
|
35
|
-
end
|
|
67
|
+
end
|
|
@@ -17,10 +17,11 @@ module RailsBestPractices
|
|
|
17
17
|
|
|
18
18
|
def evaluate_start(node)
|
|
19
19
|
if node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id"))) or
|
|
20
|
-
node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format")))
|
|
20
|
+
node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format"))) or
|
|
21
|
+
node == s(:call, nil, :match, s(:arglist, s(:str, ":controller(/:action(/:id(.:format)))")))
|
|
21
22
|
add_error "not use default route"
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
|
-
end
|
|
27
|
+
end
|