rails_best_practices 0.6.7 → 0.7.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 (25) hide show
  1. data/.gemtest +0 -0
  2. data/README.md +32 -22
  3. data/Rakefile +1 -0
  4. data/lib/rails_best_practices.rb +7 -5
  5. data/lib/rails_best_practices/core/check.rb +22 -1
  6. data/lib/rails_best_practices/core/checking_visitor.rb +16 -7
  7. data/lib/rails_best_practices/core/runner.rb +50 -11
  8. data/lib/rails_best_practices/core/visitable_sexp.rb +10 -3
  9. data/lib/rails_best_practices/lexicals.rb +2 -0
  10. data/lib/rails_best_practices/lexicals/remove_trailing_whitespace_check.rb +26 -0
  11. data/lib/rails_best_practices/reviews.rb +1 -0
  12. data/lib/rails_best_practices/reviews/law_of_demeter_review.rb +0 -3
  13. data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +3 -3
  14. data/lib/rails_best_practices/reviews/review.rb +0 -15
  15. data/lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb +80 -0
  16. data/lib/rails_best_practices/reviews/use_observer_review.rb +0 -4
  17. data/lib/rails_best_practices/reviews/use_query_attribute_review.rb +0 -5
  18. data/lib/rails_best_practices/version.rb +1 -1
  19. data/rails_best_practices.yml +24 -22
  20. data/spec/rails_best_practices/core/checking_visitor_spec.rb +21 -1
  21. data/spec/rails_best_practices/core/visitable_sexp_spec.rb +10 -0
  22. data/spec/rails_best_practices/lexicals/remove_trailing_whitespace_check_spec.rb +39 -0
  23. data/spec/rails_best_practices/reviews/needless_deep_nesting_review_spec.rb +24 -0
  24. data/spec/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review_spec.rb +196 -0
  25. metadata +12 -4
data/.gemtest ADDED
File without changes
data/README.md CHANGED
@@ -83,28 +83,30 @@ to generate `rails_best_practices.yml` file.
83
83
 
84
84
  Now you can customize this configuration file, the default configuration is as follows:
85
85
 
86
- MoveFinderToNamedScopeReview: { }
87
- UseModelAssociationReview: { }
88
- UseScopeAccessReview: { }
89
- AddModelVirtualAttributeReview: { }
90
- ReplaceComplexCreationWithFactoryMethodReview: { attribute_assignment_count: 2 }
91
- MoveModelLogicIntoModelReview: { use_count: 4 }
92
- OveruseRouteCustomizationsReview: { customize_count: 3 }
93
- NeedlessDeepNestingReview: { nested_count: 2 }
94
- NotUseDefaultRouteReview: { }
95
- KeepFindersOnTheirOwnModelReview: { }
96
- LawOfDemeterReview: { }
97
- UseObserverReview: { }
98
- IsolateSeedDataReview: { }
99
- AlwaysAddDbIndexReview: { }
100
- UseBeforeFilterReview: { }
101
- MoveCodeIntoControllerReview: { }
102
- MoveCodeIntoModelReview: { use_count: 2 }
103
- MoveCodeIntoHelperReview: { array_count: 3 }
104
- ReplaceInstanceVariableWithLocalVariableReview: { }
105
- DryBundlerInCapistranoReview: { }
106
- UseSayWithTimeInMigrationsReview: { }
107
- UseQueryAttributeReview: { }
86
+ MoveFinderToNamedScopeCheck: { }
87
+ UseModelAssociationCheck: { }
88
+ UseScopeAccessCheck: { }
89
+ AddModelVirtualAttributeCheck: { }
90
+ ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
91
+ MoveModelLogicIntoModelCheck: { use_count: 4 }
92
+ OveruseRouteCustomizationsCheck: { customize_count: 3 }
93
+ NeedlessDeepNestingCheck: { nested_count: 2 }
94
+ NotUseDefaultRouteCheck: { }
95
+ KeepFindersOnTheirOwnModelCheck: { }
96
+ LawOfDemeterCheck: { }
97
+ UseObserverCheck: { }
98
+ IsolateSeedDataCheck: { }
99
+ AlwaysAddDbIndexCheck: { }
100
+ UseBeforeFilterCheck: { }
101
+ MoveCodeIntoControllerCheck: { }
102
+ MoveCodeIntoModelCheck: { use_count: 2 }
103
+ MoveCodeIntoHelperCheck: { array_count: 3 }
104
+ ReplaceInstanceVariableWithLocalVariableCheck: { }
105
+ DryBundlerInCapistranoCheck: { }
106
+ UseSayWithTimeInMigrationsCheck: { }
107
+ UseQueryAttributeCheck: { }
108
+ RemoveTrailingWhitespaceCheck: { }
109
+ UseMultipartAlternativeAsContentTypeOfEmailCheck: {}
108
110
 
109
111
  You can remove or comment one review to disable it, and you can change the options.
110
112
 
@@ -133,6 +135,10 @@ Model
133
135
  3. Use Observer
134
136
  4. Use Query Attribute
135
137
 
138
+ Mailer
139
+
140
+ 1. Use multipart/alternative as content_type of email
141
+
136
142
  Migration
137
143
 
138
144
  1. Isolating Seed Data
@@ -154,6 +160,10 @@ Deployment
154
160
 
155
161
  1. Dry bundler in capistrano
156
162
 
163
+ Other
164
+
165
+ 1. Remove Trailing Whitespace
166
+
157
167
  Contribute
158
168
  ----------
159
169
 
