rails_best_practices 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/Gemfile +4 -0
  2. data/Gemfile.lock +18 -3
  3. data/Guardfile +19 -0
  4. data/README.md +5 -13
  5. data/lib/rails_best_practices.rb +3 -2
  6. data/lib/rails_best_practices/core.rb +1 -0
  7. data/lib/rails_best_practices/core/check.rb +37 -6
  8. data/lib/rails_best_practices/core/routes.rb +3 -1
  9. data/lib/rails_best_practices/core/runner.rb +4 -7
  10. data/lib/rails_best_practices/core_ext/erubis.rb +36 -0
  11. data/lib/rails_best_practices/core_ext/sexp.rb +1 -1
  12. data/lib/rails_best_practices/prepares/controller_prepare.rb +1 -1
  13. data/lib/rails_best_practices/prepares/model_prepare.rb +12 -6
  14. data/lib/rails_best_practices/prepares/route_prepare.rb +35 -16
  15. data/lib/rails_best_practices/prepares/schema_prepare.rb +2 -2
  16. data/lib/rails_best_practices/reviews/always_add_db_index_review.rb +3 -3
  17. data/lib/rails_best_practices/reviews/move_code_into_helper_review.rb +2 -2
  18. data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +3 -3
  19. data/lib/rails_best_practices/reviews/remove_unused_methods_in_controllers_review.rb +25 -5
  20. data/lib/rails_best_practices/reviews/remove_unused_methods_in_models_review.rb +7 -5
  21. data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +3 -3
  22. data/lib/rails_best_practices/reviews/simplify_render_in_controllers_review.rb +1 -1
  23. data/lib/rails_best_practices/reviews/simplify_render_in_views_review.rb +1 -1
  24. data/lib/rails_best_practices/version.rb +1 -1
  25. data/rails_best_practices.gemspec +4 -1
  26. data/spec/rails_best_practices/core/routes_spec.rb +12 -0
  27. data/spec/rails_best_practices/core_ext/erubis_spec.rb +24 -0
  28. data/spec/rails_best_practices/core_ext/sexp_spec.rb +5 -0
  29. data/spec/rails_best_practices/prepares/model_prepare_spec.rb +60 -0
  30. data/spec/rails_best_practices/prepares/route_prepare_spec.rb +67 -27
  31. data/spec/rails_best_practices/reviews/remove_unused_methods_in_controllers_review_spec.rb +68 -1
  32. data/spec/rails_best_practices/reviews/remove_unused_methods_in_models_review_spec.rb +1 -1
  33. data/spec/spec_helper.rb +20 -11
  34. metadata +65 -29
data/Gemfile CHANGED
@@ -2,3 +2,7 @@ source "http://rubygems.org"
2
2
  gemspec
3
3
 
4
4
  gem "ripper", :platform => :mri_18
5
+ if RUBY_PLATFORM =~ /darwin/i
6
+ gem 'rb-fsevent'
7
+ gem 'growl'
8
+ end
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_best_practices (1.4.0)
4
+ rails_best_practices (1.5.0)
5
5
  activesupport
6
6
  colored
7
7
  erubis
@@ -16,10 +16,19 @@ GEM
16
16
  colored (1.2)
17
17
  diff-lcs (1.1.2)
18
18
  erubis (2.7.0)
19
+ growl (1.0.3)
20
+ guard (0.8.8)
21
+ thor (~> 0.14.6)
22
+ guard-rspec (0.5.7)
23
+ guard (>= 0.8.4)
24
+ guard-spork (0.3.2)
25
+ guard (>= 0.8.4)
26
+ spork (>= 0.8.4)
19
27
  haml (3.1.3)
20
28
  i18n (0.5.0)
21
29
  progressbar (0.9.1)
22
30
  rake (0.8.7)
31
+ rb-fsevent (0.4.3.1)
23
32
  ripper (1.0.2)
24
33
  rspec (2.4.0)
