rails_best_practices 0.3.0 → 0.4.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 (44) hide show
  1. data/README.textile +72 -54
  2. data/Rakefile +5 -5
  3. data/VERSION +1 -1
  4. data/identifier +0 -0
  5. data/lib/rails_best_practices/checks/always_add_db_index_check.rb +27 -62
  6. data/lib/rails_best_practices/checks/check.rb +12 -13
  7. data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +7 -20
  8. data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +1 -1
  9. data/lib/rails_best_practices/checks/law_of_demeter_check.rb +6 -5
  10. data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +8 -3
  11. data/lib/rails_best_practices/checks/move_code_into_model_check.rb +53 -0
  12. data/lib/rails_best_practices/checks/needless_deep_nesting_check.rb +36 -4
  13. data/lib/rails_best_practices/checks/not_use_default_route_check.rb +3 -2
  14. data/lib/rails_best_practices/checks/overuse_route_customizations_check.rb +23 -10
  15. data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +1 -1
  16. data/lib/rails_best_practices/checks/{use_filter_check.rb → use_before_filter_check.rb} +9 -7
  17. data/lib/rails_best_practices/checks/use_scope_access_check.rb +1 -1
  18. data/lib/rails_best_practices/checks.rb +2 -4
  19. data/lib/rails_best_practices/command.rb +7 -41
  20. data/lib/rails_best_practices/core/checking_visitor.rb +2 -2
  21. data/lib/rails_best_practices/core/runner.rb +8 -6
  22. data/lib/rails_best_practices/core/visitable_sexp.rb +8 -2
  23. data/lib/rails_best_practices.rb +41 -1
  24. data/rails_best_practices.gemspec +25 -30
  25. data/rails_best_practices.yml +3 -5
  26. data/spec/rails_best_practices/checks/always_add_db_index_check_spec.rb +32 -112
  27. data/spec/rails_best_practices/checks/isolate_seed_data_check_spec.rb +0 -3
  28. data/spec/rails_best_practices/checks/keep_finders_on_their_own_model_check_spec.rb +33 -1
  29. data/spec/rails_best_practices/checks/law_of_demeter_check_spec.rb +66 -27
  30. data/spec/rails_best_practices/checks/move_code_into_helper_check_spec.rb +2 -2
  31. data/spec/rails_best_practices/checks/move_code_into_model_check_spec.rb +55 -0
  32. data/spec/rails_best_practices/checks/needless_deep_nesting_check_spec.rb +55 -21
  33. data/spec/rails_best_practices/checks/not_use_default_route_check_spec.rb +51 -22
  34. data/spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb +134 -48
  35. data/spec/rails_best_practices/checks/replace_instance_variable_with_local_variable_check_spec.rb +11 -1
  36. data/spec/rails_best_practices/checks/{use_filter_check_spec.rb → use_before_filter_check_spec.rb} +6 -6
  37. data/spec/rails_best_practices/checks/use_scope_access_check_spec.rb +22 -0
  38. metadata +49 -39
  39. data/lib/rails_best_practices/checks/many_to_many_collection_check.rb +0 -12
  40. data/lib/rails_best_practices/checks/nested_model_forms_check.rb +0 -12
  41. data/lib/rails_best_practices/checks/use_model_callback_check.rb +0 -12
  42. data/spec/rails_best_practices/checks/many_to_many_collection_check_spec.rb +0 -11
  43. data/spec/rails_best_practices/checks/nested_model_forms_check_spec.rb +0 -11
  44. data/spec/rails_best_practices/checks/use_model_callback_check_spec.rb +0 -11
data/README.textile CHANGED
@@ -1,28 +1,27 @@
1
1
  h1. rails_best_practices
2
2
 
3
- rails_best_practices is a gem to check quality of rails app files according to ihower's presentation Rails Best Practices from Kungfu RailsConf in Shanghai China.
4
- rails_best_practices is a code static parser tool.
3
+ rails_best_practices is a code metric tool to check the quality of rails codes.
5
4
 
6
5
  *************************************************
7
6
 
