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
@@ -5,49 +5,47 @@ module RailsBestPractices
|
|
5
5
|
module Checks
|
6
6
|
# Check a view file to make sure there is no complex logic call for model.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/25-move-code-into-model.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check if there are multiple method calls or attribute assignments apply to one subject,
|
17
|
+
# and the subject is a local variable or instance variable,
|
18
|
+
# then they should be moved into model.
|
9
19
|
class MoveCodeIntoModelCheck < Check
|
10
20
|
|
11
|
-
def
|
12
|
-
[:if
|
21
|
+
def interesting_review_nodes
|
22
|
+
[:if]
|
13
23
|
end
|
14
24
|
|
15
|
-
def
|
25
|
+
def interesting_review_files
|
16
26
|
VIEW_FILES
|
17
27
|
end
|
18
28
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
22
|
-
check_errors
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def check_errors
|
28
|
-
@variables.each do |node, count|
|
29
|
-
add_error "move code into model (#{node})", node.file, node.line if count > 2
|
30
|
-
end
|
29
|
+
def initialize(options={})
|
30
|
+
super()
|
31
|
+
@use_count = options['use_count'] || 2
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
# check if node to see whose conditional statementnodes contain multiple call nodes with same subject who is a local variable or instance variable.
|
35
|
+
#
|
36
|
+
# it will check every call and attrasgn nodes in the conditional statement nodes.
|
37
|
+
#
|
38
|
+
# if there are multiple call and attrasgn nodes who have the same subject,
|
39
|
+
# and the subject is a local variable or an instance variable,
|
40
|
+
# then the conditional statement nodes should be moved into model.
|
41
|
+
def review_start_if(node)
|
42
|
+
node.conditional_statement.grep_nodes(:node_type => [:call, :attrasgn]) { |child_node| remember_variable_use_count(child_node) }
|
43
|
+
|
44
|
+
variable_use_count.each do |variable_node, count|
|
45
|
+
add_error "move code into model (#{variable_node} use_count > #{@use_count})", variable_node.file, variable_node.line if count > @use_count
|
38
46
|
end
|
39
|
-
end
|
40
47
|
|
41
|
-
|
42
|
-
while call_node.subject.node_type == :call
|
43
|
-
call_node = call_node.subject
|
44
|
-
end
|
45
|
-
subject_node = call_node.subject
|
46
|
-
if [:ivar, :lvar].include?(subject_node.node_type) and subject_node[1] != :_erbout
|
47
|
-
subject_node
|
48
|
-
else
|
49
|
-
nil
|
50
|
-
end
|
48
|
+
reset_variable_use_count
|
51
49
|
end
|
52
50
|
end
|
53
51
|
end
|
@@ -3,32 +3,62 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check a controller file to make sure
|
6
|
+
# Check a controller file to make sure there are no complex finder.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/1-move-finder-to-named_scope.
|
9
9
|
#
|
10
|
-
# Implementation:
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all method calls in controller files.
|
17
|
+
# if there is any call node with message find, all, first or last,
|
18
|
+
# and it has a hash argument,
|
19
|
+
# then it is a complex finder, and should be moved to model's named scope.
|
11
20
|
class MoveFinderToNamedScopeCheck < Check
|
12
|
-
|
21
|
+
|
13
22
|
FINDER = [:find, :all, :first, :last]
|
14
|
-
|
15
|
-
def
|
23
|
+
|
24
|
+
def interesting_review_nodes
|
16
25
|
[:call]
|
17
26
|
end
|
18
|
-
|
19
|
-
def
|
27
|
+
|
28
|
+
def interesting_review_files
|
20
29
|
CONTROLLER_FILES
|
21
30
|
end
|
22
31
|
|
23
|
-
|
32
|
+
# check call node if its message is one of :find, :all, :first or :last,
|
33
|
+
# and it has a hash argument,
|
34
|
+
# then the call node is the finder that should be moved to model's named_scope.
|
35
|
+
def review_start_call(node)
|
24
36
|
add_error "move finder to named_scope" if finder?(node)
|
25
37
|
end
|
26
|
-
|
38
|
+
|
27
39
|
private
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
40
|
+
# check if the call node is a finder.
|
41
|
+
#
|
42
|
+
# if the subject of call node is a constant,
|
43
|
+
# and the message of call node is one of find, all, first or last,
|
44
|
+
# and any of its arguments is a hash,
|
45
|
+
# then it is a finder. e.g.
|
46
|
+
#
|
47
|
+
# s(:call, s(:const, :Post), :find,
|
48
|
+
# s(:arglist, s(:lit, :all),
|
49
|
+
# s(:hash,
|
50
|
+
# s(:lit, :conditions),
|
51
|
+
# s(:hash, s(:lit, :state), s(:str, "public")),
|
52
|
+
# s(:lit, :limit),
|
53
|
+
# s(:lit, 10),
|
54
|
+
# s(:lit, :order),
|
55
|
+
# s(:str, "created_at desc")
|
56
|
+
# )
|
57
|
+
# )
|
58
|
+
# )
|
59
|
+
def finder?(call_node)
|
60
|
+
:const == call_node.subject.node_type && FINDER.include?(call_node.message) && call_node.arguments.children.any? { |node| :hash == node.node_type }
|
61
|
+
end
|
32
62
|
end
|
33
63
|
end
|
34
|
-
end
|
64
|
+
end
|
@@ -3,47 +3,51 @@ 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 model logic should not exist in controller,
|
6
|
+
# Check a controller file to make sure that complex model logic should not exist in controller, should be moved into a model.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/7-move-model-logic-into-the-model.
|
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 method calls or attribute assignments apply to one subject,
|
18
|
+
# and the subject is a local variable or an instance variable,
|
19
|
+
# then they are complex model logic, and they should be moved into model.
|
10
20
|
class MoveModelLogicIntoModelCheck < Check
|
11
|
-
|
12
|
-
def
|
21
|
+
|
22
|
+
def interesting_review_nodes
|
13
23
|
[:defn]
|
14
24
|
end
|
15
|
-
|
16
|
-
def
|
25
|
+
|
26
|
+
def interesting_review_files
|
17
27
|
CONTROLLER_FILES
|
18
28
|
end
|
19
29
|
|
20
30
|
def initialize(options = {})
|
21
31
|
super()
|
22
|
-
@
|
32
|
+
@use_count = options['use_count'] || 4
|
23
33
|
end
|
24
34
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
35
|
+
# check method define node to see if there are multiple method calls and attribute assignments (more than @use_count defined) on one local variable or instance varialbe in review process.
|
36
|
+
#
|
37
|
+
# it will check every call and attrasgn nodes,
|
38
|
+
# if there are multiple call and attrasgn nodes who have the same subject,
|
39
|
+
# and the subject is a local variable or an instance variable,
|
40
|
+
# then these method calls and attribute assignments should be moved into model.
|
41
|
+
def review_start_defn(node)
|
42
|
+
node.grep_nodes(:node_type => [:call, :attrasgn]) do |child_node|
|
43
|
+
remember_variable_use_count(child_node)
|
32
44
|
end
|
33
|
-
|
34
|
-
|
35
|
-
add_error "move model logic into model (#{
|
45
|
+
|
46
|
+
variable_use_count.each do |variable_node, count|
|
47
|
+
add_error "move model logic into model (#{variable_node} use_count > #{@use_count})" if count > @use_count
|
36
48
|
end
|
37
|
-
@variables = nil
|
38
|
-
end
|
39
49
|
|
40
|
-
|
41
|
-
|
42
|
-
def call_node(node)
|
43
|
-
variable = node.subject
|
44
|
-
return if variable.nil? or ![:lvar, :ivar].include? node.subject.node_type
|
45
|
-
@variables[variable] ||= 0
|
46
|
-
@variables[variable] += 1
|
50
|
+
reset_variable_use_count
|
47
51
|
end
|
48
52
|
end
|
49
53
|
end
|
@@ -3,17 +3,40 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check config/routes.rb to make sure not to use too deep nesting routes.
|
6
|
+
# Check config/routes.rb file to make sure not to use too deep nesting routes.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/11-needless-deep-nesting.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# chech all iter nodes in route file.
|
17
|
+
#
|
18
|
+
# it is a recursively check in :iter node,
|
19
|
+
#
|
20
|
+
# if it is a :iter node,
|
21
|
+
# increment @counter at the beginning of resources,
|
22
|
+
# decrement @counter at the end of resrouces,
|
23
|
+
# recursively check nodes in iter's block body.
|
24
|
+
#
|
25
|
+
# if it is a :block node,
|
26
|
+
# then recursively check all child nodes in block node.
|
27
|
+
#
|
28
|
+
# if it is a :call node,
|
29
|
+
# and the message of the node is :resources or :resource,
|
30
|
+
# and the @counter is greater than @nested_count defined,
|
31
|
+
# then it is a needless deep nesting.
|
9
32
|
class NeedlessDeepNestingCheck < Check
|
10
|
-
|
11
|
-
def
|
33
|
+
|
34
|
+
def interesting_review_nodes
|
12
35
|
[:call, :iter]
|
13
36
|
end
|
14
37
|
|
15
|
-
def
|
16
|
-
|
38
|
+
def interesting_review_files
|
39
|
+
ROUTE_FILE
|
17
40
|
end
|
18
41
|
|
19
42
|
def initialize(options = {})
|
@@ -21,46 +44,84 @@ module RailsBestPractices
|
|
21
44
|
@counter = 0
|
22
45
|
@nested_count = options['nested_count'] || 2
|
23
46
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
47
|
+
|
48
|
+
# check all iter node in review process.
|
49
|
+
#
|
50
|
+
# It is a recursively check,
|
51
|
+
#
|
52
|
+
# if it is a :iter node, like
|
53
|
+
#
|
54
|
+
# resources posts do
|
55
|
+
# ...
|
56
|
+
# end
|
57
|
+
# increment @counter at the beginning of resources,
|
58
|
+
# decrement @counter at the end of iter resources,
|
59
|
+
# recursively check the block body.
|
60
|
+
#
|
61
|
+
# if it is a :block node, like
|
62
|
+
#
|
63
|
+
# resources :posts do
|
64
|
+
# resources :comments
|
65
|
+
# resources :votes
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# just recursively check each child node in block node.
|
69
|
+
#
|
70
|
+
# if it is a :call node with message :resources or :resource, like
|
71
|
+
#
|
72
|
+
# resources :comments
|
73
|
+
#
|
74
|
+
# test if the @counter is greater than or equal to @nested_count,
|
75
|
+
# if so, it is a needless deep nesting.
|
76
|
+
def review_start_iter(node)
|
77
|
+
recursively_check(node)
|
27
78
|
end
|
28
79
|
|
29
80
|
private
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
81
|
+
# check nested route.
|
82
|
+
#
|
83
|
+
# if the node type is :iter,
|
84
|
+
# and the subject of the node is with message :resources or :resource, like
|
85
|
+
#
|
86
|
+
# s(:iter,
|
87
|
+
# s(:call, nil, :resources,
|
88
|
+
# s(:arglist, s(:lit, :posts))
|
89
|
+
# ),
|
90
|
+
# nil,
|
91
|
+
# s(:call, nil, :resources,
|
92
|
+
# s(:arglist, s(:lit, :comments))
|
93
|
+
# )
|
94
|
+
# )
|
95
|
+
#
|
96
|
+
# then increment the @counter, recursively check the block body, and decrement the @counter.
|
97
|
+
#
|
98
|
+
# if the node type is :block, it is the block body of :iter node, like
|
99
|
+
#
|
100
|
+
# s(:block,
|
101
|
+
# s(:call, nil, :resources, s(:arglist, s(:lit, :comments))),
|
102
|
+
# s(:call, nil, :resources, s(:arglist, s(:lit, :votes)))
|
103
|
+
# )
|
104
|
+
#
|
105
|
+
# then check the each child node in the block.
|
106
|
+
#
|
107
|
+
# if the node type is :call,
|
108
|
+
# and the message of node is :resources or :resource, like
|
109
|
+
#
|
110
|
+
# s(:call, nil, :resources, s(:arglist, s(:lit, :comments)))
|
111
|
+
#
|
112
|
+
# then check if @counter is greater than or equal to @nested_count,
|
113
|
+
# if so, it is the needless deep nesting.
|
114
|
+
def recursively_check(node)
|
115
|
+
if :iter == node.node_type && :resources == node.subject.message
|
53
116
|
@counter += 1
|
54
|
-
|
117
|
+
recursively_check(node.body)
|
55
118
|
@counter -= 1
|
56
119
|
elsif :block == node.node_type
|
57
120
|
node.children.each do |child_node|
|
58
|
-
|
59
|
-
add_error "needless deep nesting (nested_count > #{@nested_count})", child_node.file, child_node.line
|
60
|
-
end
|
121
|
+
recursively_check(child_node)
|
61
122
|
end
|
62
|
-
elsif :call == node.node_type
|
63
|
-
add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter
|
123
|
+
elsif :call == node.node_type && [:resources, :resource].include?(node.message)
|
124
|
+
add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter >= @nested_count
|
64
125
|
end
|
65
126
|
end
|
66
127
|
end
|
@@ -3,23 +3,54 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check config/routes to make sure not use default route that rails generated.
|
6
|
+
# Check config/routes file to make sure not use default route that rails generated.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/12-not-use-default-route-if-you-use-restful-design
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all method call to see if any method call is the same as rails default route.
|
17
|
+
#
|
18
|
+
# map.connect ':controller/:action/:id'
|
19
|
+
# map.connect ':controller/:action/:id.:format'
|
20
|
+
#
|
21
|
+
# or
|
22
|
+
#
|
23
|
+
# match ':controller(/:action(/:id(.:format)))'
|
9
24
|
class NotUseDefaultRouteCheck < Check
|
10
|
-
|
11
|
-
def
|
25
|
+
|
26
|
+
def interesting_review_nodes
|
12
27
|
[:call]
|
13
28
|
end
|
14
|
-
|
15
|
-
def
|
16
|
-
|
29
|
+
|
30
|
+
def interesting_review_files
|
31
|
+
ROUTE_FILE
|
17
32
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
33
|
+
|
34
|
+
# check all method calls, it just compare with rails default route
|
35
|
+
#
|
36
|
+
# rails2
|
37
|
+
#
|
38
|
+
# s(:call, s(:lvar, :map), :connect,
|
39
|
+
# s(:arglist, s(:str, ":controller/:action/:id"))
|
40
|
+
# )
|
41
|
+
# s(:call, s(:lvar, :map), :connect,
|
42
|
+
# s(:arglist, s(:str, ":controller/:action/:id.:format"))
|
43
|
+
# )
|
44
|
+
#
|
45
|
+
# rails3
|
46
|
+
#
|
47
|
+
# s(:call, nil, :match,
|
48
|
+
# s(:arglist, s(:str, ":controller(/:action(/:id(.:format)))"))
|
49
|
+
# )
|
50
|
+
def review_start_call(node)
|
51
|
+
if s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id"))) == node ||
|
52
|
+
s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format"))) == node ||
|
53
|
+
s(:call, nil, :match, s(:arglist, s(:str, ":controller(/:action(/:id(.:format)))"))) == node
|
23
54
|
add_error "not use default route"
|
24
55
|
end
|
25
56
|
end
|