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