8
7
  h2. Resources
9
8
 
10
- "ihower's presentation":http://www.slideshare.net/ihower/rails-best-practices
11
- "Wiki":http://wiki.github.com/flyerhzm/rails_best_practices
9
+ Homepage: "http://rails-bestpractices.com":http://rails-bestpractices.com
10
+ Wiki: "http://github.com/flyerhzm/rails_best_practices/wiki":http://github.com/flyerhzm/rails_best_practices/wiki
12
11
 
13
12
  *************************************************
14
13
 
15
14
  h2. Install
16
15
 
17
16
  <pre><code>
18
- sudo gem install rails_best_practices --source http://gemcutter.org
17
+ sudo gem install rails_best_practices
19
18
  </code></pre>
20
19
 
21
20
  *************************************************
22
21
 
23
22
  h2. Usage
24
23
 
25
- at the root directory of rails app
24
+ At the root directory of rails app
26
25
 
27
26
  <pre><code>
28
27
  rails_best_practices .
@@ -30,6 +29,31 @@ rails_best_practices .
30
29
 
31
30
  notice the period at the end, it can be the relative or absolute path of your rails app.
32
31
 
32
+ And default rails_best_practices will do parse codes in vendor, spec, test and stories directories. If you need, see the command options:
33
+
34
+ <pre><code>
35
+ $ rails_best_practices -h
36
+ Usage: rails_best_practices [options]
37
+ -d, --debug Debug mode
38
+ --vendor include vendor files
39
+ --spec include spec files
40
+ --test include test files
41
+ --stories include stories files
42
+ -h, --help Show this message
43
+ </code></pre>
44
+
45
+ *************************************************
46
+
47
+ h2. Issue
48
+
49
+ If you got NoMethodError or any syntax error, you should use debug mode to detect which file rails_best_practices is parsing and getting the error.
50
+
51
+ <pre><code>
52
+ rails_best_practices -d .
53
+ </code></pre>
54
+
55
+ Then give me the error stack and the source code of the file that rails_best_practices is parsing error.
56
+
33
57
  *************************************************
34
58
 
35
59
  h2. Customize Configuration
@@ -42,11 +66,8 @@ MoveFinderToNamedScopeCheck: { }
42
66
  UseModelAssociationCheck: { }
43
67
  UseScopeAccessCheck: { }
44
68
  AddModelVirtualAttributeCheck: { }
45
- # UseModelCallbackCheck: { }
46
69
  ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
47
70
  MoveModelLogicIntoModelCheck: { called_count: 4 }
48
- # ManyToManyCollectionCheck: { }
49
- # NestedModelFormsCheck: { }
50
71
  OveruseRouteCustomizationsCheck: { customize_count: 3 }
51
72
  NeedlessDeepNestingCheck: { nested_count: 2 }
52
73
  NotUseDefaultRouteCheck: { }
@@ -55,58 +76,55 @@ LawOfDemeterCheck: { }
55
76
  UseObserverCheck: { }
56
77
  IsolateSeedDataCheck: { }
57
78
  AlwaysAddDbIndexCheck: { }
58
- UseFilterCheck: { }
79
+ UseBeforeFilterCheck: { }
59
80
  MoveCodeIntoControllerCheck: { }
60
- MoveCodeIntoHelperCheck: { }
81
+ MoveCodeIntoModelCheck: { }
82
+ MoveCodeIntoHelperCheck: { array_count: 3 }
61
83
  ReplaceInstanceVariableWithLocalVariableCheck: { }
62
84
  </code></pre>
63
85
 
64
86
  *************************************************
65
87
 
