rails_best_practices 1.3.0 → 1.4.0

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 (60) hide show
  1. data/Gemfile.lock +1 -1
  2. data/README.md +14 -12
  3. data/lib/rails_best_practices.rb +7 -21
  4. data/lib/rails_best_practices/core.rb +1 -0
  5. data/lib/rails_best_practices/core/check.rb +156 -6
  6. data/lib/rails_best_practices/core/checking_visitor.rb +2 -2
  7. data/lib/rails_best_practices/core/methods.rb +1 -0
  8. data/lib/rails_best_practices/core/nil.rb +10 -0
  9. data/lib/rails_best_practices/core/routes.rb +33 -0
  10. data/lib/rails_best_practices/core/runner.rb +3 -1
  11. data/lib/rails_best_practices/core_ext/sexp.rb +11 -0
  12. data/lib/rails_best_practices/prepares.rb +5 -2
  13. data/lib/rails_best_practices/prepares/controller_prepare.rb +8 -14
  14. data/lib/rails_best_practices/prepares/mailer_prepare.rb +2 -7
  15. data/lib/rails_best_practices/prepares/model_prepare.rb +3 -8
  16. data/lib/rails_best_practices/prepares/route_prepare.rb +142 -0
  17. data/lib/rails_best_practices/prepares/schema_prepare.rb +3 -8
  18. data/lib/rails_best_practices/reviews.rb +1 -0
  19. data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +3 -8
  20. data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +9 -12
  21. data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +3 -8
  22. data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +3 -8
  23. data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +2 -8
  24. data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +3 -4
  25. data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +2 -8
  26. data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +3 -8
  27. data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +3 -8
  28. data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +2 -8
  29. data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +3 -8
  30. data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +3 -8
  31. data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +3 -8
  32. data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +3 -9
  33. data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +3 -8
  34. data/lib/rails_best_practices/reviews/remove_unused_methods_in_controllers_review.rb +57 -0
  35. data/lib/rails_best_practices/reviews/remove_unused_methods_in_models_review.rb +6 -92
  36. data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +3 -8
  37. data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +3 -8
  38. data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +3 -8
  39. data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +3 -8
  40. data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +3 -8
  41. data/lib/rails_best_practices/reviews/use_before_filter_review.rb +3 -8
  42. data/lib/rails_best_practices/reviews/use_model_association_review.rb +3 -8
  43. data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +3 -8
  44. data/lib/rails_best_practices/reviews/use_observer_review.rb +3 -8
  45. data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +2 -4
  46. data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +2 -8
  47. data/lib/rails_best_practices/reviews/use_scope_access_review.rb +3 -8
  48. data/lib/rails_best_practices/version.rb +1 -1
  49. data/rails_best_practices.yml +1 -0
  50. data/spec/rails_best_practices/core/check_spec.rb +2 -2
  51. data/spec/rails_best_practices/core/checking_visitor_spec.rb +12 -32
  52. data/spec/rails_best_practices/core/nil_spec.rb +12 -0
  53. data/spec/rails_best_practices/core/routes_spec.rb +10 -0
  54. data/spec/rails_best_practices/core_ext/sexp_spec.rb +14 -0
  55. data/spec/rails_best_practices/prepares/route_prepare_spec.rb +502 -0
  56. data/spec/rails_best_practices/reviews/always_add_db_index_review_spec.rb +14 -1
  57. data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +20 -4
  58. data/spec/rails_best_practices/reviews/remove_unused_methods_in_controllers_review_spec.rb +120 -0
  59. data/spec/rails_best_practices/reviews/remove_unused_methods_in_models_review_spec.rb +1 -1
  60. metadata +113 -123
@@ -16,16 +16,15 @@ module RailsBestPractices
16
16
  # and outer the call node, it is also a call node,
17
17
  # then it violate the law of demeter.
18
18
  class LawOfDemeterReview < Review
19
+ interesting_nodes :call
20
+ interesting_files ALL_FILES
21
+
19
22
  ASSOCIATION_METHODS = %w(belongs_to has_one)
