rails_best_practices 0.5.6 → 0.6.1
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/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
|