66
- h2. Progress
67
-
68
- * Lesson 1. Move code from Controller to Model
69
- ## [-Move finder to named_scope-]
70
- ## [-Use model association-]
71
- ## [-Use scope access-]
72
- ## [-Add model virtual attribute-]
73
- ## Use model callback
74
- ## [-Replace Complex Creation with Factory Method-]
75
- ## [-Move Model Logic into the Model-]
76
- ## model.collection_model_ids (many-to-many)
77
- ## Nested Model Forms (one-to-one)
78
- ## Nested Model Forms (one-to-many)
79
-
80
- * Lesson 2. RESTful Conventions
81
- ## [-Overuse route customizations-]
82
- ## [-Needless deep nesting-]
83
- ## [-Not use default route-]
84
-
85
- * Lesson 3. Model
86
- ## [-Keep Finders on Their Own Model-]
87
- ## [-Love named_scope-] # same as Move finder to named_scope
88
- ## [-the Law of Demeter-]
89
- ## DRY: metaprogramming
90
- ## Extract into Module
91
- ## Extract to composed class
92
- ## [-Use Observer-]
93
-
94
- * Lesson 4. Migration
95
- ## [-Isolating Seed Data-]
96
- ## [-Always add DB index-]
97
-
98
- * Lesson 5. Controller
99
- ## [-Use before_filter-]
100
- ## DRY Controller
101
-
102
- * Lesson 6. View
103
- ## [-Move code into controller-]
88
+ h2. Implementation
89
+
90
+ * Move code from Controller to Model
91
+ ## Move finder to named_scope (rails2 only)
92
+ ## Use model association
93
+ ## Use scope access
94
+ ## Add model virtual attribute
95
+ ## Replace Complex Creation with Factory Method
96
+ ## Move Model Logic into the Model
97
+
98
+ * RESTful Conventions
99
+ ## Overuse route customizations
100
+ ## Needless deep nesting
101
+ ## Not use default route
102
+
103
+ * Model
104
+ ## Keep Finders on Their Own Model (rails2 only)
105
+ ## the Law of Demeter
106
+ ## Use Observer
107
+
108
+ * Migration
109
+ ## Isolating Seed Data
110
+ ## Always add DB index
111
+
112
+ * Controller
113
+ ## Use before_filter
114
+
115
+ * View
116
+ ## Move code into controller
104
117
  ## Move code into model
105
- ## [-Move code into helper-]
106
- ## [-Replace instance variable with local variable-]
107
- ## [-Use Form Builder-] # not implement, use http://github.com/justinfrench/formtastic
108
- ## [-Organize Helper files-] # not implement, it's rails default behaviour
118
+ ## Move code into helper
119
+ ## Replace instance variable with local variable
120
+
121
+ *************************************************
122
+
123
+ h2. Links
124
+
125
+ "http://railsenvy.com/2009/12/07/rails-envy-podcast-episode-102":http://railsenvy.com/2009/12/07/rails-envy-podcast-episode-102
126
+ "http://ruby5.envylabs.com/episodes/29-episode-28-november-17-2009":http://ruby5.envylabs.com/episodes/29-episode-28-november-17-2009
109
127
 
110
128
  *************************************************
111
129
 
112
- Copyright © 2009 Richard Huang (flyerhzm@gmail.com), released under the MIT license
130
+ Copyright © 2010 Richard Huang (flyerhzm@gmail.com), released under the MIT license
data/Rakefile CHANGED
@@ -6,10 +6,10 @@ require 'jeweler'
6
6
  desc 'Default: run unit tests.'
7
7
  task :default => :spec
8
8
 
9
- desc 'Generate documentation for the sitemap plugin.'
9
+ desc 'Generate documentation for the rails_best_practices plugin.'
10
10
  Rake::RDocTask.new(:rdoc) do |rdoc|
11
11
  rdoc.rdoc_dir = 'rdoc'
12
- rdoc.title = 'Bullet'
12
+ rdoc.title = 'rails_best_practices'
13
13
  rdoc.options << '--line-numbers' << '--inline-source'
14
14
  rdoc.rdoc_files.include('README')
15
15
  rdoc.rdoc_files.include('lib/**/*.rb')
@@ -22,10 +22,10 @@ end
22
22
 
23
23
  Jeweler::Tasks.new do |gemspec|
24
24
  gemspec.name = "rails_best_practices"