20
23
 
21
24
  def url
22
25
  "http://rails-bestpractices.com/posts/15-the-law-of-demeter"
23
26
  end
24
27
 
25
- def interesting_nodes
26
- [:call]
27
- end
28
-
29
28
  # check the call node,
30
29
  #
31
30
  # if the subject of the call node is also a call node,
@@ -12,6 +12,8 @@ module RailsBestPractices
12
12
  # Review process:
13
13
  # only check all view files to see if there are finders, then the finders should be moved to controller.
14
14
  class MoveCodeIntoControllerReview < Review
15
+ interesting_nodes :method_add_arg, :assign
16
+ interesting_files VIEW_FILES
15
17
 
16
18
  FINDERS = %w(find all first last)
17
19
 
@@ -19,14 +21,6 @@ module RailsBestPractices
19
21
  "http://rails-bestpractices.com/posts/24-move-code-into-controller"
20
22
  end
21
23
 
22
- def interesting_nodes
23
- [:method_add_arg, :assign]
24
- end
25
-
26
- def interesting_files
27
- VIEW_FILES
28
- end
29
-
30
24
  # check method_add_arg nodes.
31
25
  #
32
26
  # if the subject of the method_add_arg node is a constant,
@@ -19,18 +19,13 @@ module RailsBestPractices
19
19
  # and the size of the array is greater than array_count defined,
20
20
  # then the options_for_select method should be moved into helper.
21
21
  class MoveCodeIntoHelperReview < Review
22
+ interesting_nodes :method_add_arg
23
+ interesting_files VIEW_FILES
24
+
22
25
  def url
23
26
  "http://rails-bestpractices.com/posts/26-move-code-into-helper"
24
27
  end
25
28
 
26
- def interesting_nodes
27
- [:method_add_arg]
28
- end
29
-
30
- def interesting_files
31
- VIEW_FILES
32
- end
33
-
34
29
  def initialize(options = {})
35
30
  super()
36
31
  @array_count = options['array_count'] || 3
@@ -13,18 +13,13 @@ module RailsBestPractices
13
13
  # check if, unless, elsif there are multiple method calls or attribute assignments apply to one subject,
14
14
  # and the subject is a variable, then they should be moved into model.
15
15
  class MoveCodeIntoModelReview < Review
16
+ interesting_nodes :if, :unless, :elsif
17
+ interesting_files VIEW_FILES
18
+
16
19
  def url
17
20
  "http://rails-bestpractices.com/posts/25-move-code-into-model"
18
21
  end
19
22
 
20
- def interesting_nodes
21
- [:if, :unless, :elsif]
22
- end
23
-
24
- def interesting_files
25
- VIEW_FILES
26
- end
27
-
28
23
  def initialize(options={})
29
24
  super()
30
25
  @use_count = options['use_count'] || 2
@@ -15,6 +15,8 @@ module RailsBestPractices
15
15
  # and it has a hash argument,
16
16
  # then it is a complex finder, and should be moved to model's named scope.
17
17
  class MoveFinderToNamedScopeReview < Review
18
+ interesting_nodes :method_add_arg
19
+ interesting_files CONTROLLER_FILES
18
20
 
19
21
  FINDERS = %w(find all first last)
20
22
 
@@ -22,14 +24,6 @@ module RailsBestPractices
22
24
  "http://rails-bestpractices.com/posts/1-move-finder-to-named_scope"
23
25
  end
24
26
 
25
- def interesting_nodes
26
- [:method_add_arg]
27
- end
28
-
29
- def interesting_files
30
- CONTROLLER_FILES
31
- end
32
-
33
27
  # check method_add_ag node if its message is one of find, all, first or last,
34
28
  # and it has a hash argument,
35
29
  # then the call node is the finder that should be moved to model's named_scope.
@@ -15,18 +15,13 @@ module RailsBestPractices
15
15
  # and the subject is a variable,
16
16
  # then they are complex model logic, and they should be moved into model.
17
17
  class MoveModelLogicIntoModelReview < Review
