rails_best_practices 0.5.6 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +161 -0
- data/lib/rails_best_practices.rb +158 -20
- data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +108 -34
- data/lib/rails_best_practices/checks/always_add_db_index_check.rb +148 -29
- data/lib/rails_best_practices/checks/check.rb +178 -75
- data/lib/rails_best_practices/checks/dry_bundler_in_capistrano_check.rb +26 -5
- data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +66 -15
- data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +53 -12
- data/lib/rails_best_practices/checks/law_of_demeter_check.rb +59 -30
- data/lib/rails_best_practices/checks/move_code_into_controller_check.rb +35 -15
- data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +56 -12
- data/lib/rails_best_practices/checks/move_code_into_model_check.rb +30 -32
- data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +45 -15
- data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +31 -27
- data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +99 -38
- data/lib/rails_best_practices/checks/not_use_default_route_check.rb +43 -12
- data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +140 -28
- data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +44 -30
- data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +18 -7
- data/lib/rails_best_practices/checks/use_before_filter_check.rb +88 -18
- data/lib/rails_best_practices/checks/use_model_association_check.rb +61 -22
- data/lib/rails_best_practices/checks/use_observer_check.rb +125 -23
- data/lib/rails_best_practices/checks/use_query_attribute_check.rb +75 -47
- data/lib/rails_best_practices/checks/use_say_with_time_in_migrations_check.rb +59 -10
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +78 -23
- data/lib/rails_best_practices/command.rb +19 -34
- data/lib/rails_best_practices/core.rb +4 -2
- data/lib/rails_best_practices/core/checking_visitor.rb +49 -19
- data/lib/rails_best_practices/core/error.rb +5 -2
- data/lib/rails_best_practices/core/runner.rb +79 -55
- data/lib/rails_best_practices/core/visitable_sexp.rb +325 -55
- data/lib/rails_best_practices/{core/core_ext.rb → core_ext/enumerable.rb} +3 -6
- data/lib/rails_best_practices/core_ext/nil_class.rb +8 -0
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.yml +2 -2
- metadata +8 -7
- data/README.textile +0 -150
@@ -3,52 +3,164 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check config/routes.rb to make sure there are no
|
6
|
+
# Check config/routes.rb file to make sure there are no overuse route customizations.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/10-overuse-route-customizations.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# the check methods are different for rails2 and rails3 syntax.
|
17
|
+
#
|
18
|
+
# for rails2
|
19
|
+
#
|
20
|
+
# check all call nodes in route file.
|
21
|
+
# if the message of call node is resources,
|
22
|
+
# and the second argument of call node is a hash,
|
23
|
+
# and the count of the pair (key/value) in hash is greater than @customize_count,
|
24
|
+
# then these custom routes are overuse.
|
25
|
+
#
|
26
|
+
# for rails3
|
27
|
+
#
|
28
|
+
# check all iter nodes in route file.
|
29
|
+
# if the subject of iter node is with message resources,
|
30
|
+
# and in the block body of iter node, there are more than @customize_count call nodes,
|
31
|
+
# whose message is :get, :post, :update or :delete,
|
32
|
+
# then these custom routes are overuse.
|
9
33
|
class OveruseRouteCustomizationsCheck < Check
|
10
|
-
|
11
|
-
|
34
|
+
|
35
|
+
VERBS = [:get, :post, :update, :delete]
|
36
|
+
|
37
|
+
def interesting_review_nodes
|
12
38
|
[:call, :iter]
|
13
39
|
end
|
14
|
-
|
15
|
-
def
|
16
|
-
|
40
|
+
|
41
|
+
def interesting_review_files
|
42
|
+
ROUTE_FILE
|
17
43
|
end
|
18
44
|
|
19
45
|
def initialize(options = {})
|
20
46
|
super()
|
21
47
|
@customize_count = options['customize_count'] || 3
|
22
48
|
end
|
23
|
-
|
24
|
-
|
25
|
-
|
49
|
+
|
50
|
+
# check call node to see if the count of member and collection custom routes is more than @customize_count defined in review process.
|
51
|
+
# this is for rails2 syntax.
|
52
|
+
#
|
53
|
+
# if the message of call node is :resources,
|
54
|
+
# and the second argument of call node is a hash,
|
55
|
+
# and the count of the pair (key/value) in hash is greater than @customize_count, like
|
56
|
+
#
|
57
|
+
# map.resources :posts, :member => { :create_comment => :post,
|
58
|
+
# :update_comment => :update,
|
59
|
+
# :delete_comment => :delete },
|
60
|
+
# :collection => { :comments => :get }
|
61
|
+
#
|
62
|
+
# then they are overuse route customizations.
|
63
|
+
def review_start_call(node)
|
64
|
+
if member_and_collection_count_for_rails2(node) > @customize_count
|
65
|
+
add_error "overuse route customizations (customize_count > #{@customize_count})", node.file, node.subject.line
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# check iter node to see if the count of member and collection custom routes is more than @customize_count defined in review process.
|
70
|
+
# this is for rails3 syntax.
|
71
|
+
#
|
72
|
+
# if the subject of iter node is with message :resources,
|
73
|
+
# and in the block body of iter node, there are more than @customize_count call nodes,
|
74
|
+
# whose message is :get, :post, :update or :delete, like
|
75
|
+
#
|
76
|
+
# resources :posts do
|
77
|
+
# member do
|
78
|
+
# post :create_comment
|
79
|
+
# update :update_comment
|
80
|
+
# delete :delete_comment
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# collection do
|
84
|
+
# get :comments
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# then they are overuse route customizations.
|
89
|
+
def review_start_iter(node)
|
90
|
+
if member_and_collection_count_for_rails3(node) > @customize_count
|
91
|
+
add_error "overuse route customizations (customize_count > #{@customize_count})", node.file, node.subject.line
|
92
|
+
end
|
26
93
|
end
|
27
94
|
|
28
95
|
private
|
29
|
-
|
96
|
+
# check call node to calculate the count of member and collection custom routes.
|
97
|
+
# this is for rails2 syntax.
|
98
|
+
#
|
99
|
+
# if the message of call node is :resources,
|
100
|
+
# and the second argument is a hash,
|
101
|
+
# then calculate the pair (key/value) count,
|
102
|
+
# it is just the count of member and collection custom routes.
|
103
|
+
#
|
104
|
+
# s(:call, s(:lvar, :map), :resources,
|
105
|
+
# s(:arglist,
|
106
|
+
# s(:lit, :posts),
|
107
|
+
# s(:hash,
|
108
|
+
# s(:lit, :member),
|
109
|
+
# s(:hash,
|
110
|
+
# s(:lit, :create_comment),
|
111
|
+
# s(:lit, :post),
|
112
|
+
# s(:lit, :update_comment),
|
113
|
+
# s(:lit, :update),
|
114
|
+
# s(:lit, :delete_comment),
|
115
|
+
# s(:lit, :delete)
|
116
|
+
# ),
|
117
|
+
# s(:lit, :collection),
|
118
|
+
# s(:hash,
|
119
|
+
# s(:lit, :comments),
|
120
|
+
# s(:lit, :get)
|
121
|
+
# )
|
122
|
+
# )
|
123
|
+
# )
|
124
|
+
# )
|
125
|
+
def member_and_collection_count_for_rails2(node)
|
30
126
|
if :resources == node.message
|
31
|
-
|
32
|
-
|
33
|
-
|
127
|
+
hash_node = node.arguments[2]
|
128
|
+
if hash_node
|
129
|
+
(hash_node.grep_nodes_count(:node_type => :lit) - hash_node.grep_nodes_count(:node_type => :hash)) / 2
|
130
|
+
end
|
34
131
|
end
|
35
132
|
end
|
36
133
|
|
37
|
-
#
|
134
|
+
# check iter node to calculate the count of member and collection custom routes.
|
135
|
+
# this is for rails3 syntax.
|
136
|
+
#
|
137
|
+
# if its subject is with message :resources,
|
138
|
+
# then calculate the count of call nodes, whose message is :get, :post, :update or :delete,
|
139
|
+
# it is just the count of member and collection custom routes.
|
140
|
+
#
|
141
|
+
# s(:iter,
|
142
|
+
# s(:call, nil, :resources, s(:arglist, s(:lit, :posts))),
|
143
|
+
# nil,
|
144
|
+
# s(:block,
|
145
|
+
# s(:iter,
|
146
|
+
# s(:call, nil, :member, s(:arglist)),
|
147
|
+
# nil,
|
148
|
+
# s(:block,
|
149
|
+
# s(:call, nil, :post, s(:arglist, s(:lit, :create_comment))),
|
150
|
+
# s(:call, nil, :post, s(:arglist, s(:lit, :update_comment))),
|
151
|
+
# s(:call, nil, :post, s(:arglist, s(:lit, :delete_comment)))
|
152
|
+
# )
|
153
|
+
# ),
|
154
|
+
# s(:iter,
|
155
|
+
# s(:call, nil, :collection, s(:arglist)),
|
156
|
+
# nil,
|
157
|
+
# s(:call, nil, :get, s(:arglist, s(:lit, :comments)))
|
158
|
+
# )
|
159
|
+
# )
|
160
|
+
# )
|
38
161
|
def member_and_collection_count_for_rails3(node)
|
39
|
-
|
40
|
-
|
41
|
-
get_nodes.size + post_nodes.size
|
42
|
-
end
|
43
|
-
|
44
|
-
# this is the checker for rails2 style routes
|
45
|
-
def member_and_collection_count_for_rails2(node)
|
46
|
-
hash_nodes = node.grep_nodes(:node_type => :hash)
|
47
|
-
return 0 if hash_nodes.empty?
|
48
|
-
hash_key_node = hash_nodes.first[1]
|
49
|
-
if :lit == hash_key_node.node_type and [:member, :collection].include? hash_key_node[1]
|
50
|
-
customize_hash = eval(hash_nodes.first.to_s)
|
51
|
-
(customize_hash[:member].size || 0) + (customize_hash[:collection].size || 0)
|
162
|
+
if :resources == node.subject.message
|
163
|
+
node.grep_nodes_count(:node_type => :call, :message => VERBS)
|
52
164
|
end
|
53
165
|
end
|
54
166
|
end
|
@@ -3,54 +3,68 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check a controller file to make sure that complex model creation should not exist in controller,
|
6
|
+
# Check a controller file to make sure that complex model creation should not exist in controller, should be replaced with factory method.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/6-replace-complex-creation-with-factory-method.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all method defines in the controller files,
|
17
|
+
# if there are multiple attribute assignments apply to one subject,
|
18
|
+
# and the subject is a local variable or an instance variable,
|
19
|
+
# and after them there is a call node with message :save or :save!,
|
20
|
+
# then these attribute assignments are complex creation, should be replaced with factory method.
|
10
21
|
class ReplaceComplexCreationWithFactoryMethodCheck < Check
|
11
|
-
|
12
|
-
def
|
22
|
+
|
23
|
+
def interesting_review_nodes
|
13
24
|
[:defn]
|
14
25
|
end
|
15
|
-
|
16
|
-
def
|
26
|
+
|
27
|
+
def interesting_review_files
|
17
28
|
CONTROLLER_FILES
|
18
29
|
end
|
19
|
-
|
30
|
+
|
20
31
|
def initialize(options = {})
|
21
32
|
super()
|
22
33
|
@attrasgn_count = options['attribute_assignment_count'] || 2
|
23
34
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
35
|
+
|
36
|
+
# check method define node to see if there are multiple attribute assignments, more than @attrasgn_count, on one local variable or instance variable before save in review process.
|
37
|
+
#
|
38
|
+
# it wll check every attrasgn nodes in method define node,
|
39
|
+
# if there are multiple attrasgn nodes who have the same subject,
|
40
|
+
# and the subject is a local variable or an instance variable,
|
41
|
+
# and after them, there is a call node with message :save or :save!,
|
42
|
+
# then these attribute assignments are complex creation, should be replaced with factory method.
|
43
|
+
def review_start_defn(node)
|
44
|
+
node.recursive_children do |child_node|
|
45
|
+
case child_node.node_type
|
29
46
|
when :attrasgn
|
30
|
-
|
47
|
+
remember_variable_use_count(child_node)
|
31
48
|
when :call
|
32
|
-
|
49
|
+
check_variable_save(child_node)
|
33
50
|
else
|
34
51
|
end
|
35
52
|
end
|
36
|
-
|
53
|
+
reset_variable_use_count
|
37
54
|
end
|
38
|
-
|
55
|
+
|
39
56
|
private
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
variable = node.subject
|
51
|
-
add_error "replace complex creation with factory method (#{variable} attribute_assignment_count > #{@attrasgn_count})" if @variables[variable] > @attrasgn_count
|
57
|
+
# check the call node to see if it is with message :save or :save!,
|
58
|
+
# and the count attribute assignment on the subject of the call node is greater than @attrasgn_count defined,
|
59
|
+
# then it is a complex creation, should be replaced with factory method.
|
60
|
+
def check_variable_save(node)
|
61
|
+
if [:save, :save!].include? node.message
|
62
|
+
variable = node.subject
|
63
|
+
if variable_use_count[variable] > @attrasgn_count
|
64
|
+
add_error "replace complex creation with factory method (#{variable} attribute_assignment_count > #{@attrasgn_count})"
|
65
|
+
end
|
66
|
+
end
|
52
67
|
end
|
53
|
-
end
|
54
68
|
end
|
55
69
|
end
|
56
70
|
end
|
@@ -5,18 +5,29 @@ module RailsBestPractices
|
|
5
5
|
module Checks
|
6
6
|
# Check a partail view file to make sure there is no instance variable.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/27-replace-instance-variable-with-local-variable.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all instance variable in partial view files,
|
17
|
+
# if exist, then they should be replaced with local variable
|
9
18
|
class ReplaceInstanceVariableWithLocalVariableCheck < Check
|
10
|
-
|
11
|
-
def
|
19
|
+
|
20
|
+
def interesting_review_nodes
|
12
21
|
[:ivar]
|
13
22
|
end
|
14
|
-
|
15
|
-
def
|
23
|
+
|
24
|
+
def interesting_review_files
|
16
25
|
PARTIAL_VIEW_FILES
|
17
26
|
end
|
18
|
-
|
19
|
-
|
27
|
+
|
28
|
+
# check ivar node in partial view file,
|
29
|
+
# it is an instance variable, and should be replaced with local variable.
|
30
|
+
def review_start_ivar(node)
|
20
31
|
add_error "replace instance variable with local variable"
|
21
32
|
end
|
22
33
|
end
|
@@ -3,39 +3,109 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check a controller file to make sure to use before_filter to remove
|
6
|
+
# Check a controller file to make sure to use before_filter to remove duplicated first code line in different action.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice detailed here http://rails-bestpractices.com/posts/22-use-before_filter.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all first code line in method definitions (actions),
|
17
|
+
# if they are duplicated, then they should be moved to before_filter.
|
9
18
|
class UseBeforeFilterCheck < Check
|
10
19
|
|
11
|
-
def
|
20
|
+
def interesting_review_nodes
|
12
21
|
[:class]
|
13
22
|
end
|
14
23
|
|
15
|
-
def
|
24
|
+
def interesting_review_files
|
16
25
|
CONTROLLER_FILES
|
17
26
|
end
|
18
27
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
28
|
+
# check class define node to see if there are method define nodes whose first code line are duplicated in review process.
|
29
|
+
#
|
30
|
+
# it will every defn nodes in the class node,
|
31
|
+
# if there are defn nodes who have the same first code line, like
|
32
|
+
#
|
33
|
+
# s(:class, :PostsController, s(:const, :ApplicationController),
|
34
|
+
# s(:scope,
|
35
|
+
# s(:block,
|
36
|
+
# s(:defn, :show, s(:args),
|
37
|
+
# s(:scope,
|
38
|
+
# s(:block,
|
39
|
+
# s(:iasgn, :@post,
|
40
|
+
# s(:call,
|
41
|
+
# s(:call, s(:call, nil, :current_user, s(:arglist)), :posts, s(:arglist)),
|
42
|
+
# :find,
|
43
|
+
# s(:arglist,
|
44
|
+
# s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :id)))
|
45
|
+
# )
|
46
|
+
# )
|
47
|
+
# )
|
48
|
+
# )
|
49
|
+
# )
|
50
|
+
# ),
|
51
|
+
# s(:defn, :edit, s(:args),
|
52
|
+
# s(:scope,
|
53
|
+
# s(:block,
|
54
|
+
# s(:iasgn, :@post,
|
55
|
+
# s(:call,
|
56
|
+
# s(:call, s(:call, nil, :current_user, s(:arglist)), :posts, s(:arglist)),
|
57
|
+
# :find,
|
58
|
+
# s(:arglist,
|
59
|
+
# s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :id)))
|
60
|
+
# )
|
61
|
+
# )
|
62
|
+
# )
|
63
|
+
# )
|
64
|
+
# )
|
65
|
+
# )
|
66
|
+
# )
|
67
|
+
# )
|
68
|
+
# )
|
69
|
+
#
|
70
|
+
# then these duplicated first code lines should be moved to before_filter.
|
71
|
+
def review_start_class(class_node)
|
72
|
+
@first_sentences = {}
|
73
|
+
class_node.grep_nodes({:node_type => :defn}) { |defn_node| remember_first_sentence(defn_node) }
|
74
|
+
@first_sentences.each do |first_sentence, defn_nodes|
|
75
|
+
if defn_nodes.size > 1
|
76
|
+
add_error "use before_filter for #{defn_nodes.collect(&:method_name).join(',')}", class_node.file, defn_nodes.collect(&:line).join(',')
|
26
77
|
end
|
27
78
|
end
|
28
79
|
end
|
29
80
|
|
30
81
|
private
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
82
|
+
# check method define node, and remember the first sentence.
|
83
|
+
# first sentence may be :iasgn, :lasgn, :attrasgn, :call node, like
|
84
|
+
#
|
85
|
+
# s(:defn, :show, s(:args),
|
86
|
+
# s(:scope,
|
87
|
+
# s(:block,
|
88
|
+
# s(:iasgn, :@post,
|
89
|
+
# s(:call,
|
90
|
+
# s(:call, s(:call, nil, :current_user, s(:arglist)), :posts, s(:arglist)),
|
91
|
+
# :find,
|
92
|
+
# s(:arglist,
|
93
|
+
# s(:call, s(:call, nil, :params, s(:arglist)), :[], s(:arglist, s(:lit, :id)))
|
94
|
+
# )
|
95
|
+
# )
|
96
|
+
# )
|
97
|
+
# )
|
98
|
+
# )
|
99
|
+
# )
|
100
|
+
#
|
101
|
+
# the first sentence of defn node is :iasgn node.
|
102
|
+
def remember_first_sentence(defn_node)
|
103
|
+
first_sentence = defn_node.body[1]
|
104
|
+
unless first_sentence == s(:nil)
|
105
|
+
@first_sentences[first_sentence] ||= []
|
106
|
+
@first_sentences[first_sentence] << defn_node
|
107
|
+
end
|
37
108
|
end
|
38
|
-
end
|
39
109
|
end
|
40
110
|
end
|
41
111
|
end
|