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.
Files changed (37) hide show
  1. data/README.md +161 -0
  2. data/lib/rails_best_practices.rb +158 -20
  3. data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +108 -34
  4. data/lib/rails_best_practices/checks/always_add_db_index_check.rb +148 -29
  5. data/lib/rails_best_practices/checks/check.rb +178 -75
  6. data/lib/rails_best_practices/checks/dry_bundler_in_capistrano_check.rb +26 -5
  7. data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +66 -15
  8. data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +53 -12
  9. data/lib/rails_best_practices/checks/law_of_demeter_check.rb +59 -30
  10. data/lib/rails_best_practices/checks/move_code_into_controller_check.rb +35 -15
  11. data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +56 -12
  12. data/lib/rails_best_practices/checks/move_code_into_model_check.rb +30 -32
  13. data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +45 -15
  14. data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +31 -27
  15. data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +99 -38
  16. data/lib/rails_best_practices/checks/not_use_default_route_check.rb +43 -12
  17. data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +140 -28
  18. data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +44 -30
  19. data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +18 -7
  20. data/lib/rails_best_practices/checks/use_before_filter_check.rb +88 -18
  21. data/lib/rails_best_practices/checks/use_model_association_check.rb +61 -22
  22. data/lib/rails_best_practices/checks/use_observer_check.rb +125 -23
  23. data/lib/rails_best_practices/checks/use_query_attribute_check.rb +75 -47
  24. data/lib/rails_best_practices/checks/use_say_with_time_in_migrations_check.rb +59 -10
  25. data/lib/rails_best_practices/checks/use_scope_access_check.rb +78 -23
  26. data/lib/rails_best_practices/command.rb +19 -34
  27. data/lib/rails_best_practices/core.rb +4 -2
  28. data/lib/rails_best_practices/core/checking_visitor.rb +49 -19
  29. data/lib/rails_best_practices/core/error.rb +5 -2
  30. data/lib/rails_best_practices/core/runner.rb +79 -55
  31. data/lib/rails_best_practices/core/visitable_sexp.rb +325 -55
  32. data/lib/rails_best_practices/{core/core_ext.rb → core_ext/enumerable.rb} +3 -6
  33. data/lib/rails_best_practices/core_ext/nil_class.rb +8 -0
  34. data/lib/rails_best_practices/version.rb +1 -1
  35. data/rails_best_practices.yml +2 -2
  36. metadata +8 -7
  37. 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
- # 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
+ # 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 interesting_nodes
12
- [:if, :unless]
21
+ def interesting_review_nodes
22
+ [:if]
13
23
  end
14
24
 
15
- def interesting_files
25
+ def interesting_review_files
16
26
  VIEW_FILES
17
27
  end
18
28
 
19
- def evaluate_start(node)
20
- @variables = {}
21
- node.conditional_statement.grep_nodes(:node_type => :call).each { |call_node| remember_call(call_node) }
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
- def remember_call(call_node)
34
- variable_node = variable(call_node)
35
- if variable_node
36
- @variables[variable_node] ||= 0
37
- @variables[variable_node] += 1
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
- def variable(call_node)
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 finder is simple.
6
+ # Check a controller file to make sure there are no complex finder.
7
7
  #
8
- # Complex finder in controller is a code smell, use namd_scope instead.
8
+ # See the best practice details here http://rails-bestpractices.com/posts/1-move-finder-to-named_scope.
9
9
  #
10
- # Implementation: check method :find, :all, :first, :last with hash parameters.
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 interesting_nodes
23
+
24
+ def interesting_review_nodes
16
25
  [:call]
17
26
  end
18
-
19
- def interesting_files
27
+
28
+ def interesting_review_files
20
29
  CONTROLLER_FILES
21
30
  end
22
31
 
23
- def evaluate_start(node)
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
- def finder?(node)
30
- node.subject.node_type == :const && FINDER.include?(node.message) && node.arguments.children.any? {|node| node.node_type == :hash}
31
- end
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, move it into a model.
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
- # Implementation: check the count of method calling of a model,
9
- # if it is more than defined called count, then it contains model logic.
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 interesting_nodes
21
+
22
+ def interesting_review_nodes
13
23
  [:defn]
14
24
  end
15
-
16
- def interesting_files
25
+
26
+ def interesting_review_files
17
27
  CONTROLLER_FILES
18
28
  end
19
29
 
20
30
  def initialize(options = {})
21
31
  super()
22
- @called_count = options['called_count'] || 4
32
+ @use_count = options['use_count'] || 4
23
33
  end
24
34
 
25
- def evaluate_start(node)
26
- @variables = {}
27
- node.recursive_children do |child|
28
- case child.node_type
29
- when :attrasgn, :call
30
- call_node(child)
31
- end
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
- @variables.each do |variable, count|
35
- add_error "move model logic into model (#{variable} called_count > #{@called_count})" if count > @called_count
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
- private
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
- # Implementation: check nested route count, if more than nested_count, then it is needless deep nesting.
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 interesting_nodes
33
+
34
+ def interesting_review_nodes
12
35
  [:call, :iter]
13
36
  end
14
37
 
15
- def interesting_files
16
- /config\/routes.rb/
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
- def evaluate_start(node)
26
- check_nested_count(node)
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
- def check_nested_count(node)
31
- if :iter == node.node_type
32
- check_for_rails3(node)
33
- elsif :resources == node.message and node.subject
34
- check_for_rails2(node)
35
- end
36
- end
37
-
38
- def check_for_rails3(node)
39
- nested_count_for_rails3(node)
40
- end
41
-
42
- def check_for_rails2(node)
43
- if node.subject == s(:call, nil, :map, s(:arglist))
44
- @counter = 0
45
- else
46
- @counter += 1
47
- add_error "needless deep nesting (nested_count > #{@nested_count})" if @counter >= @nested_count
48
- end
49
- end
50
-
51
- def nested_count_for_rails3(node)
52
- if :iter == node.node_type and :resources == node.subject.message and !node.message
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
- nested_count_for_rails3(node[3])
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
- if :resources == child_node.message and nil == child_node.subject and @counter + 1 > @nested_count
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 and :resources == node.message
63
- add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter + 1 > @nested_count
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
- # Implementation: compare route sentence to see if it is equal to rails default route.
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 interesting_nodes
25
+
26
+ def interesting_review_nodes
12
27
  [:call]
13
28
  end
14
-
15
- def interesting_files
16
- /config\/routes.rb/
29
+
30
+ def interesting_review_files
31
+ ROUTE_FILE
17
32
  end
18
-
19
- def evaluate_start(node)
20
- if node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id"))) or
21
- node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format"))) or
22
- node == s(:call, nil, :match, s(:arglist, s(:str, ":controller(/:action(/:id(.:format)))")))
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