18
+ interesting_nodes :def
19
+ interesting_files CONTROLLER_FILES
20
+
18
21
  def url
19
22
  "http://rails-bestpractices.com/posts/7-move-model-logic-into-the-model"
20
23
  end
21
24
 
22
- def interesting_nodes
23
- [:def]
24
- end
25
-
26
- def interesting_files
27
- CONTROLLER_FILES
28
- end
29
-
30
25
  def initialize(options = {})
31
26
  super()
32
27
  @use_count = options['use_count'] || 4
@@ -24,18 +24,13 @@ module RailsBestPractices
24
24
  # and the @counter is greater than @nested_count defined,
25
25
  # then it is a needless deep nesting.
26
26
  class NeedlessDeepNestingReview < Review
27
+ interesting_nodes :method_add_block
28
+ interesting_files ROUTE_FILES
29
+
27
30
  def url
28
31
  "http://rails-bestpractices.com/posts/11-needless-deep-nesting"
29
32
  end
30
33
 
31
- def interesting_nodes
32
- [:method_add_block]
33
- end
34
-
35
- def interesting_files
36
- ROUTE_FILES
37
- end
38
-
39
34
  def initialize(options = {})
40
35
  super()
41
36
  @counter = 0
@@ -19,18 +19,13 @@ module RailsBestPractices
19
19
  #
20
20
  # match ':controller(/:action(/:id(.:format)))'
21
21
  class NotUseDefaultRouteReview < Review
22
+ interesting_nodes :command_call, :command
23
+ interesting_files ROUTE_FILES
24
+
22
25
  def url
23
26
  "http://rails-bestpractices.com/posts/12-not-use-default-route-if-you-use-restful-design"
24
27
  end
25
28
 
26
- def interesting_nodes
27
- [:command_call, :command]
28
- end
29
-
30
- def interesting_files
31
- ROUTE_FILES
32
- end
33
-
34
29
  # check all command call nodes, compare with rails2 default route
35
30
  def start_command_call(node)
36
31
  if "map" == node.subject.to_s && "connect" == node.message.to_s &&
@@ -28,6 +28,8 @@ module RailsBestPractices
28
28
  # whose message is get, post, update or delete,
29
29
  # then these custom routes are overuse.
30
30
  class OveruseRouteCustomizationsReview < Review
31
+ interesting_nodes :command_call, :method_add_block
32
+ interesting_files ROUTE_FILES
31
33
 
32
34
  VERBS = %w(get post update delete)
33
35
 
@@ -35,14 +37,6 @@ module RailsBestPractices
35
37
  "http://rails-bestpractices.com/posts/10-overuse-route-customizations"
36
38
  end
37
39
 
38
- def interesting_nodes
39
- [:command_call, :method_add_block]
40
- end
41
-
42
- def interesting_files
43
- ROUTE_FILES
44
- end
45
-
46
40
  def initialize(options = {})
47
41
  super()
48
42
  @customize_count = options['customize_count'] || 3
@@ -84,7 +78,7 @@ module RailsBestPractices
84
78
  # it is just the count of member and collection custom routes.
85
79
  def member_and_collection_count_for_rails2(node)
86
80
  if "resources" == node.message.to_s
87
- hash_node = node.arguments.all[1]
81
+ hash_node = node.arguments.all.last
88
82
  if hash_node && :bare_assoc_hash == hash_node.sexp_type
89
83
  member_node = hash_node.hash_value("member")
90
84
  collection_node = hash_node.hash_value("collection")
@@ -12,18 +12,13 @@ module RailsBestPractices
12
12
  # Review process:
13
13
  # check all helper files, if the body of module is nil, then the helper file should be removed.
14
14
  class RemoveEmptyHelpersReview < Review
15
+ interesting_nodes :module
16
+ interesting_files HELPER_FILES
17
+
15
18
  def url
16
19
  "http://rails-bestpractices.com/posts/72-remove-empty-helpers"
17
20
  end
18
21
 
19
- def interesting_files
20
- HELPER_FILES
21
- end
22
-
23
- def interesting_nodes
24
- [:module]
25
- end
26
-
27
22
  # check the body of module node, if it is nil, then it should be removed.
