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.
Files changed (63) hide show
  1. data/README.md +20 -0
  2. data/assets/result.html.haml +1 -1
  3. data/lib/rails_best_practices.rb +5 -3
  4. data/lib/rails_best_practices/core.rb +1 -1
  5. data/lib/rails_best_practices/core/check.rb +9 -12
  6. data/lib/rails_best_practices/core/checking_visitor.rb +9 -6
  7. data/lib/rails_best_practices/core/model_associations.rb +1 -1
  8. data/lib/rails_best_practices/core/nil.rb +5 -1
  9. data/lib/rails_best_practices/core/runner.rb +5 -5
  10. data/lib/rails_best_practices/core_ext/sexp.rb +688 -0
  11. data/lib/rails_best_practices/prepares/mailer_prepare.rb +4 -5
  12. data/lib/rails_best_practices/prepares/model_prepare.rb +16 -26
  13. data/lib/rails_best_practices/prepares/schema_prepare.rb +11 -17
  14. data/lib/rails_best_practices/reviews/add_model_virtual_attribute_review.rb +24 -75
  15. data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +39 -113
  16. data/lib/rails_best_practices/reviews/dry_bundler_in_capistrano_review.rb +6 -16
  17. data/lib/rails_best_practices/reviews/isolate_seed_data_review.rb +16 -32
  18. data/lib/rails_best_practices/reviews/keep_finders_on_their_own_model_review.rb +11 -20
  19. data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +7 -28
  20. data/lib/rails_best_practices/reviews/move_code_into_controller_review.rb +16 -14
  21. data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +10 -28
  22. data/lib/rails_best_practices/reviews/move_code_into_model_review.rb +12 -11
  23. data/lib/rails_best_practices/reviews/move_finder_to_named_scope_review.rb +13 -24
  24. data/lib/rails_best_practices/reviews/move_model_logic_into_model_review.rb +9 -9
  25. data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +24 -68
  26. data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +15 -22
  27. data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +31 -91
  28. data/lib/rails_best_practices/reviews/remove_empty_helpers_review.rb +4 -2
  29. data/lib/rails_best_practices/reviews/replace_complex_creation_with_factory_method_review.rb +20 -18
  30. data/lib/rails_best_practices/reviews/replace_instance_variable_with_local_variable_review.rb +5 -3
  31. data/lib/rails_best_practices/reviews/review.rb +8 -37
  32. data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +10 -6
  33. data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +9 -6
  34. data/lib/rails_best_practices/reviews/use_before_filter_review.rb +14 -72
  35. data/lib/rails_best_practices/reviews/use_model_association_review.rb +19 -31
  36. data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +5 -5
  37. data/lib/rails_best_practices/reviews/use_observer_review.rb +22 -40
  38. data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +34 -39
  39. data/lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb +14 -38
  40. data/lib/rails_best_practices/reviews/use_scope_access_review.rb +13 -44
  41. data/lib/rails_best_practices/version.rb +1 -1
  42. data/spec/rails_best_practices/core/check_spec.rb +5 -5
  43. data/spec/rails_best_practices/core/checking_visitor_spec.rb +4 -4
  44. data/spec/rails_best_practices/core/model_associations_spec.rb +4 -4
  45. data/spec/rails_best_practices/core/nil_spec.rb +7 -1
  46. data/spec/rails_best_practices/core_ext/sexp_spec.rb +430 -0
  47. data/spec/rails_best_practices/prepares/model_prepare_spec.rb +12 -12
  48. data/spec/rails_best_practices/prepares/schema_prepare_spec.rb +6 -6
  49. data/spec/rails_best_practices/reviews/move_code_into_controller_review_spec.rb +14 -2
  50. data/spec/rails_best_practices/reviews/move_code_into_helper_review_spec.rb +1 -1
  51. data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +3 -3
  52. data/spec/rails_best_practices/reviews/not_use_default_route_review_spec.rb +1 -1
  53. data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +15 -1
  54. data/spec/rails_best_practices/reviews/simplify_render_in_controllers_review_spec.rb +3 -3
  55. data/spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb +1 -1
  56. data/spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb +1 -1
  57. data/spec/rails_best_practices/reviews/use_scope_access_review_spec.rb +4 -4
  58. data/spec/rails_best_practices_spec.rb +1 -3
  59. data/spec/spec_helper.rb +4 -0
  60. metadata +6 -8
  61. data/lib/rails_best_practices/core/visitable_sexp.rb +0 -444
  62. data/spec/rails_best_practices/core/visitable_sexp_spec.rb +0 -272
  63. 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 call nodes to see if there is bundler namespace in config/deploy.rb file,
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 call node is :namespace and the arguments of the call node is :bundler,
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
- [:call]
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 :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 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 local assignment and instance assignment nodes,
14
- # if the right value is a call node with message :new,
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 :create or :create!,
18
+ # if the message is "create" or "create!",
19
19
  # then it should be isolated to db seed.
