rails_best_practices 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/.gitignore +0 -1
  2. data/.travis.yml +1 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +45 -0
  5. data/README.md +2 -0
  6. data/assets/result.html.erb +78 -0
  7. data/lib/rails_best_practices.rb +5 -4
  8. data/lib/rails_best_practices/core.rb +2 -0
  9. data/lib/rails_best_practices/core/check.rb +27 -1
  10. data/lib/rails_best_practices/core/controllers.rb +7 -0
  11. data/lib/rails_best_practices/core/methods.rb +26 -0
  12. data/lib/rails_best_practices/core/runner.rb +3 -1
  13. data/lib/rails_best_practices/core_ext/sexp.rb +33 -8
  14. data/lib/rails_best_practices/prepares.rb +13 -0
  15. data/lib/rails_best_practices/prepares/controller_prepare.rb +74 -0
  16. data/lib/rails_best_practices/prepares/mailer_prepare.rb +3 -2
  17. data/lib/rails_best_practices/prepares/model_prepare.rb +12 -10
  18. data/lib/rails_best_practices/reviews.rb +1 -0
  19. data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +1 -1
  20. data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +1 -1
  21. data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +1 -1
  22. data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +149 -0
  23. data/lib/rails_best_practices/version.rb +1 -1
  24. data/rails_best_practices.gemspec +3 -3
  25. data/rails_best_practices.yml +1 -0
  26. data/spec/rails_best_practices/core/controllers_spec.rb +5 -0
  27. data/spec/rails_best_practices/core/methods_spec.rb +22 -0
  28. data/spec/rails_best_practices/core_ext/sexp_spec.rb +28 -1
  29. data/spec/rails_best_practices/prepares/controller_prepare_spec.rb +92 -0
  30. data/spec/rails_best_practices/prepares/model_prepare_spec.rb +22 -0
  31. data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +4 -4
  32. data/spec/rails_best_practices/reviews/restrict_auto_generated_routes_review_spec.rb +334 -0
  33. data/spec/spec_helper.rb +6 -0
  34. metadata +27 -13
  35. data/assets/result.html.haml +0 -63
