rails_best_practices 0.10.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|