28
23
  def start_module(node)
29
24
  if s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil) == node.body
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/reviews/review'
3
+
4
+ module RailsBestPractices
5
+ module Reviews
6
+ class RemoveUnusedMethodsInControllersReview < Review
7
+ include Klassable
8
+ include Completeable
9
+ include Callable
10
+ include InheritedResourcesable
11
+
12
+ interesting_nodes :class
13
+ interesting_files CONTROLLER_FILES
14
+
15
+ EXCEPT_METHODS = %w(rescue_action)
16
+ INHERITED_RESOURCES_METHODS = %w(resource collection begin_of_association_chain build_resource)
17
+
18
+ def initialize(options={})
19
+ @controller_methods = Prepares.controller_methods
20
+ @routes = Prepares.routes
21
+ @inherited_resources = false
22
+ @except_methods = EXCEPT_METHODS + options['except_methods']
23
+ end
24
+
25
+ # mark custom inherited_resources methods as used.
26
+ def end_class(node)
27
+ if @inherited_resources
28
+ INHERITED_RESOURCES_METHODS.each do |method|
29
+ call_method(method)
30
+ end
31
+ end
32
+ end
33
+
34
+ # get all unused methods at the end of review process.
35
+ def on_complete
36
+ @routes.each do |route|
37
+ if "*" == route.action_name
38
+ action_names = @controller_methods.get_methods(route.controller_name_with_namespaces).map(&:method_name)
39
+ action_names.each { |action_name| call_method(action_name, route.controller_name_with_namespaces) }
40
+ else
41
+ call_method(route.action_name, route.controller_name_with_namespaces)
42
+ end
43
+ end
44
+ @controller_methods.get_all_unused_methods.each do |method|
45
+ if !@except_methods.include?(method.method_name)
46
+ add_error "remove unused methods (#{method.class_name}##{method.method_name})", method.file, method.line
47
+ end
48
+ end
49
+ end
50
+
51
+ protected
52
+ def methods
53
+ @controller_methods
54
+ end
55
+ end
56
+ end
57
+ end
@@ -14,12 +14,11 @@ module RailsBestPractices
14
14
  class RemoveUnusedMethodsInModelsReview < Review
15
15
  include Klassable
16
16
  include Completeable
17
+ include Callable
17
18
 
18
- EXCEPT_METHODS = %w(initialize validate to_xml to_json assign_attributes after_find after_initialize)
19
+ interesting_files ALL_FILES
19
20
 
20
- def interesting_nodes
21
- [:module, :class, :call, :fcall, :command, :command_call, :method_add_arg, :var_ref, :alias, :bare_assoc_hash]
22
- end
21
+ EXCEPT_METHODS = %w(initialize validate to_xml to_json assign_attributes after_find after_initialize)
23
22
 
24
23
  def initialize(options={})
25
24
  super()
@@ -27,75 +26,6 @@ module RailsBestPractices
27
26
  @except_methods = EXCEPT_METHODS + options['except_methods']
28
27
  end
29
28
 