data/Rakefile CHANGED
@@ -35,3 +35,4 @@ Rspec::Core::RakeTask.new('spec:progress') do |spec|
35
35
  end
36
36
 
37
37
  task :default => :spec
38
+ task :test => :spec
@@ -26,6 +26,7 @@ require 'rubygems'
26
26
  require 'progressbar'
27
27
  require 'colored'
28
28
  require 'haml'
29
+ require 'rails_best_practices/lexicals'
29
30
  require 'rails_best_practices/prepares'
30
31
  require 'rails_best_practices/reviews'
31
32
  require 'rails_best_practices/core'
@@ -79,9 +80,8 @@ module RailsBestPractices
79
80
  plain_output("AlwaysAddDbIndexReview is disabled as there is no db/schema.rb file in your rails project.", 'blue')
80
81
  end
81
82
 
82
- @bar = ProgressBar.new('Analyzing', prepare_files.size + review_files.size)
83
- process("prepare")
84
- process("review")
83
+ @bar = ProgressBar.new('Analyzing', lexical_files.size + prepare_files.size + review_files.size)
84
+ ["lexical", "prepare", "review"].each { |process| send(:process, process) }
85
85
  @bar.finish
86
86
 
87
87
  if @options['format'] == 'html'
@@ -92,12 +92,12 @@ module RailsBestPractices
92
92
  exit @runner.errors.size
93
93
  end
94
94
 
95
- # process prepare or reivew.
95
+ # process lexical, prepare or reivew.
96
96
  #
97
97
  # get all files for the process, analyze each file,
98
98
  # and increment progress bar unless debug.
99
99
  #
100
- # @param [String] process the process name, prepare or review.
100
+ # @param [String] process the process name, lexical, prepare or review.
101
101
  def process(process)
102
102
  files = send("#{process}_files")
103
103
  files.each do |file|
@@ -137,6 +137,8 @@ module RailsBestPractices
137
137
  end
138
138
  end
139
139
 
140
+ alias :lexical_files :review_files
141
+
140
142
  # expand all files with extenstion rb, erb, haml and builder under the dirs
141
143
  #
142
144
  # @param [Array] dirs what directories to expand
@@ -9,11 +9,17 @@ module RailsBestPractices
9
9
  CONTROLLER_FILES = /_controller\.rb$/
10
10
  MIGRATION_FILES = /db\/migrate\/.*\.rb$/
11
11
  MODEL_FILES = /models\/.*\.rb$/
12
- MAILER_FILES = /models\/.*\.rb$|mailers\/.*\.rb/
12
+ MAILER_FILES = /models\/.*mailer\.rb$|mailers\/.*mailer\.rb/
13
13
  VIEW_FILES = /views\/.*\.(erb|haml)$/
14
14
  PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)$/
15
15
  ROUTE_FILE = /config\/routes.rb/
16
16
 
17
+ attr_reader :errors
18
+
19
+ def initialize
20
+ @errors = []
21
+ end
22
+
17
23
  # default interesting nodes.
18
24
  def interesting_nodes
19
25
  []
@@ -46,6 +52,21 @@ module RailsBestPractices
46
52
  self.send("end_#{node.node_type}", node)
47
53
  end
48
54
 
55
+ # add error if source code violates rails best practice.
56
+ # error is the string message for violation of the rails best practice
57
+ # file is the filename of source code
58
+ # line is the line number of the source code which is reviewing
59
+ def add_error(error, file = @node.file, line = @node.line)
60
+ @errors << RailsBestPractices::Core::Error.new("#{file}", "#{line}", error, url)
61
+ end
62
+
63
+ # default url is empty.
64
+ #
65
+ # @return [String] the url of rails best practice
66
+ def url
67
+ ""
68
+ end
69
+
49
70
  # method_missing to catch all start and end process for each node type, like
50
71
  #
51
72
  # start_defn
@@ -3,25 +3,26 @@ module RailsBestPractices
3
3
  module Core
4
4
  # CheckingVisitor is a visitor class.
5
5
  #
6
- # it remembers all the checks for prepare and review processes according to interesting_prepare_nodes and interesting_review_nodes,
6
+ # it remembers all the checks for prepare and review processes according to interesting_nodes and interesting_nodes,
7
7
  # then recursively iterate all sexp nodes,
8
8
  #
9
9
  # for prepare process
10
- # if the node_type and the node filename match the interesting_prepare_nodes and interesting_prepare_files,
10
+ # if the node_type and the node filename match the interesting_prepare_nodes and interesting_files,
11
11
  # then run the prepare for that node.
12
12
  #
13
13
  # for review process
14
- # if the node_type and the node filename match the interesting_review_nodes and interesting_review_files,
14
+ # if the node_type and the node filename match the interesting_review_nodes and interesting_files,
15
15
  # then run the reivew for that node.
16
16
  class CheckingVisitor
17
17
  # remember all the checks for prepare and review processes according to interesting_nodes.
18
18
  #
19
- # @param [Array] prepares
20
- # @param [Array] reviews
21
- def initialize(prepares, reviews)
19
+ # @param [Hash] options
20
+ # {:lexicals => [], :prepares => [], :reviews => []}
21
+ def initialize(options={})
22
+ @lexicals = options[:lexicals]
22
23
  [:prepare, :review].each do |process|
23
24
  instance_variable_set("@#{process}_checks", {}) # @review_checks = {}
