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.
- data/Gemfile.lock +1 -1
- data/README.md +14 -12
- data/lib/rails_best_practices.rb +7 -21
- data/lib/rails_best_practices/core.rb +1 -0
- data/lib/rails_best_practices/core/check.rb +156 -6
- data/lib/rails_best_practices/core/checking_visitor.rb +2 -2
- data/lib/rails_best_practices/core/methods.rb +1 -0
- data/lib/rails_best_practices/core/nil.rb +10 -0
- data/lib/rails_best_practices/core/routes.rb +33 -0
- data/lib/rails_best_practices/core/runner.rb +3 -1
- data/lib/rails_best_practices/core_ext/sexp.rb +11 -0
- data/lib/rails_best_practices/prepares.rb +5 -2
- data/lib/rails_best_practices/prepares/controller_prepare.rb +8 -14
- data/lib/rails_best_practices/prepares/mailer_prepare.rb +2 -7
- data/lib/rails_best_practices/prepares/model_prepare.rb +3 -8
- data/lib/rails_best_practices/prepares/route_prepare.rb +142 -0
- data/lib/rails_best_practices/prepares/schema_prepare.rb +3 -8
- data/lib/rails_best_practices/reviews.rb +1 -0
- data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +3 -8
- data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +9 -12
- data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +3 -8
- data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +3 -8
- data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +2 -8
- data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +3 -4
- data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +2 -8
- data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +3 -8
- data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +3 -8
- data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +2 -8
- data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +3 -8
- data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +3 -8
- data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +3 -8
- data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +3 -9
- data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +3 -8
- data/lib/rails_best_practices/reviews/remove_unused_methods_in_controllers_review.rb +57 -0
- data/lib/rails_best_practices/reviews/remove_unused_methods_in_models_review.rb +6 -92
- data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +3 -8
- data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +3 -8
- data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +3 -8
- data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +3 -8
- data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +3 -8
- data/lib/rails_best_practices/reviews/use_before_filter_review.rb +3 -8
- data/lib/rails_best_practices/reviews/use_model_association_review.rb +3 -8
- data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +3 -8
- data/lib/rails_best_practices/reviews/use_observer_review.rb +3 -8
- data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +2 -4
- data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +2 -8
- data/lib/rails_best_practices/reviews/use_scope_access_review.rb +3 -8
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.yml +1 -0
- data/spec/rails_best_practices/core/check_spec.rb +2 -2
- data/spec/rails_best_practices/core/checking_visitor_spec.rb +12 -32
- data/spec/rails_best_practices/core/nil_spec.rb +12 -0
- data/spec/rails_best_practices/core/routes_spec.rb +10 -0
- data/spec/rails_best_practices/core_ext/sexp_spec.rb +14 -0
- data/spec/rails_best_practices/prepares/route_prepare_spec.rb +502 -0
- data/spec/rails_best_practices/reviews/always_add_db_index_review_spec.rb +14 -1
- data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +20 -4
- data/spec/rails_best_practices/reviews/remove_unused_methods_in_controllers_review_spec.rb +120 -0
- data/spec/rails_best_practices/reviews/remove_unused_methods_in_models_review_spec.rb +1 -1
- metadata +113 -123
data/Gemfile.lock
CHANGED
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
|
151
|
-
6. Move
|
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
|
163
|
-
2. the
|
164
|
-
3. Use
|
165
|
-
4. Use
|
166
|
-
5. Remove
|
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
|
175
|
-
2. Always add
|
176
|
-
3. Use
|
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
|
202
|
-
2. Remove
|
203
|
+
1. Remove trailing whitespace
|
204
|
+
2. Remove tab
|
203
205
|
|
204
206
|
Write Your Own Check List
|
205
207
|
-------------------------
|
data/lib/rails_best_practices.rb
CHANGED
@@ -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
|
-
!
|
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',
|
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
|
-
|
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
|
111
|
+
# get all files for parsing.
|
113
112
|
#
|
114
|
-
# @return [Array] all files for
|
115
|
-
def
|
116
|
-
@
|
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
|
14
|
+
ROUTE_FILES = /config\/routes.*\.rb/
|
14
15
|
SCHEMA_FILE = /db\/schema\.rb/
|
15
|
-
HELPER_FILES = /helpers
|
16
|
+
HELPER_FILES = /helpers\/.*\.rb$/
|
17
|
+
DEPLOY_FILES = /config\/deploy.*\.rb/
|
16
18
|
|
17
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
@@ -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
|
-
|
187
|
+
Prepares.constants.map { |prepare| Prepares.const_get(prepare).new }
|
186
188
|
end
|
187
189
|
|
188
190
|
# load all reviews according to configuration.
|