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
@@ -782,10 +782,21 @@ class Sexp
782
782
  end
783
783
  end
784
784
 
785
+ # check if the self node is a const.
785
786
  def const?
786
787
  :@const == self.sexp_type || (:var_ref == self.sexp_type && :@const == self[1].sexp_type)
787
788
  end
788
789
 
790
+ # true
791
+ def present?
792
+ true
793
+ end
794
+
795
+ # false
796
+ def blank?
797
+ false
798
+ end
799
+
789
800
  # remove the line and column info from sexp.
790
801
  def remove_line_and_column
791
802
  node = self.clone
@@ -3,12 +3,11 @@ require 'rails_best_practices/prepares/model_prepare'
3
3
  require 'rails_best_practices/prepares/mailer_prepare'
4
4
  require 'rails_best_practices/prepares/schema_prepare'
5
5
  require 'rails_best_practices/prepares/controller_prepare'
6
+ require 'rails_best_practices/prepares/route_prepare'
6
7
 
7
8
  module RailsBestPractices
8
9
  module Prepares
9
10
  class <<self
10
- attr_writer :models, :model_associations, :model_attributes, :mailers
11
-
12
11
  def klasses
13
12
  models + mailers + controllers
14
13
  end
@@ -41,6 +40,10 @@ module RailsBestPractices
41
40
  @controller_methods ||= Core::Methods.new
42
41
  end
43
42
 
43
+ def routes
44
+ @routes ||= Core::Routes.new
45
+ end
46
+
44
47
  # Clear all prepare objects.
45
48
  def clear
46
49
  instance_variables.each do |instance_variable|
@@ -6,17 +6,13 @@ module RailsBestPractices
6
6
  # Remember controllers and controller methods
7
7
  class ControllerPrepare < Core::Check
8
8
  include Core::Check::Klassable
9
+ include Core::Check::InheritedResourcesable
9
10
  include Core::Check::Accessable
10
11
 
11
- DEFAULT_ACTIONS = %w(index show new create edit update destroy)
12
-
13
- def interesting_nodes
14
- [:module, :class, :def, :command, :var_ref]
15
- end
12
+ interesting_nodes :class, :var_ref, :command, :def
13
+ interesting_files CONTROLLER_FILES
16
14
 
17
- def interesting_files
18
- CONTROLLER_FILES
19
- end
15
+ DEFAULT_ACTIONS = %w(index show new create edit update destroy)
20
16
 
21
17
  def initialize
22
18
  @controllers = Prepares.controllers
@@ -28,25 +24,23 @@ module RailsBestPractices
28
24
  # also check if the controller is inherit from InheritedResources::Base.
29
25
  def start_class(node)
30
26
  @controllers << @klass
31
- if "InheritedResources::Base" == current_extend_class_name
32
- @inherited_resources = true
27
+ if @inherited_resources
33
28
  @actions = DEFAULT_ACTIONS
34
29
  end
35
30
  end
36
31
 
37
32
  # remember the action names at the end of class node if the controller is a InheritedResources.
38
33
  def end_class(node)
39
- if @inherited_resources
34
+ if @inherited_resources && "ApplicationController" != current_class_name
40
35
  @actions.each do |action|
41
- @methods.add_method(current_class_name, action)
36
+ @methods.add_method(current_class_name, action, {"file" => node.file, "line" => node.line})
42
37
  end
43
38
  end
44
39
  end
45
40
 
46
41
  # check if there is a DSL call inherit_resources.
47
42
  def start_var_ref(node)
48
- if "inherit_resources" == node.to_s
49
- @inherited_resources = true
43
+ if @inherited_resources
50
44
  @actions = DEFAULT_ACTIONS
51
45
  end
52
46
  end
@@ -7,13 +7,8 @@ module RailsBestPractices
7
7
  class MailerPrepare < Core::Check
8
8
  include Core::Check::Klassable
9
9
 
10
- def interesting_nodes
11
- [:class, :module]
12
- end
13
-
14
- def interesting_files
15
- /#{MAILER_FILES}|#{MODEL_FILES}/
16
- end
10
+ interesting_nodes :class
11
+ interesting_files MAILER_FILES, MODEL_FILES
17
12
 