20
- # if the message is :save or :save!,
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, :lasgn, :iasgn]
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 local assignment node.
41
+ # check assignment node.
42
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 start_lasgn(node)
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 :create or :create!,
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 :save or :save!,
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 [:create, :create!].include? node.message
58
+ if ["create", "create!"].include? node.message.to_s
67
59
  add_error("isolate seed data")
68
- elsif [:save, :save!].include? node.message
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 local assignment or instance assignment node,
75
- # if the right vavlue is a call node with message :new,
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 :call == right_value.node_type && :new == right_value.message
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 = [:find, :all, :first, :last]
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
- [:call]
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 :find, :all, :first or :last
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
- def start_call(node)
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 :find, :all, :first or :last,
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, like
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) && :call == node.subject.node_type && node.arguments.children.any? { |node| :hash == node.node_type }
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, like
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 [:lvar, :ivar].include?(node.subject.subject.node_type) && need_delegate?(node)
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
- class_name = node.subject.subject.to_s(:remove_at => true).classify
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 && association_methods.include?(association[:meta]) &&
70
- is_association_attribute?(association[:class_name], association_name, attribute_name)
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 = [:find, :all, :first, :last]
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
- [:call]
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 call nodes.
30
+ # check method_add_arg nodes.
31
31
  #
32
- # if the subject of the call node is a constant,
33
- # and the message of the call node is one of the :find, :all, :first and :last,
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 start_call(node)
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
- :const == node.subject.node_type && FINDERS.include?(node.message)
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 calls to see if there is a complex options_for_select helper.
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 call node is options_for_select,
18
- # and the first argument of the call node is array,
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
- [:call]
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 call node with message options_for_select (sorry we only check options_for_select helper now).
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 start_call(node)
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, 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
- # )
53
+ # then it is complext.
74
54
  def complex_select_options?(node)
75
- :options_for_select == node.message && :array == node.arguments[1].node_type && node.arguments[1].size > @array_count
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 local variable or instance variable,
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 local variable or instance variable.
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 attrasgn nodes in the conditional statement nodes.
35
+ # it will check every call and assignment nodes in the conditional statement nodes.
37
36
  #
38
- # if there are multiple call and attrasgn nodes who have the same subject,
39
- # and the subject is a local variable or an instance variable,
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(:node_type => [:call, :attrasgn]) { |child_node| remember_variable_use_count(child_node) }
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})", variable_node.file, variable_node.line if 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 calls in controller files.
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
- FINDER = [:find, :all, :first, :last]
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
- [:call]
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 call node if its message is one of :find, :all, :first or :last,
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 start_call(node)
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 call node is a finder.
41
+ # check if the method_add_arg node is a finder.
42
42
  #
43
- # if the subject of call node is a constant,
44
- # and the message of call node is one of find, all, first or last,
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. e.g.
47
- #
48
- # s(:call, s(:const, :Post), :find,
49
- # s(:arglist, s(:lit, :all),
50
- # s(:hash,
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