25
34
  rspec-core (~> 2.4.0)
@@ -30,16 +39,22 @@ GEM
30
39
  diff-lcs (~> 1.1.2)
31
40
  rspec-mocks (2.4.0)
32
41
  sexp_processor (3.0.5)
33
- watchr (0.7)
42
+ spork (0.9.0.rc9)
43
+ thor (0.14.6)
34
44
 
35
45
  PLATFORMS
36
46
  ruby
37
47
 
38
48
  DEPENDENCIES
39
49
  bundler
50
+ growl
51
+ guard
52
+ guard-rspec
53
+ guard-spork
40
54
  haml
41
55
  rails_best_practices!
42
56
  rake
57
+ rb-fsevent
43
58
  ripper
44
59
  rspec
45
- watchr
60
+ spork (= 0.9.0.rc9)
@@ -0,0 +1,19 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'spork', :rspec_env => { 'RAILS_ENV' => 'test' }, :cucumber => false, :test_unit => false, :bundler => false do
5
+ watch('config/application.rb')
6
+ watch('config/environment.rb')
7
+ watch(%r{^config/environments/.+\.rb$})
8
+ watch(%r{^config/initializers/.+\.rb$})
9
+ watch('Gemfile')
10
+ watch('Gemfile.lock')
11
+ watch('spec/spec_helper.rb')
12
+ end
13
+
14
+
15
+ guard 'rspec', :version => 2, :all_after_pass => false, :all_on_start => false, :cli => "--color --format nested --fail-fast --drb" do
16
+ watch(%r{^spec/.+_spec\.rb$})
17
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
18
+ watch('spec/spec_helper.rb') { "spec" }
19
+ end
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  rails_best_practices
2
2
  ====================
3
3
 