25
- gemspec.summary = "check rails files according to ihower's presentation 'rails best practices'"
26
- gemspec.description = "check rails files according to ihower's presentation 'rails best practices'"
25
+ gemspec.summary = "a code metric tool for rails codes."
26
+ gemspec.description = "a code metric tool for rails codes."
27
27
  gemspec.email = "flyerhzm@gmail.com"
28
- gemspec.homepage = "http://github.com/flyerhzm/rails_best_practices"
28
+ gemspec.homepage = "http://rails-bestpractices.com"
29
29
  gemspec.authors = ["Richard Huang"]
30
30
  gemspec.add_dependency 'ruby_parser', '>= 2.0.4'
31
31
  gemspec.add_dependency 'ruby2ruby', '>= 1.2.4'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.4.0
data/identifier ADDED
File without changes
@@ -2,89 +2,54 @@ require 'rails_best_practices/checks/check'
2
2
 
3
3
  module RailsBestPractices
4
4
  module Checks
5
- # Check a migration file to make sure every reference key has a database index.
5
+ # Check db/schema.rb file to make sure every reference key has a database index.
6
6
  #
7
- # Implementation:
8
- # Parse migration files twice.
9
- # First, remember all reference keys and index keys.
10
- # Second, compare reference keys and index keys, and add error when reference keys are not in index keys.
7
+ # Implementation: read all add_index method calls to get the indexed columns in table, then read integer method call in create_table block to get the reference columns in tables, compare with indexed columns, if not in the indexed columns, then it violates always_add_db_index_check.
11
8
  class AlwaysAddDbIndexCheck < Check
12
9
 
13
10
  def interesting_nodes
14
- [:defs]
11
+ [:block, :call]
15
12
  end
16
13
 
17
14
  def interesting_files
18
- MIGRATION_FILES
15
+ /db\/schema.rb/
19
16
  end
20
17
 
21
18
  def initialize
22
19
  super
23
- @files = []
24
- @references = {}
25
- @indexes = {}
26
- @parse = false
20
+ @index_columns = []
27
21
  end
28
22
 
29
23
  def evaluate_start(node)
30
- if @files.include? node.file
31
- @parse = true if :up == node.message
32
- else
33
- @files << node.file
34
- end
35
-
36
- if @parse and :up == node.message
37
- @references.each do |table_name, column_names|
38
- differences = column_names - (@indexes[table_name] || [])
39
- @references[table_name] = column_names - differences
40
- hint = differences.collect {|column_name| "#{table_name} => #{column_name}"}.join(', ')
41
- add_error "always add db index (#{hint})" unless differences.empty?
24
+ if :block == node.node_type
25
+ find_index_columns(node)
26
+ elsif :call == node.node_type
27
+ case node.message
28
+ when :create_table
29
+ @table_name = node.arguments[1].to_ruby_string
30
+ when :integer
31
+ column_name = node.arguments[1].to_ruby_string
32
+ if column_name =~ /_id$/ and !indexed?(@table_name, column_name)
33
+ add_error "always add db index (#@table_name => #{column_name})", node.file, node.line
34
+ end
42
35
  end
43
- else
44
- remember_references(node.body)
45
- remember_indexes(node.body)
46
36
  end
47
37
  end
48
-
38
+
49
39
  private
50
-
51
- def remember_references(node)
52
- create_table_node = node.grep_nodes({:node_type => :call, :message => :create_table}).first
53
- if create_table_node
54
- table_name = create_table_node.arguments[1].to_ruby_string
55
- node.grep_nodes({:node_type => :call, :message => :integer}).each do |integer_node|
56
- column_name = integer_node.arguments[1].to_ruby_string
57
- if column_name =~ /_id$/
58
- @references[table_name] ||= []
59
- @references[table_name] << column_name
60
- end
61
- end
62
- node.grep_nodes({:node_type => :call, :message => :references}).each do |references_node|
63
- column_name = references_node.arguments[1].to_ruby_string + "_id"
64
- @references[table_name] ||= []
65
- @references[table_name] << column_name
66
- end
67
- node.grep_nodes({:node_type => :call, :message => :column}).each do |column_node|
68
- if 'integer' == column_node.arguments[2].to_ruby_string
69
- column_name = column_node.arguments[1].to_ruby_string
70
- if column_name =~ /_id$/
71
- @references[table_name] ||= []
72
- @references[table_name] << column_name
73
- end
74
- end
40
+ def find_index_columns(node)
41
+ node.grep_nodes({:node_type => :call, :message => :add_index}).each do |index_node|
42
+ table_name = index_node.arguments[1].to_ruby_string
43
+ reference_column = eval(index_node.arguments[2].to_ruby)
44
+ @index_columns << [table_name, reference_column]
75
45
  end