18
13
  def initialize
19
14
  @mailers = Prepares.mailers
@@ -8,15 +8,10 @@ module RailsBestPractices
8
8
  include Core::Check::Klassable
9
9
  include Core::Check::Accessable
10
10
 
11
- ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many)
12
-
13
- def interesting_nodes
14
- [:module, :class, :def, :command, :var_ref, :alias]
15
- end
11
+ interesting_nodes :class, :def, :command, :var_ref, :alias
12
+ interesting_files MODEL_FILES
16
13
 
17
- def interesting_files
18
- MODEL_FILES
19
- end
14
+ ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many)
20
15
 
21
16
  def initialize
22
17
  @models = Prepares.models
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/core/check'
3
+
4
+ module RailsBestPractices
5
+ module Prepares
6
+ # Remembber routes.
7
+ class RoutePrepare < Core::Check
8
+ interesting_nodes :command, :command_call, :method_add_block
9
+ interesting_files ROUTE_FILES
10
+
11
+ RESOURCES_ACTIONS = %w(index show new create edit update destroy)
12
+ RESOURCE_ACTIONS = %w(show new create edit update destroy)
13
+
14
+ def initialize
15
+ @routes = Prepares.routes
16
+ @namespaces = []
17
+ end
18
+
19
+ # remember route for rails3.
20
+ def start_command(node)
21
+ case node.message.to_s
22
+ when "resources"
23
+ add_resources_routes(node)
24
+ when "resource"
25
+ add_resource_routes(node)
26
+ when "get", "post", "put", "delete"
27
+ action_name = node.arguments.all.first.to_s
28
+ @routes.add_route(current_namespaces, current_resource_name, action_name)
29
+ when "match", "root"
30
+ options = node.arguments.all.last
31
+ if options.hash_value("controller").present?
32
+ controller_name = options.hash_value("controller").to_s
33
+ action_name = options.hash_value("action").present? ? options.hash_value("action").to_s : "*"
34
+ @routes.add_route(current_namespaces, controller_name, action_name)
35
+ else
36
+ route_node = options.hash_values.find { |value_node| :string_literal == value_node.sexp_type && value_node.to_s.include?('#') }
37
+ if route_node.present?
38
+ controller_name, action_name = route_node.to_s.split('#')
39
+ @routes.add_route(current_namespaces, controller_name.underscore, action_name)
40
+ end
41
+ end
42
+ else
43
+ # nothing to do
44
+ end
45
+ end
46
+
47
+ # remember route for rails2.
48
+ def start_command_call(node)
49
+ case node.message.to_s
50
+ when "resources"
51
+ add_resources_routes(node)
52
+ when "resource"
53
+ add_resource_routes(node)
54
+ when "namespace"
55
+ # nothing to do
56
+ else
57
+ options = node.arguments.all.last
58
+ controller_name = options.hash_value("controller").to_s
59
+ action_name = options.hash_value("action").present? ? options.hash_value("action").to_s : "*"
60
+ @routes.add_route(current_namespaces, controller_name, action_name)
61
+ end
62
+ end
63
+
64
+ # remember the namespace.
65
+ def start_method_add_block(node)
66
+ if "namespace" == node.message.to_s
67
+ @namespaces << node.arguments.all.first.to_s
68
+ end
69
+ end
70
+
71
+ # end of namespace call.
72
+ def end_method_add_block(node)
73
+ if "namespace" == node.message.to_s
74
+ @namespaces.pop
75
+ end
76
+ end
77
+
78
+ [:resources, :resource].each do |route_name|
79
+ class_eval <<-EOF
80
+ def add_#{route_name}_routes(node)
81
+ resource_names = node.arguments.all.select { |argument| :symbol_literal == argument.sexp_type }
82
+ resource_names.each do |resource_name|
83
+ @resource_name = node.arguments.all.first.to_s
84
+ options = node.arguments.all.last
85
+ if options.hash_value("controller").present?
86
+ @resource_name = options.hash_value("controller").to_s
87
+ end
88
+ action_names = if options.hash_value("only").present?
89
+ get_#{route_name}_actions(options.hash_value("only").to_object)
90
+ elsif options.hash_value("except").present?
91
+ self.class.const_get(:#{route_name.upcase}_ACTIONS) - get_#{route_name}_actions(options.hash_value("except").to_object)
92
+ else
93
+ self.class.const_get(:#{route_name.upcase}_ACTIONS)
94
+ end
95
+ Array(action_names).each do |action_name|
96
+ @routes.add_route(current_namespaces, current_resource_name, action_name)
97
+ end
98
+
99
+ member_routes = options.hash_value("member")
100
+ if member_routes.present?
101
+ action_names = :array == member_routes.sexp_type ? member_routes.to_object : member_routes.hash_keys
102
+ action_names.each do |action_name|
103
+ @routes.add_route(current_namespaces, current_resource_name, action_name)
104
+ end
105
+ end
106
+
107
+ collection_routes = options.hash_value("collection")
108
+ if collection_routes.present?
109
+ action_names = :array == collection_routes.sexp_type ? collection_routes.to_object : collection_routes.hash_keys
110
+ action_names.each do |action_name|
111
+ @routes.add_route(current_namespaces, current_resource_name, action_name)
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ def get_#{route_name}_actions(action_names)
118
+ case action_names
119
+ when "all"
120
+ self.class.const_get(:#{route_name.upcase}_ACTIONS)
121
+ when "none"
122
+ []
123
+ else
124
+ action_names
125
+ end
126
+ end
127
+
128
+ def add_customize_routes
129
+ end
130
+ EOF
131
+ end
132
+
133
+ def current_namespaces
134
+ @namespaces.dup
135
+ end
136
+
137
+ def current_resource_name
138
+ @resource_name
139
+ end
140
+ end
141
+ end
142
+ end
@@ -5,17 +5,12 @@ module RailsBestPractices
5
5
  module Prepares
6
6
  # Remember the model attributes.
7
7
  class SchemaPrepare < Core::Check
8
+ interesting_nodes :command, :command_call
9
+ interesting_files SCHEMA_FILE
10
+
8
11
  # all attribute types
9
12
  ATTRIBUTE_TYPES = %w(integer float boolean string text date time datetime binary)
10
13
 
11
- def interesting_nodes
12
- [:command, :command_call]
13
- end
14
-
15
- def interesting_files
16
- SCHEMA_FILE
17
- end
18
-
19
14
  def initialize
20
15
  @model_attributes = Prepares.model_attributes
21
16
  end
@@ -27,3 +27,4 @@ require 'rails_best_practices/reviews/simplify_render_in_controllers_review'
27
27
  require 'rails_best_practices/reviews/remove_empty_helpers_review'
28
28
  require 'rails_best_practices/reviews/restrict_auto_generated_routes_review'
29
29
  require 'rails_best_practices/reviews/remove_unused_methods_in_models_review'
30
+ require 'rails_best_practices/reviews/remove_unused_methods_in_controllers_review'
@@ -16,18 +16,13 @@ module RailsBestPractices
16
16
  # and after these method calls, there is a save method call for that model,
17
17
  # then the model needs to add a virtual attribute.
18
18
  class AddModelVirtualAttributeReview < Review
19
+ interesting_nodes :def
20
+ interesting_files CONTROLLER_FILES
21
+
19
22
  def url
20
23
  "http://rails-bestpractices.com/posts/4-add-model-virtual-attribute"
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
  # check method define nodes to see if there are some attribute assignments that can use model virtual attribute instead in review process.
32
27
  #
33
28
  # it will check every attribute assignment nodes and call node of message :save or :save!, if
@@ -10,12 +10,12 @@ module RailsBestPractices
10
10
  # Implementation:
11
11
  #
12
12
  # Review process:
13
- # only check the command and command_calls nodes and at the end of program node in db/schema file,
13
+ # only check the command and command_calls nodes and at the end of review process,
14
14
  # if the subject of command node is "create_table", then remember the table names
15
15
  # if the subject of command_call node is "integer" and suffix with id, then remember it as foreign key
16
16
  # if the sujbect of command_call node is "string", the name of it is _type suffixed and there is an integer column _id suffixed, then remember it as polymorphic foreign key
17
17
  # if the subject of command node is "add_index", then remember the index columns
18
- # after all of these, at the end of program node
18
+ # after all of these, at the end of review process
19
19
  #
20
20
  # ActiveRecord::Schema.define(:version => 20101201111111) do
21
21
  # ......
@@ -24,16 +24,13 @@ module RailsBestPractices
24
24
  # if there are any foreign keys not existed in index columns,
25
25
  # then the foreign keys should add db index.
26
26
  class AlwaysAddDbIndexReview < Review
27
- def url
28
- "http://rails-bestpractices.com/posts/21-always-add-db-index"
29
- end
27
+ include Completeable
30
28
 
31
- def interesting_nodes
32
- [:command, :command_call, :program]
33
- end
29
+ interesting_nodes :command, :command_call
30
+ interesting_files SCHEMA_FILE
34
31
 
35
- def interesting_files
36
- SCHEMA_FILE
32
+ def url
33
+ "http://rails-bestpractices.com/posts/21-always-add-db-index"
37
34
  end
38
35
 
39
36
  def initialize
@@ -71,12 +68,12 @@ module RailsBestPractices
71
68
  end
72
69
  end
73
70
 
74
- # check at the end of program node.
71
+ # check at the end of review process.
75
72
  #
76
73
  # compare foreign keys and index columns,
77
74
  # if there are any foreign keys not existed in index columns,
78
75
  # then we should add db index for that foreign keys.
79
- def end_program(node)
76
+ def on_complete
80
77
  remove_only_type_foreign_keys
81
78
  @foreign_keys.each do |table, foreign_key|
82
79
  table_node = @table_nodes[table]
@@ -15,18 +15,13 @@ module RailsBestPractices
15
15
  # if the message of command node is "namespace" and the first argument is "bundler",
16
16
  # then it should use bundler's capistrano recipe.
17
17
  class DryBundlerInCapistranoReview < Review
18
+ interesting_nodes :command
19
+ interesting_files DEPLOY_FILES
20
+
18
21
  def url
19
22
  "http://rails-bestpractices.com/posts/51-dry-bundler-in-capistrano"
20
23
  end
21
24
 
22
- def interesting_nodes
23
- [:command]
24
- end
25
-
26
- def interesting_files
27
- /config\/deploy.rb/
28
- end
29
-
30
25
  # check call node to see if it is with message "namespace" and argument "bundler".
31
26
  def start_command(node)
32
27
  if "namespace" == node.message.to_s && "bundler" == node.arguments.all[0].to_s
@@ -21,18 +21,13 @@ module RailsBestPractices
21
21
  # and the subject is included in new variables,
22
22
  # then it should be isolated to db seed.
23
23
  class IsolateSeedDataReview < Review
24
+ interesting_nodes :call, :assign
25
+ interesting_files MIGRATION_FILES
26
+
24
27
  def url
25
28
  "http://rails-bestpractices.com/posts/20-isolating-seed-data"
26
29
  end
27
30
 
28
- def interesting_nodes
29
- [:call, :assign]
30
- end
31
-
32
- def interesting_files
33
- MIGRATION_FILES
34
- end
35
-
36
31
  def initialize
37
32
  super
38
33
  @new_variables = []
@@ -17,6 +17,8 @@ module RailsBestPractices
17
17
  # and there is a hash argument for finder,
18
18
  # then it should keep finders on its own model.
19
19
  class KeepFindersOnTheirOwnModelReview < Review
20
+ interesting_nodes :method_add_arg
21
+ interesting_files MODEL_FILES
20
22
 
21
23
  FINDERS = %w(find all first last)
22
24
 
@@ -24,14 +26,6 @@ module RailsBestPractices
24
26
  "http://rails-bestpractices.com/posts/13-keep-finders-on-their-own-model"
25
27
  end
26
28
 
27
- def interesting_nodes
28
- [:method_add_arg]
29
- end
30
-
31
- def interesting_files
32
- MODEL_FILES
33
- end
34
-
35
29
  # check all the call nodes to see if there is a finder for other model.
36
30
  #
37
31
  # if the call node is