4
- rails_best_practices is a code metric tool to check the quality of rails codes.
4
+ [![Build Status](https://secure.travis-ci.org/flyerhzm/rails_best_practices.png)](http://travis-ci.org/flyerhzm/rails_best_practices)
5
+
6
+ [![Click here to lend your support to: rails-bestpractices.com and make a donation at www.pledgie.com !](https://www.pledgie.com/campaigns/12057.png?skin_name=chrome)](http://www.pledgie.com/campaigns/12057)
7
+
8
+ rails_best_practices is a code metric tool to check the quality of rails codes. It supports both activerecord and mongoid from version 1.5.0.
5
9
 
6
10
  Usage
7
11
  -----
@@ -69,18 +73,6 @@ or add in Gemfile
69
73
 
70
74
  gem "rails_best_practices"
71
75
 
72
- if you still want to use rails_best_practices gem in ruby 1.8, please add ripper gem dependency before rails_best_practices.
73
-
74
- Ruby 1.8
75
-
76
- gem install ripper
77
- gem install rails_best_practices
78
-
79
- or add in Gemfile
80
-
81
- gem "ripper"
82
- gem "rails_best_practices"
83
-
84
76
  Issue
85
77
  -----
86
78
 
@@ -22,14 +22,15 @@
22
22
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
  #++
25
- require 'rubygems'
25
+ require 'fileutils'
26
+
26
27
  require 'progressbar'
27
28
  require 'colored'
29
+
28
30
  require 'rails_best_practices/lexicals'
29
31
  require 'rails_best_practices/prepares'
30
32
  require 'rails_best_practices/reviews'
31
33
  require 'rails_best_practices/core'
32
- require 'fileutils'
33
34
 
34
35
  # RailsBestPractices helps you to analyze your rails code, according to best practices on http://rails-bestpractices.
35
36
  # if it finds any violatioins to best practices, it will give you some readable suggestions.
@@ -15,3 +15,4 @@ require 'rails_best_practices/core/routes'
15
15
 
16
16
  require 'rails_best_practices/core_ext/sexp'
17
17
  require 'rails_best_practices/core_ext/enumerable'
18
+ require 'rails_best_practices/core_ext/erubis'
@@ -3,19 +3,24 @@ module RailsBestPractices
3
3
  module Core
4
4
  # A Check class that takes charge of checking the sexp.
5
5
  class Check
6
-
7
6
  ALL_FILES = /.*/
8
- CONTROLLER_FILES = /controllers\/.*\.rb$/
7
+ CONTROLLER_FILES = /(controllers|cells)\/.*\.rb$/
9
8
  MIGRATION_FILES = /db\/migrate\/.*\.rb$/
10
9
  MODEL_FILES = /models\/.*\.rb$/
11
10
  MAILER_FILES = /models\/.*mailer\.rb$|mailers\/.*mailer\.rb/
12
- VIEW_FILES = /views\/.*\.(erb|haml)$/
13
- PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)$/
11
+ VIEW_FILES = /(views|cells)\/.*\.(erb|haml)$/
12
+ PARTIAL_VIEW_FILES = /(views|cells)\/.*\/_.*\.(erb|haml)$/
14
13
  ROUTE_FILES = /config\/routes.*\.rb/
15
14
  SCHEMA_FILE = /db\/schema\.rb/
16
15
  HELPER_FILES = /helpers\/.*\.rb$/
17
16
  DEPLOY_FILES = /config\/deploy.*\.rb/
18
17
 
18
+ def initialize(options={})
19
+ options.each do |key, value|
20
+ instance_variable_set("@#{key}", value)
21
+ end
22
+ end
23
+
19
24
  # interesting nodes that the check will parse.
20
25
  def interesting_nodes
21
26
  self.class.interesting_nodes
@@ -221,9 +226,14 @@ module RailsBestPractices
221
226
  when "alias_method_chain"
222
227
  method, feature = *node.arguments.all.map(&:to_s)
223
228
  call_method("#{method}_with_#{feature}")
229
+ when /(before|after)_/
230
+ node.arguments.all.each { |argument| mark_used(argument) }
224
231
  else
225
232
  mark_used(node.message)
226
- node.arguments.all.each { |argument| mark_used(argument) }
233
+ last_argument = node.arguments.all.last
234
+ if last_argument.present? && :bare_assoc_hash == last_argument.sexp_type
235
+ last_argument.hash_values.each { |argument_value| mark_used(argument_value) }
236
+ end
227
237
  end
228
238
  end
229
239
 
@@ -254,7 +264,7 @@ module RailsBestPractices
254
264
  when "try"
255
265
  mark_used(node.arguments.all.first)
256
266
  when "send"
257
- if [:symbol_literal, :string_literal].include?(node.arguments.all[0].sexp_type)
267
+ if [:symbol_literal, :string_literal].include?(node.arguments.all.first.sexp_type)
258
268
  mark_used(node.arguments.all.first)
259
269
  end
260
270
  else
@@ -310,6 +320,27 @@ module RailsBestPractices
310
320
  end
311
321
  end
312
322
 
323
+ # Helper to check except methods.
324
+ module Exceptable
325
+ def self.included(base)
326
+ base.class_eval do
327
+ def except_methods
328
+ @except_methods + internal_except_methods
329
+ end
330
+
331
+ # check if the method is in the except methods list.
332
+ def excepted?(method)
333
+ except_methods.any? do |except_method|
334
+ class_name, method_name = except_method.split('#')
335
+ (class_name == '*' && method_name == method.method_name) ||
336
+ (method_name == '*' && class_name == method.class_name) ||
337
+ (class_name == method.class_name && method_name == method.method_name)
338
+ end
339
+ end
340
+ end
341
+ end
342
+ end
343
+
313
344
  # Helper to parse the access control.
314
345
  module Accessable
315
346
  def self.included(base)
@@ -17,7 +17,9 @@ module RailsBestPractices
17
17
 
18
18
  def initialize(namespaces, controller_name, action_name)
19
19
  @namespaces = namespaces
20
- @controller_name = controller_name
20
+ entities = controller_name.split('/')
21
+ @namespaces += entities[0..-2] if entities.size > 1
22
+ @controller_name = entities.last
21
23
  @action_name = action_name
22
24
  end
23
25
 
@@ -1,11 +1,8 @@
1
1
  # encoding: utf-8
2
- require 'rubygems'
3
- require 'ripper'
4
- require 'erubis'
5
2
  require 'yaml'
3
+ require 'ripper'
6
4
  require 'active_support/inflector'
7
5
  require 'active_support/core_ext/object/blank'
8
- require 'active_support/core_ext/object/try'
9
6
 
10
7
  module RailsBestPractices
11
8
  module Core
@@ -151,12 +148,12 @@ module RailsBestPractices
151
148
  # @param [String] filename is the filename of the erb or haml code.
152
149
  # @param [String] content is the source code of erb or haml file.
153
150
  def parse_erb_or_haml(filename, content)
154
- if filename =~ /.*\.erb|.*\.rhtml$/
155
- content = Erubis::Eruby.new(content).src
151
+ if filename =~ /.*\.erb$|.*\.rhtml$/
152
+ content = Erubis::OnlyRuby.new(content).src
156
153
  elsif filename =~ /.*\.haml$/
157
154
  begin
158
155
  require 'haml'
159
- content = Haml::Engine.new(content).precompiled
156
+ content = Haml::Engine.new(content, {:ugly => true}).precompiled
160
157
  # remove \xxx characters
161
158
  content.gsub!(/\\\d{3}/, '')
162
159
  rescue LoadError
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ require 'erubis'
3
+
4
+ module Erubis
5
+ class OnlyRuby < Eruby
6
+ def add_preamble(src)
7
+ end
8
+
9
+ def add_text(src, text)
10
+ src << text.gsub(/[^\s;]/, '')
11
+ end
12
+
13
+ def add_stmt(src, code)
14
+ src << code
15
+ src << ";"
16
+ end
17
+
18
+ def add_expr_literal(src, code)
19
+ src << code
20
+ src << ";"
21
+ end
22
+
23
+ def add_expr_escaped(src, code)
24
+ src << code
25
+ src << ";"
26
+ end
27
+
28
+ def add_expr_debug(src, code)
29
+ src << code
30
+ src << ";"
31
+ end
32
+
33
+ def add_postamble(src)
34
+ end
35
+ end
36
+ end
@@ -26,7 +26,7 @@ class Sexp
26
26
  :alias, :symbol_literal, :symbol, :aref].include? sexp_type
27
27
  self[1].line
28
28
  elsif :array == sexp_type
29
- array_values[0].line
29
+ array_values.first.line
30
30
  else
31
31
  self.last.first if self.last.is_a? Array
32
32
  end
@@ -48,7 +48,7 @@ module RailsBestPractices
48
48
  # restrict actions for inherited_resources
49
49
  def start_command(node)
50
50
  if @inherited_resources && "actions" == node.message.to_s
51
- if "all" == node.arguments.all[0].to_s
51
+ if "all" == node.arguments.all.first.to_s
52
52
  @actions = DEFAULT_ACTIONS
53
53
  option_argument = node.arguments.all[1]
54
54
  if :bare_assoc_hash == option_argument.sexp_type && option_argument.hash_value("except")
@@ -11,15 +11,16 @@ module RailsBestPractices
11
11
  interesting_nodes :class, :def, :command, :var_ref, :alias
12
12
  interesting_files MODEL_FILES
13
13
 
14
- ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many)
14
+ ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many embeds_many embeds_one embedded_in)
15
15
 
