rails_best_practices 0.10.1 → 1.0.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/README.md +20 -0
- data/assets/result.html.haml +1 -1
- data/lib/rails_best_practices.rb +5 -3
- data/lib/rails_best_practices/core.rb +1 -1
- data/lib/rails_best_practices/core/check.rb +9 -12
- data/lib/rails_best_practices/core/checking_visitor.rb +9 -6
- data/lib/rails_best_practices/core/model_associations.rb +1 -1
- data/lib/rails_best_practices/core/nil.rb +5 -1
- data/lib/rails_best_practices/core/runner.rb +5 -5
- data/lib/rails_best_practices/core_ext/sexp.rb +688 -0
- data/lib/rails_best_practices/prepares/mailer_prepare.rb +4 -5
- data/lib/rails_best_practices/prepares/model_prepare.rb +16 -26
- data/lib/rails_best_practices/prepares/schema_prepare.rb +11 -17
- data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +24 -75
- data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +39 -113
- data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +6 -16
- data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +16 -32
- data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +11 -20
- data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +7 -28
- data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +16 -14
- data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +10 -28
- data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +12 -11
- data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +13 -24
- data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +9 -9
- data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +24 -68
- data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +15 -22
- data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +31 -91
- data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +4 -2
- data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +20 -18
- data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +5 -3
- data/lib/rails_best_practices/reviews/review.rb +8 -37
- data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +10 -6
- data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +9 -6
- data/lib/rails_best_practices/reviews/use_before_filter_review.rb +14 -72
- data/lib/rails_best_practices/reviews/use_model_association_review.rb +19 -31
- data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +5 -5
- data/lib/rails_best_practices/reviews/use_observer_review.rb +22 -40
- data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +34 -39
- data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +14 -38
- data/lib/rails_best_practices/reviews/use_scope_access_review.rb +13 -44
- data/lib/rails_best_practices/version.rb +1 -1
- data/spec/rails_best_practices/core/check_spec.rb +5 -5
- data/spec/rails_best_practices/core/checking_visitor_spec.rb +4 -4
- data/spec/rails_best_practices/core/model_associations_spec.rb +4 -4
- data/spec/rails_best_practices/core/nil_spec.rb +7 -1
- data/spec/rails_best_practices/core_ext/sexp_spec.rb +430 -0
- data/spec/rails_best_practices/prepares/model_prepare_spec.rb +12 -12
- data/spec/rails_best_practices/prepares/schema_prepare_spec.rb +6 -6
- data/spec/rails_best_practices/reviews/move_code_into_controller_review_spec.rb +14 -2
- data/spec/rails_best_practices/reviews/move_code_into_helper_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +3 -3
- data/spec/rails_best_practices/reviews/not_use_default_route_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +15 -1
- data/spec/rails_best_practices/reviews/simplify_render_in_controllers_review_spec.rb +3 -3
- data/spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb +1 -1
- data/spec/rails_best_practices/reviews/use_scope_access_review_spec.rb +4 -4
- data/spec/rails_best_practices_spec.rb +1 -3
- data/spec/spec_helper.rb +4 -0
- metadata +6 -8
- data/lib/rails_best_practices/core/visitable_sexp.rb +0 -444
- data/spec/rails_best_practices/core/visitable_sexp_spec.rb +0 -272
- data/spec/rails_best_practices/reviews/review_spec.rb +0 -11
@@ -10,9 +10,9 @@ module RailsBestPractices
|
|
10
10
|
# Implementation:
|
11
11
|
#
|
12
12
|
# Review process:
|
13
|
-
# only check the
|
13
|
+
# only check the command nodes to see if there is bundler namespace in config/deploy.rb file,
|
14
14
|
#
|
15
|
-
# if the message of
|
15
|
+
# if the message of command node is "namespace" and the first argument is "bundler",
|
16
16
|
# then it should use bundler's capistrano recipe.
|
17
17
|
class DryBundlerInCapistranoReview < Review
|
18
18
|
def url
|
@@ -20,26 +20,16 @@ module RailsBestPractices
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def interesting_nodes
|
23
|
-
[:
|
23
|
+
[:command]
|
24
24
|
end
|
25
25
|
|
26
26
|
def interesting_files
|
27
27
|
/config\/deploy.rb/
|
28
28
|
end
|
29
29
|
|
30
|
-
# check call node to see if it is with message
|
31
|
-
|
32
|
-
|
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 start_call(node)
|
42
|
-
if :namespace == node.message and equal?(node.arguments[1], "bundler")
|
30
|
+
# check call node to see if it is with message "namespace" and argument "bundler".
|
31
|
+
def start_command(node)
|
32
|
+
if "namespace" == node.message.to_s && "bundler" == node.arguments.all[0].to_s
|
43
33
|
add_error "dry bundler in capistrano"
|
44
34
|
end
|
45
35
|
end
|
@@ -10,14 +10,14 @@ module RailsBestPractices
|
|
10
10
|
# Implementation:
|
11
11
|
#
|
12
12
|
# Review process:
|
13
|
-
# 1. check all
|
14
|
-
# if the right value is a call node with message
|
13
|
+
# 1. check all assignment nodes,
|
14
|
+
# if the right value is a call node with message "new",
|
15
15
|
# then remember their left value as new variables.
|
16
16
|
#
|
17
17
|
# 2. check all call nodes,
|
18
|
-
# if the message is
|
18
|
+
# if the message is "create" or "create!",
|
19
19
|
# then it should be isolated to db seed.
|
20
|
-
# if the message is
|
20
|
+
# if the message is "save" or "save!",
|
21
21
|
# and the subject is included in new variables,
|
22
22
|
# then it should be isolated to db seed.
|
23
23
|
class IsolateSeedDataReview < Review
|
@@ -26,7 +26,7 @@ module RailsBestPractices
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def interesting_nodes
|
29
|
-
[:call, :
|
29
|
+
[:call, :assign]
|
30
30
|
end
|
31
31
|
|
32
32
|
def interesting_files
|
@@ -38,53 +38,37 @@ module RailsBestPractices
|
|
38
38
|
@new_variables = []
|
39
39
|
end
|
40
40
|
|
41
|
-
# check
|
41
|
+
# check assignment node.
|
42
42
|
#
|
43
|
-
# if the right value of the node is a call node with
|
44
|
-
# then remember it as new variables
|
45
|
-
def
|
46
|
-
remember_new_variable(node)
|
47
|
-
end
|
48
|
-
|
49
|
-
# check instance assignment node.
|
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 start_iasgn(node)
|
43
|
+
# if the right value of the node is a call node with "new" message,
|
44
|
+
# then remember it as new variables.
|
45
|
+
def start_assign(node)
|
54
46
|
remember_new_variable(node)
|
55
47
|
end
|
56
48
|
|
57
49
|
# check the call node.
|
58
50
|
#
|
59
|
-
# if the message of the call node is
|
51
|
+
# if the message of the call node is "create" or "create!",
|
60
52
|
# then you should isolate it to seed data.
|
61
53
|
#
|
62
|
-
# if the message of the call node is
|
54
|
+
# if the message of the call node is "save" or "save!",
|
63
55
|
# and the subject of the call node is included in @new_variables,
|
64
56
|
# then you should isolate it to seed data.
|
65
57
|
def start_call(node)
|
66
|
-
if [
|
58
|
+
if ["create", "create!"].include? node.message.to_s
|
67
59
|
add_error("isolate seed data")
|
68
|
-
elsif [
|
60
|
+
elsif ["save", "save!"].include? node.message.to_s
|
69
61
|
add_error("isolate seed data") if new_record?(node)
|
70
62
|
end
|
71
63
|
end
|
72
64
|
|
73
65
|
private
|
74
|
-
# check
|
75
|
-
# if the right vavlue is a
|
66
|
+
# check assignment node,
|
67
|
+
# if the right vavlue is a method_add_arg node with message "new",
|
76
68
|
# 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
69
|
def remember_new_variable(node)
|
86
70
|
right_value = node.right_value
|
87
|
-
if :
|
71
|
+
if :method_add_arg == right_value.sexp_type && "new" == right_value.message.to_s
|
88
72
|
@new_variables << node.left_value.to_s
|
89
73
|
end
|
90
74
|
end
|
@@ -18,14 +18,14 @@ module RailsBestPractices
|
|
18
18
|
# then it should keep finders on its own model.
|
19
19
|
class KeepFindersOnTheirOwnModelReview < Review
|
20
20
|
|
21
|
-
FINDERS =
|
21
|
+
FINDERS = %w(find all first last)
|
22
22
|
|
23
23
|
def url
|
24
24
|
"http://rails-bestpractices.com/posts/13-keep-finders-on-their-own-model"
|
25
25
|
end
|
26
26
|
|
27
27
|
def interesting_nodes
|
28
|
-
[:
|
28
|
+
[:method_add_arg]
|
29
29
|
end
|
30
30
|
|
31
31
|
def interesting_files
|
@@ -36,36 +36,27 @@ module RailsBestPractices
|
|
36
36
|
#
|
37
37
|
# if the call node is
|
38
38
|
#
|
39
|
-
# 1. the message of call node is one of the
|
39
|
+
# 1. the message of call node is one of the find, all, first or last
|
40
40
|
# 2. the subject of call node is also a call node (it's the other model)
|
41
41
|
# 3. the any of its arguments is a hash (complex finder)
|
42
42
|
#
|
43
43
|
# then it should keep finders on its own model.
|
44
|
-
|
44
|
+
|
45
|
+
def start_method_add_arg(node)
|
45
46
|
add_error "keep finders on their own model" if other_finder?(node)
|
46
47
|
end
|
47
48
|
|
48
49
|
private
|
49
50
|
# check if the call node is the finder of other model.
|
50
51
|
#
|
51
|
-
# the message of the node should be one of
|
52
|
+
# the message of the node should be one of find, all, first or last,
|
52
53
|
# and the subject of the node should be with message :call (this is the other model),
|
53
|
-
# and any of its arguments is a hash,
|
54
|
-
#
|
55
|
-
# s(:call,
|
56
|
-
# s(:call, s(:self), :comment, s(:arglist)),
|
57
|
-
# :find,
|
58
|
-
# s(:arglist, s(:lit, :all),
|
59
|
-
# s(:hash,
|
60
|
-
# s(:lit, :conditions),
|
61
|
-
# s(:hash, s(:lit, :is_spam), s(:false)),
|
62
|
-
# s(:lit, :limit),
|
63
|
-
# s(:lit, 10)
|
64
|
-
# )
|
65
|
-
# )
|
66
|
-
# )
|
54
|
+
# and any of its arguments is a hash,
|
55
|
+
# then it is the finder of other model.
|
67
56
|
def other_finder?(node)
|
68
|
-
FINDERS.include?(node.message) &&
|
57
|
+
FINDERS.include?(node[1].message.to_s) &&
|
58
|
+
:call == node[1].subject.sexp_type &&
|
59
|
+
node.arguments.grep_nodes_count(:sexp_type => :bare_assoc_hash) > 0
|
69
60
|
end
|
70
61
|
end
|
71
62
|
end
|
@@ -16,6 +16,7 @@ module RailsBestPractices
|
|
16
16
|
# and outer the call node, it is also a call node,
|
17
17
|
# then it violate the law of demeter.
|
18
18
|
class LawOfDemeterReview < Review
|
19
|
+
ASSOCIATION_METHODS = %w(belongs_to has_one)
|
19
20
|
|
20
21
|
def url
|
21
22
|
"http://rails-bestpractices.com/posts/15-the-law-of-demeter"
|
@@ -29,17 +30,10 @@ module RailsBestPractices
|
|
29
30
|
#
|
30
31
|
# if the subject of the call node is also a call node,
|
31
32
|
# and the subject of the subject call node matchs one of the class names,
|
32
|
-
# and the message of the subject call node matchs one of the association name with the class name,
|
33
|
-
#
|
34
|
-
# s(:call,
|
35
|
-
# s(:call, s(:ivar, :@invoice), :user, s(:arglist)),
|
36
|
-
# :name,
|
37
|
-
# s(:arglist)
|
38
|
-
# )
|
39
|
-
#
|
33
|
+
# and the message of the subject call node matchs one of the association name with the class name,
|
40
34
|
# then it violates the law of demeter.
|
41
35
|
def start_call(node)
|
42
|
-
if
|
36
|
+
if :call == node.subject.sexp_type && need_delegate?(node)
|
43
37
|
add_error "law of demeter"
|
44
38
|
end
|
45
39
|
end
|
@@ -50,29 +44,14 @@ module RailsBestPractices
|
|
50
44
|
# if the subject of subject of the call node matchs any in model names,
|
51
45
|
# and the message of subject of the call node matchs any in association names,
|
52
46
|
# then it needs delegate.
|
53
|
-
#
|
54
|
-
# e.g. the source code is
|
55
|
-
#
|
56
|
-
# @invoic.user.name
|
57
|
-
#
|
58
|
-
# then the call node is
|
59
|
-
#
|
60
|
-
# s(:call, s(:call, s(:ivar, :@invoice), :user, s(:arglist)), :name, s(:arglist))
|
61
|
-
#
|
62
|
-
# as you see the subject of subject of the call node is [:ivar, @invoice],
|
63
|
-
# and the message of subject of the call node is :user
|
64
47
|
def need_delegate?(node)
|
65
|
-
|
48
|
+
return unless variable(node)
|
49
|
+
class_name = variable(node).to_s.sub('@', '').classify
|
66
50
|
association_name = node.subject.message.to_s
|
67
51
|
association = model_associations.get_association(class_name, association_name)
|
68
52
|
attribute_name = node.message.to_s
|
69
|
-
association &&
|
70
|
-
is_association_attribute?(association[
|
71
|
-
end
|
72
|
-
|
73
|
-
# only check belongs_to and has_one association.
|
74
|
-
def association_methods
|
75
|
-
[:belongs_to, :has_one]
|
53
|
+
association && ASSOCIATION_METHODS.include?(association["meta"]) &&
|
54
|
+
is_association_attribute?(association["class_name"], association_name, attribute_name)
|
76
55
|
end
|
77
56
|
|
78
57
|
def is_association_attribute?(association_class, association_name, attribute_name)
|
@@ -13,40 +13,42 @@ module RailsBestPractices
|
|
13
13
|
# only check all view files to see if there are finders, then the finders should be moved to controller.
|
14
14
|
class MoveCodeIntoControllerReview < Review
|
15
15
|
|
16
|
-
FINDERS =
|
16
|
+
FINDERS = %w(find all first last)
|
17
17
|
|
18
18
|
def url
|
19
19
|
"http://rails-bestpractices.com/posts/24-move-code-into-controller"
|
20
20
|
end
|
21
21
|
|
22
22
|
def interesting_nodes
|
23
|
-
[:
|
23
|
+
[:method_add_arg, :assign]
|
24
24
|
end
|
25
25
|
|
26
26
|
def interesting_files
|
27
27
|
VIEW_FILES
|
28
28
|
end
|
29
29
|
|
30
|
-
# check
|
30
|
+
# check method_add_arg nodes.
|
31
31
|
#
|
32
|
-
# if the subject of the
|
33
|
-
# and the message of the
|
32
|
+
# if the subject of the method_add_arg node is a constant,
|
33
|
+
# and the message of the method_add_arg node is one of the find, all, first and last,
|
34
34
|
# then it is a finder and should be moved to controller.
|
35
|
-
def
|
35
|
+
def start_method_add_arg(node)
|
36
36
|
add_error "move code into controller" if finder?(node)
|
37
37
|
end
|
38
38
|
|
39
|
+
# check assign nodes.
|
40
|
+
#
|
41
|
+
# if the subject of the right value node is a constant,
|
42
|
+
# and the message of the right value node is one of the find, all, first and last,
|
43
|
+
# then it is a finder and should be moved to controller.
|
44
|
+
def start_assign(node)
|
45
|
+
add_error "move code into controller", node.file, node.right_value.line if finder?(node.right_value)
|
46
|
+
end
|
47
|
+
|
39
48
|
private
|
40
49
|
# check if the node is a finder call node.
|
41
|
-
# e.g. the following call node is a finder
|
42
|
-
#
|
43
|
-
# s(:call,
|
44
|
-
# s(:const, :Post),
|
45
|
-
# :find,
|
46
|
-
# s(:arglist, s(:lit, :all))
|
47
|
-
# )
|
48
50
|
def finder?(node)
|
49
|
-
|
51
|
+
node.subject.const? && FINDERS.include?(node.message.to_s)
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
@@ -12,10 +12,10 @@ module RailsBestPractices
|
|
12
12
|
# Implementation:
|
13
13
|
#
|
14
14
|
# Review process:
|
15
|
-
# check al method
|
15
|
+
# check al method method_add_arg nodes to see if there is a complex options_for_select helper.
|
16
16
|
#
|
17
|
-
# if the message of the
|
18
|
-
# and the first argument of the
|
17
|
+
# if the message of the method_add_arg node is options_for_select,
|
18
|
+
# and the first argument of the method_add_arg node is array,
|
19
19
|
# and the size of the array is greater than array_count defined,
|
20
20
|
# then the options_for_select method should be moved into helper.
|
21
21
|
class MoveCodeIntoHelperReview < Review
|
@@ -24,7 +24,7 @@ module RailsBestPractices
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def interesting_nodes
|
27
|
-
[:
|
27
|
+
[:method_add_arg]
|
28
28
|
end
|
29
29
|
|
30
30
|
def interesting_files
|
@@ -36,12 +36,12 @@ module RailsBestPractices
|
|
36
36
|
@array_count = options['array_count'] || 3
|
37
37
|
end
|
38
38
|
|
39
|
-
# check
|
39
|
+
# check method_add_arg node with message options_for_select (sorry we only check options_for_select helper now).
|
40
40
|
#
|
41
41
|
# if the first argument of options_for_select method call is an array,
|
42
42
|
# and the size of the array is more than @array_count defined,
|
43
43
|
# then the options_for_select helper should be moved into helper.
|
44
|
-
def
|
44
|
+
def start_method_add_arg(node)
|
45
45
|
add_error "move code into helper (array_count >= #{@array_count})" if complex_select_options?(node)
|
46
46
|
end
|
47
47
|
|
@@ -50,29 +50,11 @@ module RailsBestPractices
|
|
50
50
|
#
|
51
51
|
# if the first argument is an array,
|
52
52
|
# and the size of array is greater than @array_count you defined,
|
53
|
-
# then it is complext
|
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
|
-
# )
|
53
|
+
# then it is complext.
|
74
54
|
def complex_select_options?(node)
|
75
|
-
|
55
|
+
"options_for_select" == node[1].message.to_s &&
|
56
|
+
:array == node.arguments.all[0].sexp_type &&
|
57
|
+
node.arguments.all[0].array_size > @array_count
|
76
58
|
end
|
77
59
|
end
|
78
60
|
end
|
@@ -10,16 +10,15 @@ module RailsBestPractices
|
|
10
10
|
# Implementation:
|
11
11
|
#
|
12
12
|
# Review process:
|
13
|
-
# check if there are multiple method calls or attribute assignments apply to one subject,
|
14
|
-
# and the subject is a
|
15
|
-
# then they should be moved into model.
|
13
|
+
# check if, unless, elsif there are multiple method calls or attribute assignments apply to one subject,
|
14
|
+
# and the subject is a variable, then they should be moved into model.
|
16
15
|
class MoveCodeIntoModelReview < Review
|
17
16
|
def url
|
18
17
|
"http://rails-bestpractices.com/posts/25-move-code-into-model"
|
19
18
|
end
|
20
19
|
|
21
20
|
def interesting_nodes
|
22
|
-
[:if]
|
21
|
+
[:if, :unless, :elsif]
|
23
22
|
end
|
24
23
|
|
25
24
|
def interesting_files
|
@@ -31,22 +30,24 @@ module RailsBestPractices
|
|
31
30
|
@use_count = options['use_count'] || 2
|
32
31
|
end
|
33
32
|
|
34
|
-
# check if node to see whose conditional statementnodes contain multiple call nodes with same subject who is a
|
33
|
+
# check if node to see whose conditional statementnodes contain multiple call nodes with same subject who is a variable.
|
35
34
|
#
|
36
|
-
# it will check every call and
|
35
|
+
# it will check every call and assignment nodes in the conditional statement nodes.
|
37
36
|
#
|
38
|
-
# if there are multiple call and
|
39
|
-
# and the subject is a
|
40
|
-
# then the conditional statement nodes should be moved into model.
|
37
|
+
# if there are multiple call and assignment nodes who have the same subject,
|
38
|
+
# and the subject is a variable, then the conditional statement nodes should be moved into model.
|
41
39
|
def start_if(node)
|
42
|
-
node.conditional_statement.grep_nodes(:
|
40
|
+
node.conditional_statement.grep_nodes(:sexp_type => :call) { |child_node| remember_variable_use_count(child_node) }
|
43
41
|
|
44
42
|
variable_use_count.each do |variable_node, count|
|
45
|
-
add_error "move code into model (#{variable_node} use_count > #{@use_count})"
|
43
|
+
add_error "move code into model (#{variable_node} use_count > #{@use_count})" if count > @use_count
|
46
44
|
end
|
47
45
|
|
48
46
|
reset_variable_use_count
|
49
47
|
end
|
48
|
+
|
49
|
+
alias_method :start_unless, :start_if
|
50
|
+
alias_method :start_elsif, :start_if
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -10,55 +10,44 @@ module RailsBestPractices
|
|
10
10
|
# Implementation:
|
11
11
|
#
|
12
12
|
# Review process:
|
13
|
-
# check all method
|
13
|
+
# check all method method_add_arg nodes in controller files.
|
14
14
|
# if there is any call node with message find, all, first or last,
|
15
15
|
# and it has a hash argument,
|
16
16
|
# then it is a complex finder, and should be moved to model's named scope.
|
17
17
|
class MoveFinderToNamedScopeReview < Review
|
18
18
|
|
19
|
-
|
19
|
+
FINDERS = %w(find all first last)
|
20
20
|
|
21
21
|
def url
|
22
22
|
"http://rails-bestpractices.com/posts/1-move-finder-to-named_scope"
|
23
23
|
end
|
24
24
|
|
25
25
|
def interesting_nodes
|
26
|
-
[:
|
26
|
+
[:method_add_arg]
|
27
27
|
end
|
28
28
|
|
29
29
|
def interesting_files
|
30
30
|
CONTROLLER_FILES
|
31
31
|
end
|
32
32
|
|
33
|
-
# check
|
33
|
+
# check method_add_ag node if its message is one of find, all, first or last,
|
34
34
|
# and it has a hash argument,
|
35
35
|
# then the call node is the finder that should be moved to model's named_scope.
|
36
|
-
def
|
36
|
+
def start_method_add_arg(node)
|
37
37
|
add_error "move finder to named_scope" if finder?(node)
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
|
-
# check if the
|
41
|
+
# check if the method_add_arg node is a finder.
|
42
42
|
#
|
43
|
-
# if the subject of
|
44
|
-
# and the message of call
|
43
|
+
# if the subject of method_add_arg node is a constant,
|
44
|
+
# and the message of call method_add_arg is one of find, all, first or last,
|
45
45
|
# and any of its arguments is a hash,
|
46
|
-
# then it is a finder.
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
# s(:lit, :conditions),
|
52
|
-
# s(:hash, s(:lit, :state), s(:str, "public")),
|
53
|
-
# s(:lit, :limit),
|
54
|
-
# s(:lit, 10),
|
55
|
-
# s(:lit, :order),
|
56
|
-
# s(:str, "created_at desc")
|
57
|
-
# )
|
58
|
-
# )
|
59
|
-
# )
|
60
|
-
def finder?(call_node)
|
61
|
-
:const == call_node.subject.node_type && FINDER.include?(call_node.message) && call_node.arguments.children.any? { |node| :hash == node.node_type }
|
46
|
+
# then it is a finder.
|
47
|
+
def finder?(node)
|
48
|
+
FINDERS.include?(node[1].message.to_s) &&
|
49
|
+
:call == node[1].sexp_type &&
|
50
|
+
node.arguments.grep_nodes_count(:sexp_type => :bare_assoc_hash) > 0
|
62
51
|
end
|
63
52
|
end
|
64
53
|
end
|