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
@@ -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
|