16
16
  def initialize
17
17
  @models = Prepares.models
18
18
  @model_associations = Prepares.model_associations
19
+ @model_attributes = Prepares.model_attributes
19
20
  @methods = Prepares.model_methods
20
21
  end
21
22
 
22
- # check class node to remember the last class name.
23
+ # remember the class name.
23
24
  def start_class(node)
24
25
  if "ActionMailer::Base" != current_extend_class_name
25
26
  @models << @klass
@@ -59,12 +60,17 @@ module RailsBestPractices
59
60
  def start_command(node)
60
61
  case node.message.to_s
61
62
  when *%w(named_scope scope alias_method)
62
- method_name = node.arguments.all[0].to_s
63
+ method_name = node.arguments.all.first.to_s
63
64
  @methods.add_method(current_class_name, method_name, {"file" => node.file, "line" => node.line}, current_access_control)
64
65
  when "alias_method_chain"
65
66
  method, feature = *node.arguments.all.map(&:to_s)
66
67
  @methods.add_method(current_class_name, "#{method}_with_#{feature}", {"file" => node.file, "line" => node.line}, current_access_control)
67
68
  @methods.add_method(current_class_name, "#{method}", {"file" => node.file, "line" => node.line}, current_access_control)
69
+ when "field"
70
+ arguments = node.arguments.all
71
+ attribute_name = arguments.first.to_s
72
+ attribute_type = arguments.last.hash_value("type").present? ? arguments.last.hash_value("type").to_s : "String"
73
+ @model_attributes.add_attribute(current_class_name, attribute_name, attribute_type)
68
74
  when *ASSOCIATION_METHODS