24
- eval("#{process}s").each do |check| # reviews.each do |check|
25
+ options["#{process}s".to_sym].each do |check| # options[:reviews].each do |check|
25
26
  check.send("interesting_nodes").each do |node| # check.interesting_nodes.each do |node|
26
27
  instance_variable_get("@#{process}_checks")[node] ||= [] # @review_checks[node] ||= []
27
28
  instance_variable_get("@#{process}_checks")[node] << check # @review_checks[node] << check
@@ -31,6 +32,14 @@ module RailsBestPractices
31
32
  end
32
33
  end
33
34
 
35
+ # for lexical process
36
+ # check the content of files one by one.
37
+ def lexical(filename, content)
38
+ @lexicals.each do |lexical|
39
+ lexical.check(filename, content)
40
+ end
41
+ end
42
+
34
43
  # for prepare process
35
44
  # if the node_type and the node filename match the interesting_nodes and interesting_files,
36
45
  # then run the prepare for that node.
@@ -40,13 +40,30 @@ module RailsBestPractices
40
40
 
41
41
  prepares = Array(options[:prepares])
42
42
  reviews = Array(options[:reviews])
43
+ @lexicals = load_lexicals
43
44
  @prepares = prepares.empty? ? load_prepares : prepares
44
45
  @reviews = reviews.empty? ? load_reviews : reviews
45
46
 
46
- @checker ||= CheckingVisitor.new(@prepares, @reviews)
47
+ @checker ||= CheckingVisitor.new(:prepares => @prepares, :reviews => @reviews, :lexicals => @lexicals)
47
48
  @debug = false
48
49
  end
49
50
 
51
+ # lexical analysis the file.
52
+ #
53
+ # @param [String] filename name of the file
54
+ # @param [String] content content of the file
55
+ def lexical(filename, content)
56
+ puts filename if @debug
57
+ @checker.lexical(filename, content)
58
+ end
59
+
60
+ # lexical analysis the file.
61
+ #
62
+ # @param [String] filename
63
+ def lexical_file(filename)
64
+ lexical(filename, File.read(filename))
65
+ end
66
+
50
67
  # prepare and review a file's content with filename.
51
68
  # the file may be a ruby, erb or haml file.
52
69
  #
@@ -67,11 +84,11 @@ module RailsBestPractices
67
84
  EOS
68
85
  end
69
86
 
70
- # get all errors from reviews.
87
+ # get all errors from lexicals and reviews.
71
88
  #
72
- # @return [Array] all errors from reviews
89
+ # @return [Array] all errors from lexicals and reviews
73
90
  def errors
74
- @reviews.collect {|review| review.errors}.flatten
91
+ (@reviews + @lexicals).collect {|check| check.errors}.flatten
75
92
  end
76
93
 
77
94
  private
@@ -111,6 +128,20 @@ module RailsBestPractices
111
128
  content
112
129
  end
113
130
 
131
+ # load all lexical checks.
132
+ def load_lexicals
133
+ checks_from_config.inject([]) { |active_checks, check|
134
+ begin
135
+ check_name, options = *check
136
+ klass = RailsBestPractices::Lexicals.const_get(check_name)
137
+ active_checks << (options.empty? ? klass.new : klass.new(options))
138
+ rescue
139
+ # the check does not exist in the Lexicals namepace.
140
+ end
141
+ active_checks
142
+ }
143
+ end
144
+
114
145
  # load all prepares.
115
146
  def load_prepares
116
147
  [RailsBestPractices::Prepares::ModelPrepare.new, RailsBestPractices::Prepares::MailerPrepare.new]
@@ -118,13 +149,21 @@ module RailsBestPractices
118
149
 
119
150
  # load all reviews according to configuration.
120
151
  def load_reviews
121
- check_objects = []
122
- checks = YAML.load_file @config
123
- checks.each do |check|
124
- klass = RailsBestPractices::Reviews.const_get(check[0].gsub(/Check/, 'Review'))
125
- check_objects << (check[1].empty? ? klass.new : klass.new(check[1]))
126
- end
127
- check_objects
152
+ checks_from_config.inject([]) { |active_checks, check|
153
+ begin
154
+ check_name, options = *check
155
+ klass = RailsBestPractices::Reviews.const_get(check_name.gsub(/Check/, 'Review'))
156
+ active_checks << (options.empty? ? klass.new : klass.new(options))
157
+ rescue
158
+ # the check does not exist in the Reviews namepace.
159
+ end
160
+ active_checks
161
+ }
162
+ end
163
+
164
+ # read the checks from yaml config.
165
+ def checks_from_config
166
+ @checks ||= YAML.load_file @config
128
167
  end
129
168
  end
130
169
  end
@@ -400,6 +400,8 @@ class Sexp
400
400
  # @return [String] to_s
401
401
  def to_s(options={})
402
402
  case node_type
403
+ when :true, :false, :nil
404
+ self[0].to_s
403
405
  when :ivar
404
406
  options[:remove_at] ? self[1].to_s[1..-1] : self[1].to_s
405
407
  when :lvar, :str, :lit, :const
@@ -408,12 +410,17 @@ class Sexp
408
410
  "[\"#{self.children.collect(&:to_s).join('", "')}\"]"
409
411
  when :hash
410
412
  key_value = false # false is key, true is value
411
- result = ['{"']
413
+ result = ['{']
412
414
  children.each do |child|
413
- result << "#{child.to_s}#{key_value ? '", "' : '" => "'}"
415
+ if [:true, :false, :nil, :array, :hash].include? child.node_type
416
+ result << "#{child}"
417
+ else
418
+ result << "\"#{child}\""
419
+ end
420
+ result << (key_value ? ", " : " => ")
414
421
  key_value = !key_value