76
46
  end
77
- end
78
-
79
- def remember_indexes(node)
80
- add_index_nodes = node.grep_nodes({:node_type => :call, :message => :add_index})
81
- add_index_nodes.each do |add_index_node|
82
- table_name = add_index_node.arguments[1].to_ruby_string
83
- column_name = add_index_node.arguments[2].to_ruby_string
84
- @indexes[table_name] ||= []
85
- @indexes[table_name] << column_name
47
+
48
+ def indexed?(table_name, column_name)
49
+ !!@index_columns.find do |reference|
50
+ reference[0] == table_name and reference[1].class == String ? reference[1] == column_name : reference[1].include?(column_name)
51
+ end
86
52
  end
87
- end
88
53
  end
89
54
  end
90
55
  end
@@ -3,13 +3,15 @@ require 'rails_best_practices/core/error'
3
3
  module RailsBestPractices
4
4
  module Checks
5
5
  class Check
6
- NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar]
6
+ NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar, :block, :iter]
7
7
 
8
- CONTROLLER_FILES = /_controller.rb$/
9
- MIGRATION_FILES = /db\/migrate\/.*rb/
10
- MODLE_FILES = /models\/.*rb/
11
- VIEW_FILES = /views\/.*erb/
12
- PARTIAL_VIEW_FILES = /views\/.*\/_.*erb/
8
+ CONTROLLER_FILES = /_controller\.rb$/
9
+ MIGRATION_FILES = /db\/migrate\/.*\.rb/
10
+ MODLE_FILES = /models\/.*\.rb/
11
+ VIEW_FILES = /views\/.*\.(erb|haml)/
12
+ PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)/
13
+
14
+ attr_reader :errors
13
15
 
14
16
  def initialize
15
17
  @errors = []
@@ -52,13 +54,10 @@ module RailsBestPractices
52
54
  evaluate_end(node)
53
55
  end
54
56
 
55
- def add_error(error, line = nil)
56
- line = @node.line if line.nil?
57
- @errors << RailsBestPractices::Core::Error.new("#{@node.file}", "#{line}", error)
58
- end
59
-
60
- def errors
61
- @errors
57
+ def add_error(error, file = nil, line = nil)
58
+ file ||= @node.file
59
+ line ||= @node.line
60
+ @errors << RailsBestPractices::Core::Error.new("#{file}", "#{line}", error)
62
61
  end
63
62
  end
64
63
  end
@@ -8,7 +8,7 @@ module RailsBestPractices
8
8
  class IsolateSeedDataCheck < Check
9
9
 
10
10
  def interesting_nodes
11
- [:defs, :call, :lasgn]
11
+ [:call, :lasgn]
12
12
  end
13
13
 
14
14
  def interesting_files
@@ -18,28 +18,15 @@ module RailsBestPractices
18
18
  def initialize
19
19
  super
20
20
  @new_variables = []
21
- @files = []
22
- @parse = false
23
21
  end
24
22
 
25
23
  def evaluate_start(node)
26
- # check duplicate migration because of always_add_db_index_check.
27
- if :defs == node.node_type
28
- if @files.include? node.file
29
- @parse = true if :up == node.message
30
- else
31
- @files << node.file
32
- end
33
- end
34
-
35
- if @parse
36
- if [:create, :create!].include? node.message
37
- add_error("isolate seed data")
38
- elsif :lasgn == node.node_type
39
- remember_new_variable(node)
40
- elsif [:save, :save!].include? node.message
41
- add_error("isolate seed data") if new_record?(node)
42
- end
24
+ if [:create, :create!].include? node.message
25
+ add_error("isolate seed data")
26
+ elsif :lasgn == node.node_type
27
+ remember_new_variable(node)
28
+ elsif [:save, :save!].include? node.message
29
+ add_error("isolate seed data") if new_record?(node)
43
30
  end
