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
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_best_practices (1.3.0)
4
+ rails_best_practices (1.4.0)
5
5
  activesupport
6
6
  colored
7
7
  erubis
data/README.md CHANGED
@@ -135,6 +135,7 @@ Now you can customize this configuration file, the default configuration is as f
135
135
  RemoveTabCheck: {}
136
136
  RestrictAutoGeneratedRoutesCheck: { }
137
137
  RemoveUnusedMethodsInModelsCheck: { except_methods: [] }
138
+ RemoveUnusedMethodsInControllersCheck: { except_methods: [] }
138
139
 
139
140
  You can remove or comment one review to disable it, and you can change the options.
140
141
 
@@ -147,8 +148,8 @@ Move code from Controller to Model
147
148
  2. Use model association
148
149
  3. Use scope access
149
150
  4. Add model virtual attribute
150
- 5. Replace Complex Creation with Factory Method
151
- 6. Move Model Logic into the Model
151
+ 5. Replace complex creation with factory method
152
+ 6. Move model logic into the Model
152
153
 
153
154
  RESTful Conventions
154
155
 
@@ -159,11 +160,11 @@ RESTful Conventions
159
160
 
160
161
  Model
161
162
 
162
- 1. Keep Finders on Their Own Model (rails2 only)
163
- 2. the Law of Demeter
164
- 3. Use Observer
165
- 4. Use Query Attribute
166
- 5. Remove Unused Methods In Models (Experiment, not available by default configuration)
163
+ 1. Keep finders on their own model (rails2 only)
164
+ 2. the law of demeter
165
+ 3. Use observer
166
+ 4. Use query attribute
167
+ 5. Remove unused methods in models
167
168
 
168
169
  Mailer
169
170
 
@@ -171,14 +172,15 @@ Mailer
171
172
 
172
173
  Migration
173
174
 