69
75
  remember_association(node)
70
76
  else
@@ -81,9 +87,9 @@ module RailsBestPractices
81
87
  # remember associations, with class to association names.
82
88
  def remember_association(node)
83
89
  association_meta = node.message.to_s
84
- association_name = node.arguments.all[0].to_s
85
- arguments_node = node.arguments.all[1]
86
- if arguments_node && :bare_assoc_hash == arguments_node.sexp_type
90
+ association_name = node.arguments.all.first.to_s
91
+ arguments_node = node.arguments.all.last
92
+ if arguments_node.hash_value("class_name").present?
87
93
  association_class = arguments_node.hash_value("class_name").to_s
88
94
  end
89
95
  association_class ||= association_name.classify
@@ -24,10 +24,22 @@ module RailsBestPractices
24
24
  when "resource"
25
25
  add_resource_routes(node)
26
26
  when "get", "post", "put", "delete"
27
- action_name = node.arguments.all.first.to_s
28
- @routes.add_route(current_namespaces, current_resource_name, action_name)
27
+ first_argument = node.arguments.all.first
28
+ if current_controller_name.present?
29
+ action_name = first_argument.to_s
30
+ @routes.add_route(current_namespaces, current_controller_name, action_name)
31
+ else
32
+ if :bare_assoc_hash == first_argument.sexp_type
33
+ route_node = first_argument.hash_values.first
34
+ controller_name, action_name = route_node.to_s.split('#')
35
+ else
36
+ controller_name, action_name = first_argument.to_s.split('/')
37
+ end
38
+ @routes.add_route(current_namespaces, controller_name.underscore, action_name)
39
+ end
29
40
  when "match", "root"
30
41
  options = node.arguments.all.last
42
+ return if :string_literal == options.sexp_type
31
43
  if options.hash_value("controller").present?
32
44
  controller_name = options.hash_value("controller").to_s
33
45
  action_name = options.hash_value("action").present? ? options.hash_value("action").to_s : "*"
@@ -55,9 +67,11 @@ module RailsBestPractices
55
67
  # nothing to do
56
68
  else
57
69
  options = node.arguments.all.last
58
- controller_name = options.hash_value("controller").to_s
70
+ if options.hash_value("controller").present?
71
+ @controller_name = options.hash_value("controller").to_s
72
+ end
59
73
  action_name = options.hash_value("action").present? ? options.hash_value("action").to_s : "*"
60
- @routes.add_route(current_namespaces, controller_name, action_name)
74
+ @routes.add_route(current_namespaces, current_controller_name, action_name)
61
75
  end