44
31
  end
45
32
 
@@ -22,7 +22,7 @@ module RailsBestPractices
22
22
  private
23
23
 
24
24
  def others_finder?(node)
25
- node.message == :find and node.subject.node_type == :call
25
+ [:find, :all, :first, :last].include? node.message and node.subject.node_type == :call and node.arguments.size > 1
26
26
  end
27
27
  end
28
28
  end
@@ -5,8 +5,8 @@ module RailsBestPractices
5
5
  # Check to make sure not avoid the law of demeter.
6
6
  #
7
7
  # Implementation:
8
- # 1. check all models to record belongs_to associations
9
- # 2. check if calling belongs_to association's method or attribute
8
+ # 1. check all models to record belongs_to and has_one associations
9
+ # 2. check if calling belongs_to and has_one association's method or attribute
10
10
  class LawOfDemeterCheck < Check
11
11
 
12
12
  def interesting_nodes
@@ -20,7 +20,7 @@ module RailsBestPractices
20
20
 
21
21
  def evaluate_start(node)
22
22
  if node.node_type == :class
23
- remember_belongs_to(node)
23
+ remember_association(node)
24
24
  elsif [:lvar, :ivar].include?(node.subject.node_type) and node.subject != s(:lvar, :_erbout)
25
25
  add_error "law of demeter" if need_delegate?(node)
26
26
  end
@@ -28,8 +28,9 @@ module RailsBestPractices
28
28
 
29
29
  private
30
30
 
31
- def remember_belongs_to(node)
32
- node.body.grep_nodes(:message => :belongs_to).collect do |body_node|
31
+ # remember belongs_to or has_one node
32
+ def remember_association(node)
33
+ (node.body.grep_nodes(:message => :belongs_to) + node.body.grep_nodes(:message => :has_one)).collect do |body_node|
33
34
  class_name = node.subject.to_s.underscore
34
35
  @associations[class_name] ||= []
35
36
  @associations[class_name] << body_node.arguments[1].to_ruby_string
@@ -4,7 +4,7 @@ module RailsBestPractices
4
4
  module Checks
5
5
  # Check a view file to make sure there is no complex options_for_select message call.
6
6
  #
7
- # Implementation: Check if arguments of options_for_select message call contain more than 2 method call, then move it into helper.
7
+ # Implementation: Check if first argument of options_for_select is an array and contains more than two nodes, then it should be moved into helper.
8
8
  class MoveCodeIntoHelperCheck < Check
9
9
 
10
10
  def interesting_nodes
@@ -15,14 +15,19 @@ module RailsBestPractices
15
15
  VIEW_FILES
16
16
  end
17
17
 
18
+ def initialize(options = {})
19
+ super()
20
+ @array_count = options['array_count'] || 3
21
+ end
22
+
18
23
  def evaluate_start(node)
19
- add_error "move code into helper" if complex_select_options?(node)
24
+ add_error "move code into helper (array_count >= #{@array_count})" if complex_select_options?(node)
20
25
  end
21
26
 
22
27
  private
23
28
 
24
29
  def complex_select_options?(node)
25
- :options_for_select == node.message and node.arguments.grep_nodes(:node_type => :call).size > 2
30
+ :options_for_select == node.message and :array == node.arguments[1].node_type and node.arguments[1].size > @array_count
26
31
  end
27
32
  end
28
33
  end