174
- 1. Isolating Seed Data
175
- 2. Always add DB index
176
- 3. Use Say with Time in Migrations
175
+ 1. Isolating seed data
176
+ 2. Always add db`index
177
+ 3. Use say with time in migrations
177
178
 
178
179
  Controller
179
180
 
180
181
  1. Use before_filter
181
182
  2. Simplify render in controllers
183
+ 3. Remove unused methods In controllers
182
184
 
183
185
  Helper
184
186
 
@@ -198,8 +200,8 @@ Deployment
198
200
 
199
201
  Other
200
202
 
201
- 1. Remove Trailing Whitespace
202
- 2. Remove Tab
203
+ 1. Remove trailing whitespace
204
+ 2. Remove tab
203
205
 
204
206
  Write Your Own Check List
205
207
  -------------------------
@@ -77,11 +77,11 @@ module RailsBestPractices
77
77
  @runner.color = !options['without-color']
78
78
 
79
79
  if @runner.checks.find { |check| check.is_a? Reviews::AlwaysAddDbIndexReview } &&
80
- !review_files.find { |file| file.index "db\/schema.rb" }
80
+ !parse_files.find { |file| file.index "db\/schema.rb" }
81
81
  plain_output("AlwaysAddDbIndexReview is disabled as there is no db/schema.rb file in your rails project.", 'blue')
82
82
  end
83
83
 
84
- @bar = ProgressBar.new('Source Codes', lexical_files.size + prepare_files.size + review_files.size)
84
+ @bar = ProgressBar.new('Source Codes', parse_files.size * 3)
85
85
  ["lexical", "prepare", "review"].each { |process| send(:process, process) }
86
86
  @runner.on_complete
87
87
  @bar.finish
@@ -102,29 +102,17 @@ module RailsBestPractices
102
102
  #
103
103
  # @param [String] process the process name, lexical, prepare or review.
104
104
  def process(process)
105
- files = send("#{process}_files")
106
- files.each do |file|
105
+ parse_files.each do |file|
107
106
  @runner.send("#{process}_file", file)
108
107
  @bar.inc unless @options['debug']
109
108
  end
110
109
  end
111
110
 
112
- # get all files for prepare process.
111
+ # get all files for parsing.
113
112
  #
114
- # @return [Array] all files for prepare process
115
- def prepare_files
116
- @prepare_files ||= begin
117
- ['app/models', 'app/mailers', 'db/schema.rb', 'app/controllers'].inject([]) { |files, name|
118
- files += expand_dirs_to_files(File.join(@path, name))
119
- }.compact
120
- end
121
- end
122
-
123
- # get all files for review process.
124
- #
125
- # @return [Array] all files for review process
126
- def review_files
127
- @review_files ||= begin
113
+ # @return [Array] all files for parsing
114
+ def parse_files
115
+ @parse_files ||= begin
128
116
  files = expand_dirs_to_files(@path)
129
117
  files = file_sort(files)
130
118
 
@@ -142,8 +130,6 @@ module RailsBestPractices
142
130
  end
143
131
  end
144
132
 
145
- alias :lexical_files :review_files
146
-
147
133
  # expand all files with extenstion rb, erb, haml and builder under the dirs
148
134
  #
149
135
  # @param [Array] dirs what directories to expand
@@ -11,6 +11,7 @@ require 'rails_best_practices/core/model_attributes'
11
11
  require 'rails_best_practices/core/mailers'
12
12
  require 'rails_best_practices/core/methods'
13
13
  require 'rails_best_practices/core/controllers'
14
+ require 'rails_best_practices/core/routes'
14
15
 
15
16
  require 'rails_best_practices/core_ext/sexp'
16
17
  require 'rails_best_practices/core_ext/enumerable'
@@ -4,24 +4,34 @@ module RailsBestPractices
4
4
  # A Check class that takes charge of checking the sexp.
5
5
  class Check
6
6
 
7
+ ALL_FILES = /.*/
7
8
  CONTROLLER_FILES = /controllers\/.*\.rb$/
8
9
  MIGRATION_FILES = /db\/migrate\/.*\.rb$/
9
10
  MODEL_FILES = /models\/.*\.rb$/
10
11
  MAILER_FILES = /models\/.*mailer\.rb$|mailers\/.*mailer\.rb/
11
12
  VIEW_FILES = /views\/.*\.(erb|haml)$/
12
13
  PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)$/
13
- ROUTE_FILES = /config\/routes(.*)?\.rb/
14
+ ROUTE_FILES = /config\/routes.*\.rb/
14
15
  SCHEMA_FILE = /db\/schema\.rb/
15
- HELPER_FILES = /helpers.*\.rb$/
16
+ HELPER_FILES = /helpers\/.*\.rb$/
17
+ DEPLOY_FILES = /config\/deploy.*\.rb/
16
18
 
17
- # default interesting nodes.
19
+ # interesting nodes that the check will parse.
18
20
  def interesting_nodes
19
- []
21
+ self.class.interesting_nodes
20
22
  end
21
23
 
22
- # default interesting files.
24
+ # interesting files that the check will parse.
23
25
  def interesting_files
24
- /.*/
26
+ self.class.interesting_files
27
+ end
28
+
29
+ # check if the check will need to parse the node file.
30
+ #
31
+ # @param [String] the file name of node.
32
+ # @return [Boolean] true if the check will need to parse the file.
33
+ def parse_file?(node_file)
34
+ interesting_files.any? { |pattern| node_file =~ pattern }
25
35
  end
26
36
 
27
37
  # delegate to start_### according to the sexp_type, like
@@ -92,6 +102,18 @@ module RailsBestPractices
92
102
  end
93
103
 
94
104
  class <<self
105
+ def interesting_nodes(*nodes)
106
+ @interesting_nodes ||= []
107
+ @interesting_nodes += nodes
108
+ @interesting_nodes.uniq
109
+ end
110
+
111
+ def interesting_files(*file_patterns)
112
+ @interesting_files ||= []
113
+ @interesting_files += file_patterns
114
+ @interesting_files.uniq
115
+ end
116
+
95
117
  # callbacks for start_xxx and end_xxx.
96
118
  def callbacks
97
119
  @callbacks ||= {}
@@ -111,6 +133,8 @@ module RailsBestPractices
111
133
  module Klassable
112
134
  def self.included(base)
113
135
  base.class_eval do
136
+ interesting_nodes :module, :class
137
+
114
138
  # remember module name
115
139
  add_callback "start_module" do |node|
116
140
  modules << node.module_name.to_s
@@ -153,6 +177,9 @@ module RailsBestPractices
153
177
  module Completeable
154
178
  def self.included(base)
155
179
  base.class_eval do
180
+ interesting_nodes :class
181
+ interesting_files /rails_best_practices\.complete/
182
+
156
183
  add_callback "end_class" do |node|
157
184
  if "RailsBestPractices::Complete" == node.class_name.to_s
158
185
  on_complete
@@ -162,10 +189,133 @@ module RailsBestPractices
162
189
  end
163
190
  end
164
191
 
192
+ # Helper to add callbacks to mark the methods are used.
193
+ module Callable
194
+ def self.included(base)
195
+ base.class_eval do
196
+ interesting_nodes :call, :fcall, :var_ref, :command_call, :command, :alias, :bare_assoc_hash, :method_add_arg
197
+
198
+ # remembe the message of call node.
199
+ add_callback "start_call" do |node|
200
+ mark_used(node.message)
201
+ end
202
+
203
+ # remembe the message of fcall node.
204
+ add_callback "start_fcall" do |node|
205
+ mark_used(node.message)
206
+ end
207
+
208
+ # remembe name of var_ref node.
209
+ add_callback "start_var_ref" do |node|
210
+ mark_used(node)
211
+ end
212
+
213
+ # remember the message of command node.
214
+ # remember the argument of alias_method and alias_method_chain as well.
215
+ add_callback "start_command" do |node|
216
+ case node.message.to_s
217
+ when "named_scope", "scope"
218
+ # nothing
219
+ when "alias_method"
220
+ mark_used(node.arguments.all[1])
221
+ when "alias_method_chain"
222
+ method, feature = *node.arguments.all.map(&:to_s)
223
+ call_method("#{method}_with_#{feature}")
224
+ else
225
+ mark_used(node.message)
226
+ node.arguments.all.each { |argument| mark_used(argument) }
227
+ end
228
+ end
229
+
230
+ # remembe the message of command call node.
231
+ add_callback "start_command_call" do |node|
232
+ mark_used(node.message)
233
+ end
234
+
235
+ # remember the old method of alias node.
236
+ add_callback "start_alias" do |node|
237
+ mark_used(node.old_method)
238
+ end
239
+
240
+ # remember hash values for hash key "methods".
241
+ #
242
+ # def to_xml(options = {})
243
+ # super options.merge(:exclude => :visible, :methods => [:is_discussion_conversation])
244
+ # end
245
+ add_callback "start_bare_assoc_hash" do |node|
246
+ if node.hash_keys.include? "methods"
247
+ mark_used(node.hash_value("methods"))
248
+ end
249
+ end
250
+
251
+ # remember the first argument for try and send method.
252
+ add_callback "start_method_add_arg" do |node|
253
+ case node.message.to_s
254
+ when "try"
255
+ mark_used(node.arguments.all.first)
256
+ when "send"
257
+ if [:symbol_literal, :string_literal].include?(node.arguments.all[0].sexp_type)
258
+ mark_used(node.arguments.all.first)
259
+ end
260
+ else
261
+ # nothing
262
+ end
263
+ end
264
+
265
+ private
266
+ def mark_used(method_node)
267
+ if :bare_assoc_hash == method_node.sexp_type
268
+ method_node.hash_values.each { |value_node| mark_used(value_node) }
269
+ elsif :array == method_node.sexp_type
270
+ method_node.array_values.each { |value_node| mark_used(value_node) }
271
+ else
272
+ method_name = method_node.to_s
273
+ end
274
+ call_method(method_name)
275
+ end
276
+
277
+ def call_method(method_name, class_name=current_class_name)
278
+ if methods.has_method?(class_name, method_name)
279
+ methods.get_method(class_name, method_name).mark_used
280
+ end
281
+ methods.mark_parent_class_method_used(class_name, method_name)
282
+ methods.mark_subclasses_method_used(class_name, method_name)
283
+ methods.possible_public_used(method_name)
284
+ end
285
+ end
286
+ end
287
+ end
288
+
289
+ # Helper to indicate if the controller is inherited from InheritedResources.
290
+ module InheritedResourcesable
291
+ def self.included(base)
292
+ base.class_eval do
293
+ interesting_nodes :class, :var_ref
294
+ interesting_files CONTROLLER_FILES
295
+
296
+ # check if the controller is inherit from InheritedResources::Base.
297
+ add_callback "start_class" do |node|
298
+ if "InheritedResources::Base" == current_extend_class_name
299
+ @inherited_resources = true
300
+ end
301
+ end
302
+
303
+ # check if there is a DSL call inherit_resources.
304
+ add_callback "start_var_ref" do |node|
305
+ if "inherit_resources" == node.to_s
306
+ @inherited_resources = true
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end
312
+
165
313
  # Helper to parse the access control.
166
314
  module Accessable
167
315
  def self.included(base)
168
316
  base.class_eval do
317
+ interesting_nodes :var_ref, :class, :module
318
+
169
319
  # remember the current access control for methods.
170
320
  add_callback "start_var_ref" do |node|
171
321
  if %w(public protected private).include? node.to_s
@@ -53,7 +53,7 @@ module RailsBestPractices
53
53
  checks = @#{process}_checks[node.sexp_type] # checks = @review_checks[node.sexp_type]
54
54
  if checks # if checks
55
55
  checks.each { |check| # checks.each { |check|
56
- if node.file =~ check.interesting_files # if node.file =~ check.interesting_files
56
+ if check.parse_file?(node.file) # if check.parse_file?(node.file)
57
57
  check.node_start(node) # check.node_start(node)
58
58
  end # end
59
59
  } # }
@@ -64,7 +64,7 @@ module RailsBestPractices
64
64
  } # }
65
65
  if checks # if checks
66
66
  checks.each { |check| # checks.each { |check|
67
- if node.file =~ check.interesting_files # if node.file =~ check.interesting_files
67
+ if check.parse_file?(node.file) # if check.parse_file?(node.file)
68
68
  check.node_end(node) # check.node_end(node)
69
69
  end # end
70
70
  } # }
@@ -16,6 +16,7 @@ module RailsBestPractices
16
16
  # @param [String] access control, public, protected or private
17
17
  def add_method(class_name, method_name, meta={}, access_control="public")
18
18
  return if class_name == ""
19
+ return if has_method?(class_name, method_name)
19
20
  methods(class_name) << Method.new(class_name, method_name, access_control, meta)
20
21
  if access_control == "public"
21
22
  @possible_methods[method_name] = false
@@ -18,6 +18,16 @@ module RailsBestPractices
18
18
  self
19
19
  end
20
20
 
21
+ # false
22
+ def present?
23
+ false
24
+ end
25
+
26
+ # true
27
+ def blank?
28
+ true
29
+ end
30
+
21
31
  # return self.
22
32
  def method_missing(method_sym, *arguments, &block)
23
33
  self
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+ module RailsBestPractices
3
+ module Core
4
+ class Routes < Array
5
+ # add a route.
6
+ #
7
+ # @param [Array] namesapces
8
+ # @param [String] controller name
9
+ # @param [String] action name
10
+ def add_route(namespaces, controller_name, action_name)
11
+ self << Route.new(namespaces, controller_name, action_name)
12
+ end
13
+ end
14
+
15
+ class Route
16
+ attr_reader :namespaces, :controller_name, :action_name
17
+
18
+ def initialize(namespaces, controller_name, action_name)
19
+ @namespaces = namespaces
20
+ @controller_name = controller_name
21
+ @action_name = action_name
22
+ end
23
+
24
+ def controller_name_with_namespaces
25
+ namespaces.map { |namespace| "#{namespace.camelize}::" }.join("") + "#{controller_name.camelize}Controller"
26
+ end
27
+
28
+ def to_s
29
+ "#{controller_name_with_namespaces}\##{action_name}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -4,6 +4,8 @@ require 'ripper'
4
4
  require 'erubis'
5
5
  require 'yaml'
6
6
  require 'active_support/inflector'
7
+ require 'active_support/core_ext/object/blank'
8
+ require 'active_support/core_ext/object/try'
7
9
 
8
10
  module RailsBestPractices
9
11
  module Core
@@ -182,7 +184,7 @@ module RailsBestPractices
182
184
 
183
185
  # load all prepares.
184
186
  def load_prepares
185
- [Prepares::ModelPrepare.new, Prepares::MailerPrepare.new, Prepares::SchemaPrepare.new, Prepares::ControllerPrepare.new]
187
+ Prepares.constants.map { |prepare| Prepares.const_get(prepare).new }
186
188
  end
187
189
 
188
190
  # load all reviews according to configuration.