rails_best_practices 1.4.0 → 1.5.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 (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