30
- # remember the message of call node.
31
- def start_call(node)
32
- mark_used(node.message)
33
- end
34
-
35
- # remember the message of fcall node.
36
- def start_fcall(node)
37
- mark_used(node.message)
38
- end
39
-
40
- # remember name of var_ref node.
41
- def start_var_ref(node)
42
- mark_used(node)
43
- end
44
-
45
- # remember the message of command call node.
46
- def start_command_call(node)
47
- mark_used(node.message)
48
- end
49
-
50
- # remember the message of command node.
51
- # remember the argument of alias_method and alias_method_chain as well.
52
- def start_command(node)
53
- case node.message.to_s
54
- when "named_scope", "scope"
55
- # nothing
56
- when "alias_method"
57
- mark_used(node.arguments.all[1])
58
- when "alias_method_chain"
59
- method, feature = *node.arguments.all.map(&:to_s)
60
- call_method("#{method}_with_#{feature}")
61
- else
62
- mark_used(node.message)
63
- node.arguments.all.each { |argument| mark_used(argument) }
64
- end
65
- end
66
-
67
- # remember the old method of alias node.
68
- def start_alias(node)
69
- mark_used(node.old_method)
70
- end
71
-
72
- # remember hash values for hash key "methods".
73
- #
74
- # def to_xml(options = {})
75
- # super options.merge(:exclude => :visible, :methods => [:is_discussion_conversation])
76
- # end
77
- def start_bare_assoc_hash(node)
78
- if node.hash_keys.include? "methods"
79
- mark_used(node.hash_value("methods"))
80
- end
81
- end
82
-
83
- # remember the first argument for try and send method.
84
- def start_method_add_arg(node)
85
- case node.message.to_s
86
- when "try"
87
- method_name = node.arguments.all[0].to_s
88
- call_method(method_name)
89
- when "send"
90
- if [:symbol_literal, :string_literal].include?(node.arguments.all[0].sexp_type)
91
- method_name = node.arguments.all[0].to_s
92
- call_method(method_name)
93
- end
94
- else
95
- # nothing
96
- end
97
- end
98
-
99
29
  # get all unused methods at the end of review process.
100
30
  def on_complete
101
31
  @model_methods.get_all_unused_methods.each do |method|
@@ -105,25 +35,9 @@ module RailsBestPractices
105
35
  end
106
36
  end
107
37
 
108
- private
109
- def mark_used(method_node)
110
- if :bare_assoc_hash == method_node.sexp_type
111
- method_node.hash_values.each { |value_node| mark_used(value_node) }
112
- elsif :array == method_node.sexp_type
113
- method_node.array_values.each { |value_node| mark_used(value_node) }
114
- else
115
- method_name = method_node.to_s
116
- end
117
- call_method(method_name)
118
- end
119
-
120
- def call_method(method_name)
121
- if @model_methods.has_method?(current_class_name, method_name)
122
- @model_methods.get_method(current_class_name, method_name).mark_used
123
- end
124
- @model_methods.mark_parent_class_method_used(current_class_name, method_name)
125
- @model_methods.mark_subclasses_method_used(current_class_name, method_name)
126
- @model_methods.possible_public_used(method_name)
38
+ protected
39
+ def methods
40
+ @model_methods
127
41
  end
128
42
  end
129
43
  end
@@ -16,18 +16,13 @@ module RailsBestPractices
16
16
  # and after them there is a call node with message "save" or "save!",
17
17
  # then these attribute assignments are complex creation, should be replaced with factory method.
18
18
  class ReplaceComplexCreationWithFactoryMethodReview < Review
19
+ interesting_nodes :def
20
+ interesting_files CONTROLLER_FILES
21
+
19
22
  def url
20
23
  "http://rails-bestpractices.com/posts/6-replace-complex-creation-with-factory-method"
21
24
  end
22
25
 
23
- def interesting_nodes
24
- [:def]
25
- end
26
-
27
- def interesting_files
28
- CONTROLLER_FILES
29
- end
30
-
31
26
  def initialize(options = {})
32
27
  super()
33
28
  @assigns_count = options['attribute_assignment_count'] || 2
@@ -13,18 +13,13 @@ module RailsBestPractices
13
13
  # check all instance variable in partial view files,
14
14
  # if exist, then they should be replaced with local variable
15
15
  class ReplaceInstanceVariableWithLocalVariableReview < Review
16
+ interesting_nodes :var_ref
17
+ interesting_files PARTIAL_VIEW_FILES
18
+
16
19
  def url
17
20
  "http://rails-bestpractices.com/posts/27-replace-instance-variable-with-local-variable"
18
21
  end
19
22
 
20
- def interesting_nodes
21
- [:var_ref]
22
- end
23
-
24
- def interesting_files
25
- PARTIAL_VIEW_FILES
26
- end
27
-
28
23
  # check ivar node in partial view file,
29
24
  # it is an instance variable, and should be replaced with local variable.
30
25
  def start_var_ref(node)