62
76
  end
63
77
 
@@ -65,6 +79,11 @@ module RailsBestPractices
65
79
  def start_method_add_block(node)
66
80
  if "namespace" == node.message.to_s
67
81
  @namespaces << node.arguments.all.first.to_s
82
+ elsif "with_options" == node.message.to_s
83
+ argument = node.arguments.all.last
84
+ if :bare_assoc_hash == argument.sexp_type && argument.hash_value("controller").present?
85
+ @controller_name = argument.hash_value("controller").to_s
86
+ end
68
87
  end
69
88
  end
70
89
 
@@ -80,27 +99,27 @@ module RailsBestPractices
80
99
  def add_#{route_name}_routes(node)
81
100
  resource_names = node.arguments.all.select { |argument| :symbol_literal == argument.sexp_type }
82
101
  resource_names.each do |resource_name|
83
- @resource_name = node.arguments.all.first.to_s
102
+ @controller_name = node.arguments.all.first.to_s
84
103
  options = node.arguments.all.last
85
104
  if options.hash_value("controller").present?
86
- @resource_name = options.hash_value("controller").to_s
105
+ @controller_name = options.hash_value("controller").to_s
87
106
  end
88
107
  action_names = if options.hash_value("only").present?
89
108
  get_#{route_name}_actions(options.hash_value("only").to_object)
90
109
  elsif options.hash_value("except").present?
91
- self.class.const_get(:#{route_name.upcase}_ACTIONS) - get_#{route_name}_actions(options.hash_value("except").to_object)
110
+ self.class.const_get(:#{route_name.to_s.upcase}_ACTIONS) - get_#{route_name}_actions(options.hash_value("except").to_object)
92
111
  else
93
- self.class.const_get(:#{route_name.upcase}_ACTIONS)
112
+ self.class.const_get(:#{route_name.to_s.upcase}_ACTIONS)
94
113
  end
95
- Array(action_names).each do |action_name|
96
- @routes.add_route(current_namespaces, current_resource_name, action_name)
114
+ action_names.each do |action_name|
115
+ @routes.add_route(current_namespaces, current_controller_name, action_name)
97
116
  end
98
117
 
99
118
  member_routes = options.hash_value("member")
100
119
  if member_routes.present?
101
120
  action_names = :array == member_routes.sexp_type ? member_routes.to_object : member_routes.hash_keys
102
121
  action_names.each do |action_name|
103
- @routes.add_route(current_namespaces, current_resource_name, action_name)
122
+ @routes.add_route(current_namespaces, current_controller_name, action_name)
104
123
  end
105
124
  end
106
125
 
@@ -108,7 +127,7 @@ module RailsBestPractices
108
127
  if collection_routes.present?
109
128
  action_names = :array == collection_routes.sexp_type ? collection_routes.to_object : collection_routes.hash_keys
110
129
  action_names.each do |action_name|
111
- @routes.add_route(current_namespaces, current_resource_name, action_name)
130
+ @routes.add_route(current_namespaces, current_controller_name, action_name)
112
131
  end
113
132
  end
114
133
  end
@@ -117,11 +136,11 @@ module RailsBestPractices
117
136
  def get_#{route_name}_actions(action_names)
118
137
  case action_names
119
138
  when "all"
120
- self.class.const_get(:#{route_name.upcase}_ACTIONS)
139
+ self.class.const_get(:#{route_name.to_s.upcase}_ACTIONS)
121
140
  when "none"
122
141
  []
123
142
  else
124
- action_names
143
+ Array(action_names)
125
144
  end
126
145
  end
127
146
 
@@ -134,8 +153,8 @@ module RailsBestPractices
134
153
  @namespaces.dup
135
154
  end
136
155
 
137
- def current_resource_name
138
- @resource_name
156
+ def current_controller_name
157
+ @controller_name
139
158
  end
140
159
  end
141
160
  end