rails_best_practices 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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