415
422
  end
416
- result.join("").sub(/, "$/, '') + '}'
423
+ result.join("").sub(/, $/, '') + '}'
417
424
  when :colon2
418
425
  "#{self[1]}::#{self[2]}"
419
426
  else
@@ -0,0 +1,2 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/lexicals/remove_trailing_whitespace_check'
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/core/check'
3
+
4
+ module RailsBestPractices
5
+ module Lexicals
6
+ # Make sure there are no trailing whitespace in codes.
7
+ #
8
+ # See the best practice details here http://rails-bestpractices.com/posts/60-remove-trailing-whitespace
9
+ class RemoveTrailingWhitespaceCheck < Core::Check
10
+ def url
11
+ "http://rails-bestpractices.com/posts/60-remove-trailing-whitespace"
12
+ end
13
+
14
+ # check if the content of file contain a trailing whitespace.
15
+ #
16
+ # @param [String] filename name of the file
17
+ # @param [String] content content of the file
18
+ def check(filename, content)
19
+ if content =~ / +\n/m
20
+ line_no = $`.count("\n") + 1
21
+ add_error("remove trailing whitespace", filename, line_no)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -21,3 +21,4 @@ require 'rails_best_practices/reviews/replace_instance_variable_with_local_varia
21
21
  require 'rails_best_practices/reviews/dry_bundler_in_capistrano_review'
22
22
  require 'rails_best_practices/reviews/use_say_with_time_in_migrations_review'
23
23
  require 'rails_best_practices/reviews/use_query_attribute_review'
24
+ require 'rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review'
@@ -9,9 +9,6 @@ module RailsBestPractices
9
9
  #
10
10
  # Implementation:
11
11
  #
12
- # Prepare process:
13
- # only review all model files to save model names and association names.
14
- #
15
12
  # Review process:
16
13
  # check all method calls to see if there is method call to the association object.
17
14
  # if there is a call node whose subject is an object of model (compare by name),
@@ -113,13 +113,13 @@ module RailsBestPractices
113
113
  # if so, it is the needless deep nesting.
114
114
  def recursively_check(node)
115
115
  if :iter == node.node_type && :resources == node.subject.message
116
+ options = eval(node.subject.arguments[2].to_s)
117
+ return if options["shallow"] == true
116
118
  @counter += 1
117
119
  recursively_check(node.body)
118
120
  @counter -= 1
119
121
  elsif :block == node.node_type
120
- node.children.each do |child_node|
121
- recursively_check(child_node)
122
- end
122
+ node.children.each { |child_node| recursively_check(child_node) }
123
123
  elsif :call == node.node_type && [:resources, :resource].include?(node.message)
124
124
  add_error "needless deep nesting (nested_count > #{@nested_count})", node.file, node.line if @counter >= @nested_count
125
125
  end
@@ -6,21 +6,6 @@ module RailsBestPractices
6
6
  module Reviews
7
7
  # A Review class that takes charge of reviewing one rails best practice.
8
8
  class Review < Core::Check
9
- attr_reader :errors
10
-
11
- def initialize
12
- super
13
- @errors = []
14
- end
15
-
16
- # add error if source code violates rails best practice.
17
- # error is the string message for violation of the rails best practice
18
- # file is the filename of source code
19
- # line is the line number of the source code which is reviewing
20
- def add_error(error, file = @node.file, line = @node.line)
21
- @errors << RailsBestPractices::Core::Error.new("#{file}", "#{line}", error, url)
22
- end
23
-
24
9
  # remember use count for the local or instance variable in the call or attrasgn node.
25
10
  #
26
11
  # find the local variable or instance variable in the call or attrasgn node,
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/core/runner'
3
+ require 'rails_best_practices/reviews/review'
4
+
5
+ module RailsBestPractices
6
+ module Reviews
7
+ # Make sure to use multipart/alternative as content_type of email.
8
+ #
9
+ # See the best practice details here http://rails-bestpractices.com/posts/41-use-multipart-alternative-as-content_type-of-email.
10
+ #
11
+ # Implementation:
12
+ #
13
+ # Review process:
14
+ # check class node to remember the class name,
15
+ # and check the method definition nodes to see if the corresponding mailer views exist or not.
16
+ class UseMultipartAlternativeAsContentTypeOfEmailReview < Review
17
+ def url
18
+ "http://rails-bestpractices.com/posts/41-use-multipart-alternative-as-content_type-of-email"
19
+ end
20
+
21
+ def interesting_nodes
22
+ [:class, :defn]
23
+ end
24
+
25
+ def interesting_files
26
+ MAILER_FILES
27
+ end
28
+
29
+ # check class node to remember the ActionMailer class name.
30
+ def start_class(node)
31
+ @klazz_name = node.class_name
32
+ end
33
+
34
+ # check defn node and find if the corresponding views exist or not?
35
+ def start_defn(node)
36
+ name = node.method_name
37
+ return unless deliver_method?(name)
38
+ if !rails2_mailer_views_exist?(name) && !rails3_mailer_views_exist?(name)
39
+ add_error("use multipart/alternative as content_type of email")
40
+ end
41
+ end
42
+
43
+ private
44
+ # check if rails2's syntax mailer views exist or not according to the method name.
45
+ # @param [String] name method name in action_mailer
46
+ def rails2_mailer_views_exist?(name)
47
+ (exist?("#{name}.text.plain.erb") && exist?("#{name}.text.html.erb")) ||
48
+ (exist?("#{name}.text.plain.haml") && exist?("#{name}.text.html.haml")) ||
49
+ (exist?("#{name}.text.plain.rhtml") && exist?("#{name}.text.html.rhtml"))
50
+ end
51
+
52
+ # check if rails3's syntax mailer views exist or not according to the method name.
53
+ #
54
+ # @param [String] name method name in action_mailer
55
+ def rails3_mailer_views_exist?(name)
56
+ (exist?("#{name}.text.erb") && exist?("#{name}.html.erb")) ||
57
+ (exist?("#{name}.text.haml") && exist?("#{name}.html.haml"))
58
+ end
59
+
60
+ # check if the filename existed in the mailer directory.
61
+ def exist?(filename)
62
+ File.exist? File.join(mailer_directory, filename)
63
+ end
64
+
65
+ # check if the method is a deliver_method.
66
+ #
67
+ # @param [String] name the name of the method
68
+ def deliver_method?(name)
69
+ Dir.entries(mailer_directory).find { |filename| filename.index name.to_s }
70
+ rescue
71
+ false
72
+ end
73
+
74
+ # the view directory of mailer.
75
+ def mailer_directory
76
+ File.join(Core::Runner.base_path, "app/views/#{@klazz_name.to_s.underscore}")
77
+ end
78
+ end
79
+ end
80
+ end
@@ -11,10 +11,6 @@ module RailsBestPractices
11
11
  #
12
12
  # Implementation:
13
13
  #
14
- # Prepare process:
15
- # check all class nodes to see if they are the subclass of ActionMailer::Base,
16
- # if so, remember the class name.
17
- #
18
14
  # Review process:
19
15
  # check all call nodes to see if they are callback definitions, like after_create, before_destroy,
20
16
  # if so, remember the callback methods.
@@ -9,11 +9,6 @@ module RailsBestPractices
9
9
  #
10
10
  # Implementation:
11
11
  #
12
- # Prepare process:
13
- # only check all model files to save model names and association names,
14
- # model names are saved as only when subject of call method is equal to one of the model name, then the call method may use query attribute instead,
15
- # association names are saved as association attributes should not be detected as query attributes.
16
- #
17
12
  # Review process:
18
13
  # check all method calls within conditional statements, like @user.login.nil?
19
14
  # if their subjects are one of the model names
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module RailsBestPractices
3
- VERSION = "0.6.7"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
 
@@ -1,22 +1,24 @@
1
- MoveFinderToNamedScopeReview: { }
2
- UseModelAssociationReview: { }
3
- UseScopeAccessReview: { }
4
- AddModelVirtualAttributeReview: { }
5
- ReplaceComplexCreationWithFactoryMethodReview: { attribute_assignment_count: 2 }
6
- MoveModelLogicIntoModelReview: { use_count: 4 }
7
- OveruseRouteCustomizationsReview: { customize_count: 3 }
8
- NeedlessDeepNestingReview: { nested_count: 2 }
9
- NotUseDefaultRouteReview: { }
10
- KeepFindersOnTheirOwnModelReview: { }
11
- LawOfDemeterReview: { }
12
- UseObserverReview: { }
13
- IsolateSeedDataReview: { }
14
- AlwaysAddDbIndexReview: { }
15
- UseBeforeFilterReview: { }
16
- MoveCodeIntoControllerReview: { }
17
- MoveCodeIntoModelReview: { use_count: 2 }
18
- MoveCodeIntoHelperReview: { array_count: 3 }
19
- ReplaceInstanceVariableWithLocalVariableReview: { }
20
- DryBundlerInCapistranoReview: { }
21
- UseSayWithTimeInMigrationsReview: { }
22
- UseQueryAttributeReview: { }
1
+ MoveFinderToNamedScopeCheck: { }
2
+ UseModelAssociationCheck: { }
3
+ UseScopeAccessCheck: { }
4
+ AddModelVirtualAttributeCheck: { }
5
+ ReplaceComplexCreationWithFactoryMethodCheck: { attribute_assignment_count: 2 }
6
+ MoveModelLogicIntoModelCheck: { use_count: 4 }
7
+ OveruseRouteCustomizationsCheck: { customize_count: 3 }
8
+ NeedlessDeepNestingCheck: { nested_count: 2 }
9
+ NotUseDefaultRouteCheck: { }
10
+ KeepFindersOnTheirOwnModelCheck: { }
11
+ LawOfDemeterCheck: { }
12
+ UseObserverCheck: { }
13
+ IsolateSeedDataCheck: { }
14
+ AlwaysAddDbIndexCheck: { }
15
+ UseBeforeFilterCheck: { }
16
+ MoveCodeIntoControllerCheck: { }
17
+ MoveCodeIntoModelCheck: { use_count: 2 }
18
+ MoveCodeIntoHelperCheck: { array_count: 3 }
19
+ ReplaceInstanceVariableWithLocalVariableCheck: { }
20
+ DryBundlerInCapistranoCheck: { }
21
+ UseSayWithTimeInMigrationsCheck: { }
22
+ UseQueryAttributeCheck: { }
23
+ RemoveTrailingWhitespaceCheck: { }
24
+ UseMultipartAlternativeAsContentTypeOfEmailCheck: {}
@@ -1,6 +1,10 @@
1
1
  # encoding: utf-8
2
2
  require 'spec_helper'
3
3
 
4
+ class TestLexical1
5
+ end
6
+ class TestLexical2
7
+ end
4
8
  class TestPrepare1
5
9
  def interesting_nodes
6
10
  [:call]
@@ -42,11 +46,27 @@ class TestReview2
42
46
  end
43
47
 
44
48
  describe RailsBestPractices::Core::CheckingVisitor do
49
+ let(:lexical1) { TestLexical1.new }
50
+ let(:lexical2) { TestLexical2.new }
45
51
  let(:prepare1) { TestPrepare1.new }
46
52
  let(:prepare2) { TestPrepare2.new }
47
53
  let(:review1) { TestReview1.new }
48
54
  let(:review2) { TestReview2.new }
49
- let(:visitor) { RailsBestPractices::Core::CheckingVisitor.new([prepare1, prepare2], [review1, review2]) }
55
+ let(:visitor) {
56
+ RailsBestPractices::Core::CheckingVisitor.new(
57
+ :lexicals => [lexical1, lexical2],
58
+ :prepares => [prepare1, prepare2],
59
+ :reviews => [review1, review2]
60
+ )
61
+ }
62
+
63
+ it "should lexical check" do
64
+ filename = "app/models/user.rb"
65
+ content = "class User; end"
66
+ lexical1.should_receive(:check).with(filename, content)
67
+ lexical2.should_receive(:check).with(filename, content)
68
+ visitor.lexical(filename, content)
69
+ end
50
70
 
51
71
  it "should prepare model associations" do
52
72
  node = stub(:node_type => :call, :children => [], :file => "app/models/user.rb")
@@ -254,6 +254,16 @@ describe Sexp do
254
254
  node.to_s.should == '{"first_name" => "Richard", "last_name" => "Huang"}'
255
255
  end
256
256
 
257
+ it "should get to_s for hash with true/false" do
258
+ node = parser.parse("{:shallow => true, :index => false}")
259
+ node.to_s.should == '{"shallow" => true, "index" => false}'
260
+ end
261
+
262
+ it "should get to_s for array and hash mixed" do
263
+ node = parser.parse("{:only => [:show, :index]}")
264
+ node.to_s.should == '{"only" => ["show", "index"]}'
265
+ end
266
+
257
267
  it "should get to_s for colon2" do
258
268
  node = parser.parse("RailsBestPractices::Core")
259
269
  node.to_s.should == "RailsBestPractices::Core"
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBestPractices::Lexicals::RemoveTrailingWhitespaceCheck do
4
+ let(:runner) { RailsBestPractices::Core::Runner.new(:lexicals => RailsBestPractices::Lexicals::RemoveTrailingWhitespaceCheck.new) }
5
+
6
+ it "should remove trailing whitespace" do
7
+ content =<<-EOF
8
+ class User < ActiveRecord::Base
9
+ has_many :projects
10
+ end
11
+ EOF
12
+ content.gsub!("\n", " \n")
13
+ runner.lexical('app/models/user.rb', content)
14
+ runner.should have(1).errors
15
+ runner.errors[0].to_s.should == "app/models/user.rb:1 - remove trailing whitespace"
16
+ end
17
+
18
+ it "should remove whitespace with third line" do
19
+ content =<<-EOF
20
+ class User < ActiveRecord::Base
21
+ has_many :projects
22
+ end
23
+ EOF
24
+ content.gsub!("d\n", "d \n")
25
+ runner.lexical('app/models/user.rb', content)
26
+ runner.should have(1).errors
27
+ runner.errors[0].to_s.should == "app/models/user.rb:3 - remove trailing whitespace"
28
+ end
29
+
30
+ it "should not remove trailing whitespace" do
31
+ content =<<-EOF
32
+ class User < ActiveRecord::Base
33
+ has_many :projects
34
+ end
35
+ EOF
36
+ runner.lexical('app/models/user.rb', content)
37
+ runner.should have(0).errors
38
+ end
39
+ end
@@ -17,6 +17,18 @@ describe RailsBestPractices::Reviews::NeedlessDeepNestingReview do
17
17
  runner.errors[0].to_s.should == "config/routes.rb:3 - needless deep nesting (nested_count > 2)"
18
18
  end
19
19
 
20
+ it "should not needless deep nesting for shallow" do
21
+ content = <<-EOF
22
+ map.resources :posts, :shallow => true do |post|
23
+ post.resources :comments do |comment|
24
+ comment.resources :favorites
25
+ end
26
+ end
27
+ EOF
28
+ runner.review('config/routes.rb', content)
29
+ runner.should have(0).errors
30
+ end
31
+
20
32
  it "should needless deep nesting with resource" do
21
33
  content = <<-EOF
22
34
  map.resources :posts do |post|
@@ -84,6 +96,18 @@ describe RailsBestPractices::Reviews::NeedlessDeepNestingReview do
84
96
  runner.errors[0].to_s.should == "config/routes.rb:4 - needless deep nesting (nested_count > 2)"
85
97
  end
86
98
 
99
+ it "should not needless deep nesting for shallow" do
100
+ content = <<-EOF
101
+ resources :posts, :shallow => true do
102
+ resources :comments do
103
+ resources :favorites
104
+ end
105
+ end
106
+ EOF
107
+ runner.review('config/routes.rb', content)
108
+ runner.should have(0).errors
109
+ end
110
+
87
111
  it "should needless deep nesting with resource" do
88
112
  content = <<-EOF
89
113
  resources :posts do
@@ -0,0 +1,196 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBestPractices::Reviews::UseMultipartAlternativeAsContentTypeOfEmailReview do
4
+ let(:runner) { RailsBestPractices::Core::Runner.new(:reviews => RailsBestPractices::Reviews::UseMultipartAlternativeAsContentTypeOfEmailReview.new) }
5
+
6
+ context "rails2" do
7
+ before :each do
8
+ RailsBestPractices::Core::Runner.stub!(:base_path).and_return(".")
9
+ end
10
+
11
+ it "should use mulipart/alternative as content_type of email" do
12
+ content =<<-EOF
13
+ class ProjectMailer < ActionMailer::Base
14
+ def send_email(email)
15
+ subject email.subject
16
+ from email.from
17
+ recipients email.recipients
18
+ sent_on Time.now
19
+ body :email => email
20
+ end
21
+ end
22
+ EOF
23
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
24
+ runner.review('app/mailers/project_mailer.rb', content)
25
+ runner.should have(1).errors
26
+ runner.errors[0].to_s.should == "app/mailers/project_mailer.rb:2 - use multipart/alternative as content_type of email"
27
+ end
28
+
29
+ it "should not use mulipart/alternative as content_type of email by erb" do
30
+ content =<<-EOF
31
+ class ProjectMailer < ActionMailer::Base
32
+ def send_email(email)
33
+ subject email.subject
34
+ from email.from
35
+ recipients email.recipients
36
+ sent_on Time.now
37
+ body :email => email
38
+ end
39
+ end
40
+ EOF
41
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
42
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.erb").and_return(true)
43
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.erb").and_return(true)
44
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.erb").and_return(false)
45
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.erb").and_return(false)
46
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.haml").and_return(false)
47
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.haml").and_return(false)
48
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.haml").and_return(false)
49
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.haml").and_return(false)
50
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.rhtml").and_return(false)
51
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.rhtml").and_return(false)
52
+ runner.review('app/mailers/project_mailer.rb', content)
53
+ runner.should have(0).errors
54
+ end
55
+
56
+ it "should not use mulipart/alternative as content_type of email by haml" do
57
+ content =<<-EOF
58
+ class ProjectMailer < ActionMailer::Base
59
+ def send_email(email)
60
+ subject email.subject
61
+ from email.from
62
+ recipients email.recipients
63
+ sent_on Time.now
64
+ body :email => email
65
+ end
66
+ end
67
+ EOF
68
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
69
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.erb").and_return(false)
70
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.erb").and_return(false)
71
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.erb").and_return(false)
72
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.erb").and_return(false)
73
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.haml").and_return(true)
74
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.haml").and_return(true)
75
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.haml").and_return(false)
76
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.haml").and_return(false)
77
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.rhtml").and_return(false)
78
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.rhtml").and_return(false)
79
+ runner.review('app/mailers/project_mailer.rb', content)
80
+ runner.should have(0).errors
81
+ end
82
+
83
+ it "should not use mulipart/alternative as content_type of email by rhtml" do
84
+ content =<<-EOF
85
+ class ProjectMailer < ActionMailer::Base
86
+ def send_email(email)
87
+ subject email.subject
88
+ from email.from
89
+ recipients email.recipients
90
+ sent_on Time.now
91
+ body :email => email
92
+ end
93
+ end
94
+ EOF
95
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
96
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.erb").and_return(false)
97
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.erb").and_return(false)
98
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.erb").and_return(false)
99
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.erb").and_return(false)
100
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.haml").and_return(false)
101
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.haml").and_return(false)
102
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.haml").and_return(false)
103
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.haml").and_return(false)
104
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.rhtml").and_return(true)
105
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.rhtml").and_return(true)
106
+ runner.review('app/mailers/project_mailer.rb', content)
107
+ runner.should have(0).errors
108
+ end
109
+
110
+ it "should not use mulipart/alternative as content_type of email for non deliver method" do
111
+ content =<<-EOF
112
+ class ProjectMailer < ActionMailer::Base
113
+ def no_deliver
114
+ end
115
+ end
116
+ EOF
117
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
118
+ runner.review('app/mailers/project_mailer.rb', content)
119
+ runner.should have(0).errors
120
+ end
121
+ end
122
+
123
+ context "rails3" do
124
+ it "should use mulipart/alternative as content_type of email" do
125
+ content =<<-EOF
126
+ class ProjectMailer < ActionMailer::Base
127
+ def send_email(email)
128
+ subject email.subject
129
+ from email.from
130
+ recipients email.recipients
131
+ sent_on Time.now
132
+ body :email => email
133
+ end
134
+ end
135
+ EOF
136
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
137
+ runner.review('app/mailers/project_mailer.rb', content)
138
+ runner.should have(1).errors
139
+ runner.errors[0].to_s.should == "app/mailers/project_mailer.rb:2 - use multipart/alternative as content_type of email"
140
+ end
141
+
142
+ it "should not use mulipart/alternative as content_type of email by erb" do
143
+ content =<<-EOF
144
+ class ProjectMailer < ActionMailer::Base
145
+ def send_email(email)
146
+ subject email.subject
147
+ from email.from
148
+ recipients email.recipients
149
+ sent_on Time.now
150
+ body :email => email
151
+ end
152
+ end
153
+ EOF
154
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
155
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.erb").and_return(false)
156
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.erb").and_return(false)
157
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.erb").and_return(true)
158
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.erb").and_return(true)
159
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.haml").and_return(false)
160
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.haml").and_return(false)
161
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.haml").and_return(false)
162
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.haml").and_return(false)
163
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.rhtml").and_return(false)
164
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.rhtml").and_return(false)
165
+ runner.review('app/mailers/project_mailer.rb', content)
166
+ runner.should have(0).errors
167
+ end
168
+
169
+ it "should not use mulipart/alternative as content_type of email by haml" do
170
+ content =<<-EOF
171
+ class ProjectMailer < ActionMailer::Base
172
+ def send_email(email)
173
+ subject email.subject
174
+ from email.from
175
+ recipients email.recipients
176
+ sent_on Time.now
177
+ body :email => email
178
+ end
179
+ end
180
+ EOF
181
+ Dir.stub!(:entries).with("./app/views/project_mailer").and_return(["send_email.html.erb"])
182
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.erb").and_return(false)
183
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.erb").and_return(false)
184
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.erb").and_return(false)
185
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.erb").and_return(false)
186
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.haml").and_return(false)
187
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.haml").and_return(false)
188
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.haml").and_return(true)
189
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.html.haml").and_return(true)
190
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.plain.rhtml").and_return(false)
191
+ File.stub!(:exist?).with("./app/views/project_mailer/send_email.text.html.rhtml").and_return(false)
192
+ runner.review('app/mailers/project_mailer.rb', content)
193
+ runner.should have(0).errors
194
+ end
195
+ end
196
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_best_practices
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 3
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 6
9
8
  - 7
10
- version: 0.6.7
9
+ - 0
10
+ version: 0.7.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Richard Huang
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-02-14 00:00:00 +08:00
18
+ date: 2011-02-15 00:00:00 +08:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -196,6 +196,7 @@ extensions: []
196
196
  extra_rdoc_files: []
197
197
 
198
198
  files:
199
+ - .gemtest
199
200
  - .gitignore
200
201
  - .rspec.example
201
202
  - .rvmrc.example
@@ -219,6 +220,8 @@ files:
219
220
  - lib/rails_best_practices/core/visitable_sexp.rb
220
221
  - lib/rails_best_practices/core_ext/enumerable.rb
221
222
  - lib/rails_best_practices/core_ext/nil_class.rb
223
+ - lib/rails_best_practices/lexicals.rb
224
+ - lib/rails_best_practices/lexicals/remove_trailing_whitespace_check.rb
222
225
  - lib/rails_best_practices/prepares.rb
223
226
  - lib/rails_best_practices/prepares/mailer_prepare.rb
224
227
  - lib/rails_best_practices/prepares/model_prepare.rb
@@ -242,6 +245,7 @@ files:
242
245
  - lib/rails_best_practices/reviews/review.rb
243
246
  - lib/rails_best_practices/reviews/use_before_filter_review.rb
244
247
  - lib/rails_best_practices/reviews/use_model_association_review.rb
248
+ - lib/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review.rb
245
249
  - lib/rails_best_practices/reviews/use_observer_review.rb
246
250
  - lib/rails_best_practices/reviews/use_query_attribute_review.rb
247
251
  - lib/rails_best_practices/reviews/use_say_with_time_in_migrations_review.rb
@@ -256,6 +260,7 @@ files:
256
260
  - spec/rails_best_practices/core/visitable_sexp_spec.rb
257
261
  - spec/rails_best_practices/core_ext/enumerable_spec.rb
258
262
  - spec/rails_best_practices/core_ext/nil_class_spec.rb
263
+ - spec/rails_best_practices/lexicals/remove_trailing_whitespace_check_spec.rb
259
264
  - spec/rails_best_practices/prepares/mailer_prepare_spec.rb
260
265
  - spec/rails_best_practices/prepares/model_prepare_spec.rb
261
266
  - spec/rails_best_practices/reviews/add_model_virtual_attribute_review_spec.rb
@@ -277,6 +282,7 @@ files:
277
282
  - spec/rails_best_practices/reviews/review_spec.rb
278
283
  - spec/rails_best_practices/reviews/use_before_filter_review_spec.rb
279
284
  - spec/rails_best_practices/reviews/use_model_association_review_spec.rb
285
+ - spec/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review_spec.rb
280
286
  - spec/rails_best_practices/reviews/use_observer_review_spec.rb
281
287
  - spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb
282
288
  - spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb
@@ -340,6 +346,7 @@ test_files:
340
346
  - spec/rails_best_practices/core/visitable_sexp_spec.rb
341
347
  - spec/rails_best_practices/core_ext/enumerable_spec.rb
342
348
  - spec/rails_best_practices/core_ext/nil_class_spec.rb
349
+ - spec/rails_best_practices/lexicals/remove_trailing_whitespace_check_spec.rb
343
350
  - spec/rails_best_practices/prepares/mailer_prepare_spec.rb
344
351
  - spec/rails_best_practices/prepares/model_prepare_spec.rb
345
352
  - spec/rails_best_practices/reviews/add_model_virtual_attribute_review_spec.rb
@@ -361,6 +368,7 @@ test_files:
361
368
  - spec/rails_best_practices/reviews/review_spec.rb
362
369
  - spec/rails_best_practices/reviews/use_before_filter_review_spec.rb
363
370
  - spec/rails_best_practices/reviews/use_model_association_review_spec.rb
371
+ - spec/rails_best_practices/reviews/use_multipart_alternative_as_content_type_of_email_review_spec.rb
364
372
  - spec/rails_best_practices/reviews/use_observer_review_spec.rb
365
373
  - spec/rails_best_practices/reviews/use_query_attribute_review_spec.rb
366
374
  - spec/rails_best_practices/reviews/use_say_with_time_in_migrations_review_spec.rb