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