rails_best_practices 0.5.6 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +161 -0
- data/lib/rails_best_practices.rb +158 -20
- data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +108 -34
- data/lib/rails_best_practices/checks/always_add_db_index_check.rb +148 -29
- data/lib/rails_best_practices/checks/check.rb +178 -75
- data/lib/rails_best_practices/checks/dry_bundler_in_capistrano_check.rb +26 -5
- data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +66 -15
- data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +53 -12
- data/lib/rails_best_practices/checks/law_of_demeter_check.rb +59 -30
- data/lib/rails_best_practices/checks/move_code_into_controller_check.rb +35 -15
- data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +56 -12
- data/lib/rails_best_practices/checks/move_code_into_model_check.rb +30 -32
- data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +45 -15
- data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +31 -27
- data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +99 -38
- data/lib/rails_best_practices/checks/not_use_default_route_check.rb +43 -12
- data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +140 -28
- data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +44 -30
- data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +18 -7
- data/lib/rails_best_practices/checks/use_before_filter_check.rb +88 -18
- data/lib/rails_best_practices/checks/use_model_association_check.rb +61 -22
- data/lib/rails_best_practices/checks/use_observer_check.rb +125 -23
- data/lib/rails_best_practices/checks/use_query_attribute_check.rb +75 -47
- data/lib/rails_best_practices/checks/use_say_with_time_in_migrations_check.rb +59 -10
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +78 -23
- data/lib/rails_best_practices/command.rb +19 -34
- data/lib/rails_best_practices/core.rb +4 -2
- data/lib/rails_best_practices/core/checking_visitor.rb +49 -19
- data/lib/rails_best_practices/core/error.rb +5 -2
- data/lib/rails_best_practices/core/runner.rb +79 -55
- data/lib/rails_best_practices/core/visitable_sexp.rb +325 -55
- data/lib/rails_best_practices/{core/core_ext.rb → core_ext/enumerable.rb} +3 -6
- data/lib/rails_best_practices/core_ext/nil_class.rb +8 -0
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.yml +2 -2
- metadata +8 -7
- data/README.textile +0 -150
@@ -5,19 +5,40 @@ module RailsBestPractices
|
|
5
5
|
module Checks
|
6
6
|
# Check config/deploy.rb file to make sure using the bundler's capistrano recipe.
|
7
7
|
#
|
8
|
-
#
|
9
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/51-dry-bundler-in-capistrano
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# only check the call nodes to see if there is bundler namespace in config/deploy.rb file,
|
17
|
+
#
|
18
|
+
# if the message of call node is :namespace and the arguments of the call node is :bundler,
|
19
|
+
# then it should use bundler's capistrano recipe.
|
10
20
|
class DryBundlerInCapistranoCheck < Check
|
11
21
|
|
12
|
-
def
|
22
|
+
def interesting_review_nodes
|
13
23
|
[:call]
|
14
24
|
end
|
15
25
|
|
16
|
-
def
|
26
|
+
def interesting_review_files
|
17
27
|
/config\/deploy.rb/
|
18
28
|
end
|
19
29
|
|
20
|
-
|
30
|
+
# check call node in review process to see if it is with message :namespace and arguments :bundler.
|
31
|
+
#
|
32
|
+
# the ruby code is
|
33
|
+
#
|
34
|
+
# namespace :bundler do
|
35
|
+
# ...
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# then the call node is as follows
|
39
|
+
#
|
40
|
+
# s(:call, nil, :namespace, s(:arglist, s(:lit, :bundler)))
|
41
|
+
def review_start_call(node)
|
21
42
|
if :namespace == node.message and equal?(node.arguments[1], "bundler")
|
22
43
|
add_error "dry bundler in capistrano"
|
23
44
|
end
|
@@ -3,16 +3,33 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
#
|
6
|
+
# Make sure not to insert data in migration, move them to seed file.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/20-isolating-seed-data.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# 1. check all local assignment and instance assignment nodes,
|
17
|
+
# if the right value is a call node with message :new,
|
18
|
+
# then remember their left value as new variables.
|
19
|
+
#
|
20
|
+
# 2. check all call nodes,
|
21
|
+
# if the message is :create or :create!,
|
22
|
+
# then it should be isolated to db seed.
|
23
|
+
# if the message is :save or :save!,
|
24
|
+
# and the subject is included in new variables,
|
25
|
+
# then it should be isolated to db seed.
|
9
26
|
class IsolateSeedDataCheck < Check
|
10
27
|
|
11
|
-
def
|
28
|
+
def interesting_review_nodes
|
12
29
|
[:call, :lasgn, :iasgn]
|
13
30
|
end
|
14
31
|
|
15
|
-
def
|
32
|
+
def interesting_review_files
|
16
33
|
MIGRATION_FILES
|
17
34
|
end
|
18
35
|
|
@@ -21,27 +38,61 @@ module RailsBestPractices
|
|
21
38
|
@new_variables = []
|
22
39
|
end
|
23
40
|
|
24
|
-
|
41
|
+
# check local assignment node in review process.
|
42
|
+
#
|
43
|
+
# if the right value of the node is a call node with :new message,
|
44
|
+
# then remember it as new variables (@new_variables).
|
45
|
+
def review_start_lasgn(node)
|
46
|
+
remember_new_variable(node)
|
47
|
+
end
|
48
|
+
|
49
|
+
# check instance assignment node in review process.
|
50
|
+
#
|
51
|
+
# if the right value of the node is a call node with :new message,
|
52
|
+
# then remember it as new variables (@new_variables).
|
53
|
+
def review_start_iasgn(node)
|
54
|
+
remember_new_variable(node)
|
55
|
+
end
|
56
|
+
|
57
|
+
# check the call node in review process.
|
58
|
+
#
|
59
|
+
# if the message of the call node is :create or :create!,
|
60
|
+
# then you should isolate it to seed data.
|
61
|
+
#
|
62
|
+
# if the message of the call node is :save or :save!,
|
63
|
+
# and the subject of the call node is included in @new_variables,
|
64
|
+
# then you should isolate it to seed data.
|
65
|
+
def review_start_call(node)
|
25
66
|
if [:create, :create!].include? node.message
|
26
67
|
add_error("isolate seed data")
|
27
|
-
elsif [:lasgn, :iasgn].include? node.node_type
|
28
|
-
remember_new_variable(node)
|
29
68
|
elsif [:save, :save!].include? node.message
|
30
69
|
add_error("isolate seed data") if new_record?(node)
|
31
70
|
end
|
32
71
|
end
|
33
72
|
|
34
73
|
private
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
74
|
+
# check local assignment or instance assignment node,
|
75
|
+
# if the right vavlue is a call node with message :new,
|
76
|
+
# then remember the left value as new variable.
|
77
|
+
#
|
78
|
+
# if the local variable node is
|
79
|
+
#
|
80
|
+
# s(:lasgn, :role, s(:call, s(:const, :Role), :new, s(:arglist, s(:hash, s(:lit, :name), s(:lvar, :name)))))
|
81
|
+
#
|
82
|
+
# then the new variables (@new_variables) is
|
83
|
+
#
|
84
|
+
# ["role"]
|
85
|
+
def remember_new_variable(node)
|
86
|
+
right_value = node.right_value
|
87
|
+
if :call == right_value.node_type && :new == right_value.message
|
88
|
+
@new_variables << node.left_value.to_s
|
89
|
+
end
|
39
90
|
end
|
40
|
-
end
|
41
91
|
|
42
|
-
|
43
|
-
|
44
|
-
|
92
|
+
# see if the subject of the call node is included in the @new_varaibles.
|
93
|
+
def new_record?(node)
|
94
|
+
@new_variables.include? node.subject.to_s
|
95
|
+
end
|
45
96
|
end
|
46
97
|
end
|
47
98
|
end
|
@@ -3,28 +3,69 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check
|
6
|
+
# Check model files to ake sure finders are on their own model.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/13-keep-finders-on-their-own-model.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all call nodes in model files.
|
17
|
+
#
|
18
|
+
# if the call node is a finder (find, all, first or last),
|
19
|
+
# and the it calls the other model,
|
20
|
+
# and there is a hash argument for finder,
|
21
|
+
# then it should keep finders on its own model.
|
9
22
|
class KeepFindersOnTheirOwnModelCheck < Check
|
10
|
-
|
11
|
-
|
23
|
+
|
24
|
+
FINDERS = [:find, :all, :first, :last]
|
25
|
+
|
26
|
+
def interesting_review_nodes
|
12
27
|
[:call]
|
13
28
|
end
|
14
29
|
|
15
|
-
def
|
16
|
-
|
30
|
+
def interesting_review_files
|
31
|
+
MODEL_FILES
|
17
32
|
end
|
18
33
|
|
19
|
-
|
20
|
-
|
34
|
+
# check all the call nodes to see if there is a finder for other model.
|
35
|
+
#
|
36
|
+
# if the call node is
|
37
|
+
#
|
38
|
+
# 1. the message of call node is one of the :find, :all, :first or :last
|
39
|
+
# 2. the subject of call node is also a call node (it's the other model)
|
40
|
+
# 3. the any of its arguments is a hash (complex finder)
|
41
|
+
#
|
42
|
+
# then it should keep finders on its own model.
|
43
|
+
def review_start_call(node)
|
44
|
+
add_error "keep finders on their own model" if other_finder?(node)
|
21
45
|
end
|
22
46
|
|
23
47
|
private
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
48
|
+
# check if the call node is the finder of other model.
|
49
|
+
#
|
50
|
+
# the message of the node should be one of :find, :all, :first or :last,
|
51
|
+
# and the subject of the node should be with message :call (this is the other model),
|
52
|
+
# and any of its arguments is a hash, like
|
53
|
+
#
|
54
|
+
# s(:call,
|
55
|
+
# s(:call, s(:self), :comment, s(:arglist)),
|
56
|
+
# :find,
|
57
|
+
# s(:arglist, s(:lit, :all),
|
58
|
+
# s(:hash,
|
59
|
+
# s(:lit, :conditions),
|
60
|
+
# s(:hash, s(:lit, :is_spam), s(:false)),
|
61
|
+
# s(:lit, :limit),
|
62
|
+
# s(:lit, 10)
|
63
|
+
# )
|
64
|
+
# )
|
65
|
+
# )
|
66
|
+
def other_finder?(node)
|
67
|
+
FINDERS.include?(node.message) && :call == node.subject.node_type && node.arguments.children.any? { |node| :hash == node.node_type }
|
68
|
+
end
|
28
69
|
end
|
29
70
|
end
|
30
71
|
end
|
@@ -3,47 +3,76 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check to make sure not avoid the law of demeter.
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
6
|
+
# Check to make sure not to avoid the law of demeter.
|
7
|
+
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/15-the-law-of-demeter.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# only check all model files to save model names and association names.
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# check all method calls to see if there is method call to the association object.
|
17
|
+
# if there is a call node whose subject is an object of model (compare by name),
|
18
|
+
# and whose message is an association of that model (also compare by name),
|
19
|
+
# and outer the call node, it is also a call node,
|
20
|
+
# then it violate the law of demeter.
|
11
21
|
class LawOfDemeterCheck < Check
|
12
|
-
|
13
|
-
def interesting_nodes
|
14
|
-
[:call, :class]
|
15
|
-
end
|
16
22
|
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prepare_model_associations
|
24
|
+
|
25
|
+
def interesting_review_nodes
|
26
|
+
[:call]
|
20
27
|
end
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
# check the call node in review process,
|
30
|
+
#
|
31
|
+
# if the subject of the call node is also a call node,
|
32
|
+
# and the subject of the subject call node matchs one of the class names,
|
33
|
+
# and the message of the subject call node matchs one of the association name with the class name, like
|
34
|
+
#
|
35
|
+
# s(:call,
|
36
|
+
# s(:call, s(:ivar, :@invoice), :user, s(:arglist)),
|
37
|
+
# :name,
|
38
|
+
# s(:arglist)
|
39
|
+
# )
|
40
|
+
#
|
41
|
+
# then it violates the law of demeter.
|
42
|
+
def review_start_call(node)
|
43
|
+
if [:lvar, :ivar].include?(node.subject.subject.node_type) && need_delegate?(node)
|
44
|
+
add_error "law of demeter"
|
27
45
|
end
|
28
46
|
end
|
29
47
|
|
30
48
|
private
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
49
|
+
# check if the call node can use delegate to avoid violating law of demeter.
|
50
|
+
#
|
51
|
+
# if the subject of subject of the call node matchs any in model names,
|
52
|
+
# and the message of subject of the call node matchs any in association names,
|
53
|
+
# then it needs delegate.
|
54
|
+
#
|
55
|
+
# e.g. the source code is
|
56
|
+
#
|
57
|
+
# @invoic.user.name
|
58
|
+
#
|
59
|
+
# then the call node is
|
60
|
+
#
|
61
|
+
# s(:call, s(:call, s(:ivar, :@invoice), :user, s(:arglist)), :name, s(:arglist))
|
62
|
+
#
|
63
|
+
# as you see the subject of subject of the call node is [:ivar, @invoice],
|
64
|
+
# and the message of subject of the call node is :user
|
65
|
+
def need_delegate?(node)
|
66
|
+
@associations.each do |class_name, associations|
|
67
|
+
return true if equal?(node.subject.subject, class_name) && associations.find { |association| equal?(association, node.subject.message) }
|
68
|
+
end
|
69
|
+
false
|
38
70
|
end
|
39
|
-
end
|
40
71
|
|
41
|
-
|
42
|
-
|
43
|
-
|
72
|
+
# only check belongs_to and has_one association.
|
73
|
+
def association_methods
|
74
|
+
[:belongs_to, :has_one]
|
44
75
|
end
|
45
|
-
false
|
46
|
-
end
|
47
76
|
end
|
48
77
|
end
|
49
78
|
end
|
@@ -3,30 +3,50 @@ require 'rails_best_practices/checks/check'
|
|
3
3
|
|
4
4
|
module RailsBestPractices
|
5
5
|
module Checks
|
6
|
-
# Check a view file to make sure there is no finder.
|
6
|
+
# Check a view file to make sure there is no finder, finder should be moved to controller.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/24-move-code-into-controller.
|
9
|
+
#
|
10
|
+
# Implementation:
|
11
|
+
#
|
12
|
+
# Prepare process:
|
13
|
+
# none
|
14
|
+
#
|
15
|
+
# Review process:
|
16
|
+
# only check all view files to see if there are finders, then the finders should be moved to controller.
|
9
17
|
class MoveCodeIntoControllerCheck < Check
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def
|
18
|
+
|
19
|
+
FINDERS = [:find, :all, :first, :last]
|
20
|
+
|
21
|
+
def interesting_review_nodes
|
14
22
|
[:call]
|
15
23
|
end
|
16
|
-
|
17
|
-
def
|
24
|
+
|
25
|
+
def interesting_review_files
|
18
26
|
VIEW_FILES
|
19
27
|
end
|
20
|
-
|
21
|
-
|
28
|
+
|
29
|
+
# check call nodes in review process.
|
30
|
+
#
|
31
|
+
# if the subject of the call node is a constant,
|
32
|
+
# and the message of the call node is one of the :find, :all, :first and :last,
|
33
|
+
# then it is a finder and should be moved to controller.
|
34
|
+
def review_start_call(node)
|
22
35
|
add_error "move code into controller" if finder?(node)
|
23
36
|
end
|
24
|
-
|
25
|
-
private
|
26
37
|
|
27
|
-
|
28
|
-
node
|
29
|
-
|
38
|
+
private
|
39
|
+
# check if the node is a finder call node.
|
40
|
+
# e.g. the following call node is a finder
|
41
|
+
#
|
42
|
+
# s(:call,
|
43
|
+
# s(:const, :Post),
|
44
|
+
# :find,
|
45
|
+
# s(:arglist, s(:lit, :all))
|
46
|
+
# )
|
47
|
+
def finder?(node)
|
48
|
+
:const == node.subject.node_type && FINDERS.include?(node.message)
|
49
|
+
end
|
30
50
|
end
|
31
51
|
end
|
32
52
|
end
|
@@ -5,14 +5,29 @@ module RailsBestPractices
|
|
5
5
|
module Checks
|
6
6
|
# Check a view file to make sure there is no complex options_for_select message call.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# See the best practice details here http://rails-bestpractices.com/posts/26-move-code-into-helper.
|
9
|
+
#
|
10
|
+
# TODO: we need a better soluation, any suggestion?
|
11
|
+
#
|
12
|
+
# Implementation:
|
13
|
+
#
|
14
|
+
# Prepare process:
|
15
|
+
# none
|
16
|
+
#
|
17
|
+
# Review process:
|
18
|
+
# check al method calls to see if there is a complex options_for_select helper.
|
19
|
+
#
|
20
|
+
# if the message of the call node is options_for_select,
|
21
|
+
# and the first argument of the call node is array,
|
22
|
+
# and the size of the array is greater than array_count defined,
|
23
|
+
# then the options_for_select method should be moved into helper.
|
9
24
|
class MoveCodeIntoHelperCheck < Check
|
10
|
-
|
11
|
-
def
|
25
|
+
|
26
|
+
def interesting_review_nodes
|
12
27
|
[:call]
|
13
28
|
end
|
14
|
-
|
15
|
-
def
|
29
|
+
|
30
|
+
def interesting_review_files
|
16
31
|
VIEW_FILES
|
17
32
|
end
|
18
33
|
|
@@ -21,15 +36,44 @@ module RailsBestPractices
|
|
21
36
|
@array_count = options['array_count'] || 3
|
22
37
|
end
|
23
38
|
|
24
|
-
|
39
|
+
# check call node with message options_for_select (sorry we only check options_for_select helper now).
|
40
|
+
#
|
41
|
+
# if the first argument of options_for_select method call is an array,
|
42
|
+
# and the size of the array is more than @array_count defined,
|
43
|
+
# then the options_for_select helper should be moved into helper.
|
44
|
+
def review_start_call(node)
|
25
45
|
add_error "move code into helper (array_count >= #{@array_count})" if complex_select_options?(node)
|
26
46
|
end
|
27
|
-
|
47
|
+
|
28
48
|
private
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
49
|
+
# check if the arguments of options_for_select are complex.
|
50
|
+
#
|
51
|
+
# if the first argument is an array,
|
52
|
+
# and the size of array is greater than @array_count you defined,
|
53
|
+
# then it is complext, e.g.
|
54
|
+
#
|
55
|
+
# s(:call, nil, :options_for_select,
|
56
|
+
# s(:arglist,
|
57
|
+
# s(:array,
|
58
|
+
# s(:array,
|
59
|
+
# s(:call, nil, :t, s(:arglist, s(:lit, :draft))),
|
60
|
+
# s(:str, "draft")
|
61
|
+
# ),
|
62
|
+
# s(:array,
|
63
|
+
# s(:call, nil, :t, s(:arglist, s(:lit, :published))),
|
64
|
+
# s(:str, "published")
|
65
|
+
# )
|
66
|
+
# ),
|
67
|
+
# s(:call,
|
68
|
+
# s(:call, nil, :params, s(:arglist)),
|
69
|
+
# :[],
|
70
|
+
# s(:arglist, s(:lit, :default_state))
|
71
|
+
# )
|
72
|
+
# )
|
73
|
+
# )
|
74
|
+
def complex_select_options?(node)
|
75
|
+
:options_for_select == node.message and :array == node.arguments[1].node_type and node.arguments[1].size > @array_count
|
76
|
+
end
|
33
77
|
end
|
34
78
|
end
|
35
|
-
end
|
79
|
+
end
|