data/.gitignore CHANGED
@@ -6,7 +6,6 @@
6
6
  pkg/**
7
7
  *.gem
8
8
  .bundle
9
- Gemfile.lock
10
9
  rdoc/**
11
10
  doc/**
12
11
  .yardoc/**
data/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ rvm: 1.9.2
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
  gemspec
3
+
4
+ gem "ripper", :platform => :mri_18
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rails_best_practices (1.1.0)
5
+ activesupport
6
+ colored
7
+ erubis
8
+ i18n
9
+ progressbar
10
+ sexp_processor
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ activesupport (3.0.7)
16
+ colored (1.2)
17
+ diff-lcs (1.1.2)
18
+ erubis (2.7.0)
19
+ haml (3.1.3)
20
+ i18n (0.5.0)
21
+ progressbar (0.9.1)
22
+ rake (0.8.7)
23
+ ripper (1.0.2)
24
+ rspec (2.4.0)
25
+ rspec-core (~> 2.4.0)
26
+ rspec-expectations (~> 2.4.0)
27
+ rspec-mocks (~> 2.4.0)
28
+ rspec-core (2.4.0)
29
+ rspec-expectations (2.4.0)
30
+ diff-lcs (~> 1.1.2)
31
+ rspec-mocks (2.4.0)
32
+ sexp_processor (3.0.5)
33
+ watchr (0.7)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ bundler
40
+ haml
41
+ rails_best_practices!
42
+ rake
43
+ ripper
44
+ rspec
45
+ watchr
data/README.md CHANGED
@@ -132,6 +132,7 @@ Now you can customize this configuration file, the default configuration is as f
132
132
  SimplifyRenderInControllersCheck: {}
133
133
  RemoveEmptyHelpersCheck: {}
134
134
  RemoveTabCheck: {}
135
+ RestrictAutoGeneratedRoutesCheck: { }
135
136
 
136
137
  You can remove or comment one review to disable it, and you can change the options.
137
138
 
@@ -152,6 +153,7 @@ RESTful Conventions
152
153
  1. Overuse route customizations
153
154
  2. Needless deep nesting
154
155
  3. Not use default route
156
+ 4. Restrict auto-generated routes
155
157
 
156
158
  Model
157
159
 
@@ -0,0 +1,78 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <meta charset='UTF-8' />
5
+ <title>Output of rails_best_practices</title>
6
+
7
+ <style type="text/css">
8
+ body {
9
+ color: #333;
10
+ background: #eee;
11
+ padding: 0 20px;
12
+ }
13
+ h1 {
14
+ color: ##4E4E4E;
15
+ }
16
+ table {
17
+ background: white;
18
+ border: 1px solid #666;
19
+ border-collapse: collapse;
20
+ margin: 20px 0;
21
+ font-size: 14px;
22
+ }
23
+ table th, table td {
24
+ padding: 4px;
25
+ border: 1px solid #D0D0D0;
26
+ }
27
+ table th {
28
+ background-color: #DFC;
29
+ color: #337022;
30
+ }
31
+ table td.filename {
32
+ color: #ED1556;
33
+ }
34
+ table tr:hover {
35
+ background-color: #FFFFC0;
36
+ }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <h1>rails_best_practices output</h1>
41
+ <h2>
42
+ Please go to
43
+ <a href='http://rails-bestpractices.com' target='_blank'>http://rails-bestpractices.com</a>
44
+ to see more useful Rails Best Practices.
45
+ </h2>
46
+ <h2>
47
+ <% if @errors.empty? %>
48
+ No error found. Cool!
49
+ <% else %>
50
+ Found <%= @errors.size %> errors.
51
+ <% end %>
52
+ </h2>
53
+ <table>
54
+ <tr>
55
+ <th>Filename</th>
56
+ <th>Line Number</th>
57
+ <th>Warning Message</th>
58
+ </tr>
59
+ <% @errors.each do |error| %>
60
+ <tr>
61
+ <td class='filename'>
62
+ <% if @textmate %>
63
+ <a href='txmt://open/?url=file://<%= File.expand_path(error.filename) %>&amp;line=<%= error.line_number %>'><%= error.filename %></a>
64
+ <% elsif @mvim %>
65
+ <a href='mvim://open/?url=file://<%= File.expand_path(error.filename) %>&amp;line=<%= error.line_number %>'><%= error.filename %></a>
66
+ <% else %>
67
+ <%= error.filename %>
68
+ <% end %>
69
+ </td>
70
+ <td class='line'><%= error.line_number %></td>
71
+ <td class='message'>
72
+ <a href='<%= error.url %>' target='_blank'><%= error.message %></a>
73
+ </td>
74
+ </tr>
75
+ <% end %>
76
+ </table>
77
+ </body>
78
+ </html>
@@ -25,7 +25,6 @@
25
25
  require 'rubygems'
26
26
  require 'progressbar'
27
27
  require 'colored'
28
- require 'haml'
29
28
  require 'rails_best_practices/lexicals'
30
29
  require 'rails_best_practices/prepares'
31
30
  require 'rails_best_practices/reviews'
@@ -113,7 +112,7 @@ module RailsBestPractices
113
112
  # @return [Array] all files for prepare process
114
113
  def prepare_files
115
114
  @prepare_files ||= begin
116
- ['app/models', 'app/mailers', 'db/schema.rb'].inject([]) { |files, name|
115
+ ['app/models', 'app/mailers', 'db/schema.rb', 'app/controllers'].inject([]) { |files, name|
117
116
  files += expand_dirs_to_files(File.join(@path, name))
118
117
  }.compact
119
118
  end
@@ -223,10 +222,12 @@ module RailsBestPractices
223
222
  end
224
223
 
225
224
  def output_html_errors
226
- template = File.read(File.join(File.dirname(__FILE__), "..", "assets", "result.html.haml"))
225
+ require 'erubis'
226
+ template = File.read(File.join(File.dirname(__FILE__), "..", "assets", "result.html.erb"))
227
227
 
228
228
  File.open("rails_best_practices_output.html", "w+") do |file|
229
- file.puts Haml::Engine.new(template).render(Object.new, :errors => @runner.errors, :textmate => @options["with-textmate"], :mvim => @options["with-mvim"])
229
+ eruby = Erubis::Eruby.new(template)
230
+ file.puts eruby.evaluate(:errors => @runner.errors, :textmate => @options["with-textmate"], :mvim => @options["with-mvim"])
230
231
  end
231
232
  end
232
233
  end
@@ -8,6 +8,8 @@ require 'rails_best_practices/core/models'
8
8
  require 'rails_best_practices/core/model_associations'
9
9
  require 'rails_best_practices/core/model_attributes'
10
10
  require 'rails_best_practices/core/mailers'
11
+ require 'rails_best_practices/core/methods'
12
+ require 'rails_best_practices/core/controllers'
11
13
 
12
14
  require 'rails_best_practices/core_ext/sexp'
13
15
  require 'rails_best_practices/core_ext/enumerable'
@@ -9,7 +9,7 @@ module RailsBestPractices
9
9
  MAILER_FILES = /models\/.*mailer\.rb$|mailers\/.*mailer\.rb/
10
10
  VIEW_FILES = /views\/.*\.(erb|haml)$/
11
11
  PARTIAL_VIEW_FILES = /views\/.*\/_.*\.(erb|haml)$/
12
- ROUTE_FILE = /config\/routes\.rb/
12
+ ROUTE_FILES = /config\/routes(.*)?\.rb/
13
13
  SCHEMA_FILE = /db\/schema\.rb/
14
14
  HELPER_FILES = /helpers.*\.rb$/
15
15
 
@@ -83,6 +83,32 @@ module RailsBestPractices
83
83
  super
84
84
  end
85
85
  end
86
+
87
+ module Classable
88
+ # remember module name.
89
+ def start_module(node)
90
+ modules << node.module_name
91
+ end
92
+
93
+ # end of the module.
94
+ def end_module(node)
95
+ modules.pop
96
+ end
97
+
98
+ # get the class name with module name.
99
+ def class_name(node)
100
+ class_name = node.class_name.to_s
101
+ if modules.empty?
102
+ class_name
103
+ else
104
+ modules.map { |modu| "#{modu}::" }.join("") + class_name
105
+ end
106
+ end
107
+
108
+ def modules
109
+ @moduels ||= []
110
+ end
111
+ end
86
112
  end
87
113
  end
88
114
  end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+ module RailsBestPractices
3
+ module Core
4
+ class Controllers < Array
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+ module RailsBestPractices
3
+ module Core
4
+ class Methods
5
+ def initialize
6
+ @methods = {}
7
+ end
8
+
9
+ def add_method(model_name, method_name)
10
+ @methods[model_name] ||= []
11
+ @methods[model_name] << method_name
12
+ end
13
+
14
+ def get_methods(model_name)
15
+ @methods[model_name] ||= []
16
+ @methods[model_name].to_a
17
+ end
18
+
19
+ def has_method?(model_name, method_name)
20
+ @methods[model_name] ||= []
21
+ @methods[model_name].include? method_name
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -126,6 +126,8 @@ module RailsBestPractices
126
126
  content = Haml::Engine.new(content).precompiled
127
127
  # remove \xxx characters
128
128
  content.gsub!(/\\\d{3}/, '')
129
+ rescue LoadError
130
+ raise "In order to parse #{filename}, please install the haml gem"
129
131
  rescue Haml::Error
130
132
  # do nothing, just ignore the wrong haml files.
131
133
  end
@@ -149,7 +151,7 @@ module RailsBestPractices
149
151
 
150
152
  # load all prepares.
151
153
  def load_prepares
152
- [Prepares::ModelPrepare.new, Prepares::MailerPrepare.new, Prepares::SchemaPrepare.new]
154
+ [Prepares::ModelPrepare.new, Prepares::MailerPrepare.new, Prepares::SchemaPrepare.new, Prepares::ControllerPrepare.new]
153
155
  end
154
156
 
155
157
  # load all reviews according to configuration.
@@ -120,6 +120,21 @@ class Sexp
120
120
  end
121
121
  end
122
122
 
123
+ # Get the module name of the module node.
124
+ #
125
+ # s(:module,
126
+ # s(:const_ref, s(:@const, "Admin", s(1, 7))),
127
+ # s(:bodystmt, s(:stmts_add, s(:stmts_new), s(:void_stmt)), nil, nil, nil)
128
+ # )
129
+ # => s(:const_ref, s(:@const, "Admin", s(1, 7))),
130
+ #
131
+ # @return [Sexp] module name node
132
+ def module_name
133
+ if :module == sexp_type
134
+ self[1]
135
+ end
136
+ end
137
+
123
138
  # Get the class name of the class node.
124
139
  #
125
140
  # s(:class,
@@ -234,6 +249,8 @@ class Sexp
234
249
  self[4]
235
250
  when :method_add_arg
236
251
  self[2].arguments
252
+ when :method_add_block
253
+ self[1].arguments
237
254
  when :arg_paren
238
255
  self[1]
239
256
  when :array
@@ -591,14 +608,16 @@ class Sexp
591
608
  if :array == sexp_type
592
609
  first_node = self[1]
593
610
  array_size = 0
594
- while true
595
- array_size += 1
596
- first_node = s(:args_new) == first_node[1] ? first_node[2] : first_node[1]
597
- if :args_add != first_node.sexp_type
598
- if :array == first_node.sexp_type
599
- array_size += first_node.array_size
611
+ if first_node
612
+ while true
613
+ array_size += 1
614
+ first_node = s(:args_new) == first_node[1] ? first_node[2] : first_node[1]
615
+ if :args_add != first_node.sexp_type
616
+ if :array == first_node.sexp_type
617
+ array_size += first_node.array_size
618
+ end
619
+ break
600
620
  end
601
- break
602
621
  end
603
622
  end
604
623
  array_size
@@ -619,7 +638,13 @@ class Sexp
619
638
  def to_object
620
639
  case sexp_type
621
640
  when :array
622
- arguments.all.map(&:to_s)
641
+ if nil == self[1]
642
+ []
643
+ else
644
+ arguments.all.map(&:to_s)
645
+ end
646
+ else
647
+ to_s
623
648
  end
624
649
  end
625
650
 
@@ -2,6 +2,7 @@
2
2
  require 'rails_best_practices/prepares/model_prepare'
3
3
  require 'rails_best_practices/prepares/mailer_prepare'
4
4
  require 'rails_best_practices/prepares/schema_prepare'
5
+ require 'rails_best_practices/prepares/controller_prepare'
5
6
 
6
7
  module RailsBestPractices
7
8
  module Prepares
@@ -23,6 +24,18 @@ module RailsBestPractices
23
24
  def mailers
24
25
  @mailers ||= Core::Mailers.new
25
26
  end
27
+
28
+ def controllers
29
+ @controllers ||= Core::Controllers.new
30
+ end
31
+
32
+ def controller_methods
33
+ @controller_methods ||= Core::Methods.new
34
+ end
35
+
36
+ def clear
37
+ @models = @model_associations = @model_attributes = @mailers = @controllers = @controller_methods = nil
38
+ end
26
39
  end
27
40
  end
28
41
  end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/core/check'
3
+
4
+ module RailsBestPractices
5
+ module Prepares
6
+ # Remember controllers and controller methods
7
+ class ControllerPrepare < Core::Check
8
+ include Core::Check::Classable
9
+
10
+ DEFAULT_ACTIONS = %w(index show new create edit update destroy)
11
+
12
+ def interesting_nodes
13
+ [:module, :class, :def, :command, :var_ref]
14
+ end
15
+
16
+ def interesting_files
17
+ CONTROLLER_FILES
18
+ end
19
+
20
+ def initialize
21
+ @controllers = Prepares.controllers
22
+ @methods = Prepares.controller_methods
23
+ @inherited_resources = false
24
+ end
25
+
26
+ # check class node to remember the class name.
27
+ # also check if the controller is inherit from InheritedResources::Base.
28
+ def start_class(node)
29
+ @class_name = class_name(node)
30
+ @controllers << @class_name
31
+ if "InheritedResources::Base" == node.base_class.to_s
32
+ @inherited_resources = true
33
+ @actions = DEFAULT_ACTIONS
34
+ end
35
+ end
36
+
37
+ # remember the action names at the end of class node if the controller is a InheritedResources.
38
+ def end_class(node)
39
+ if @inherited_resources
40
+ @actions.each do |action|
41
+ @methods.add_method(@class_name, action)
42
+ end
43
+ end
44
+ end
45
+
46
+ # check if there is a DSL call inherit_resources.
47
+ def start_var_ref(node)
48
+ if "inherit_resources" == node.to_s
49
+ @inherited_resources = true
50
+ @actions = DEFAULT_ACTIONS
51
+ end
52
+ end
53
+
54
+ # restrict actions for inherited_resources
55
+ def start_command(node)
56
+ if @inherited_resources && "actions" == node.message.to_s
57
+ @actions = node.arguments.all.map(&:to_s)
58
+ end
59
+ end
60
+
61
+ # check def node to remember all methods.
62
+ #
63
+ # the remembered methods (@methods) are like
64
+ # {
65
+ # "Post" => ["create", "destroy"],
66
+ # "Comment" => ["create"]
67
+ # }
68
+ def start_def(node)
69
+ method_name = node.method_name.to_s
70
+ @methods.add_method(@class_name, method_name)
71
+ end
72
+ end
73
+ end
74
+ end