rails_best_practices 0.5.6 → 0.6.1
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.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
|