@@ -0,0 +1,53 @@
1
+ require 'rails_best_practices/checks/check'
2
+
3
+ module RailsBestPractices
4
+ module Checks
5
+ # Check a view file to make sure there is no complex logic call for model.
6
+ #
7
+ # Implementation: Check if a local variable or instance variable called greater than 2 times in if or unless conditional statement, then it should more code into model.
8
+ class MoveCodeIntoModelCheck < Check
9
+
10
+ def interesting_nodes
11
+ [:if, :unless]
12
+ end
13
+
14
+ def interesting_files
15
+ VIEW_FILES
16
+ end
17
+
18
+ def evaluate_start(node)
19
+ @variables = {}
20
+ node.conditional_statement.grep_nodes(:node_type => :call).each { |call_node| remember_call(call_node) }
21
+ check_errors
22
+ end
23
+
24
+ private
25
+
26
+ def check_errors
27
+ @variables.each do |node, count|
28
+ add_error "move code into model (#{node.to_ruby})" if count > 2
29
+ end
30
+ end
31
+
32
+ def remember_call(call_node)
33
+ variable_node = variable(call_node)
34
+ if variable_node
35
+ @variables[variable_node] ||= 0
36
+ @variables[variable_node] += 1
37
+ end
38
+ end
39
+
40
+ def variable(call_node)
41
+ while call_node.subject.node_type == :call
42
+ call_node = call_node.subject
43
+ end
44
+ subject_node = call_node.subject
45
+ if [:ivar, :lvar].include?(subject_node.node_type) and subject_node[1] != :_erbout
46
+ subject_node
47
+ else
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -8,7 +8,7 @@ module RailsBestPractices
8
8
  class NeedlessDeepNestingCheck < Check
9
9
 
10
10
  def interesting_nodes
11
- [:call]
11
+ [:call, :iter]
12
12
  end
13
13
 
14
14
  def interesting_files
@@ -17,11 +17,28 @@ module RailsBestPractices
17
17
 
18
18
  def initialize(options = {})
19
19
  super()
20
+ @counter = 0
20
21
  @nested_count = options['nested_count'] || 2
21
22
  end
22
23
 
23
24
  def evaluate_start(node)
24
- if node.message == :resources
25
+ check_nested_count(node)
26
+ end
27
+
28
+ private
29
+ def check_nested_count(node)
30
+ if :iter == node.node_type
31
+ check_for_rails3(node)
32
+ elsif :resources == node.message and node.subject
33
+ check_for_rails2(node)
34
+ end
35
+ end
36
+
37
+ def check_for_rails3(node)
38
+ nested_count_for_rails3(node)
39
+ end
40
+
41
+ def check_for_rails2(node)
25
42
  if node.subject == s(:call, nil, :map, s(:arglist))
26
43
  @counter = 0
27
44
  else
@@ -29,7 +46,22 @@ module RailsBestPractices
29
46
  add_error "needless deep nesting (nested_count > #{@nested_count})" if @counter >= @nested_count
30
47
  end
31
48
  end
32
- end
49
+
50
+ def nested_count_for_rails3(node)
51
+ if :iter == node.node_type and :resources == node.subject.message and !node.message
52
+ @counter += 1
53
+ nested_count_for_rails3(node[3])
54
+ @counter -= 1
55
+ elsif :block == node.node_type
56
+ node.children.each do |child_node|
57
+ if :resources == child_node.message and nil == child_node.subject and @counter + 1 > @nested_count
58
+ add_error "needless deep nesting (nested_count > #{@nested_count})", child_node.file, child_node.line
59
+ end
60
+ end
61
+ elsif :call == node.node_type and :resources == node.message
62
+ add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter + 1 > @nested_count
63
+ end
64
+ end
33
65
  end
34
66
  end
35
- end
67
+ end
@@ -17,10 +17,11 @@ module RailsBestPractices
17
17
 
18
18
  def evaluate_start(node)
19
19
  if node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id"))) or
20
- node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format")))
20
+ node == s(:call, s(:lvar, :map), :connect, s(:arglist, s(:str, ":controller/:action/:id.:format"))) or
21
+ node == s(:call, nil, :match, s(:arglist, s(:str, ":controller(/:action(/:id(.:format)))")))
21
22
  add_error "not use default route"
22
23
  end
23
24
  end
24
25
  end
25
26
  end
26
- end
27
+ end