rails_best_practices 1.0.1 → 1.1.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 (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
@@ -5,9 +5,10 @@ module RailsBestPractices
5
5
  module Prepares
6
6
  # Remember the mailer names.
7
7
  class MailerPrepare < Core::Check
8
+ include Core::Check::Classable
8
9
 
9
10
  def interesting_nodes
10
- [:class]
11
+ [:class, :module]
11
12
  end
12
13
 
13
14
  def interesting_files
@@ -24,7 +25,7 @@ module RailsBestPractices
24
25
  # then remember its class name.
25
26
  def start_class(node)
26
27
  if "ActionMailer::Base" == node.base_class.to_s
27
- @mailers << node.class_name.to_s
28
+ @mailers << class_name(node)
28
29
  end
29
30
  end
30
31
  end
@@ -3,12 +3,14 @@ require 'rails_best_practices/core/check'
3
3
 
4
4
  module RailsBestPractices
5
5
  module Prepares
6
- # Remember the model associations.
6
+ # Remember models and model associations.
7
7
  class ModelPrepare < Core::Check
8
+ include Core::Check::Classable
9
+
8
10
  ASSOCIATION_METHODS = %w(belongs_to has_one has_many has_and_belongs_to_many)
9
11
 
10
12
  def interesting_nodes
11
- [:class, :command]
13
+ [:class, :command, :module]
12
14
  end
13
15
 
14
16
  def interesting_files
@@ -22,19 +24,19 @@ module RailsBestPractices
22
24
 
23
25
  # check class node to remember the last class name.
24
26
  def start_class(node)
25
- @last_klazz= node.class_name.to_s
26
- @models << @last_klazz
27
+ @class_name= class_name(node)
28
+ @models << @class_name
27
29
  end
28
30
 
29
31
  # check command node to remember all assoications.
30
32
  #
31
33
  # the remembered association names (@associations) are like
32
34
  # {
33
- # :Project=>{
34
- # "categories" => {:has_and_belongs_to_many => "Category"},
35
- # "project_manager" => {:has_one => "ProjectManager"},
36
- # "portfolio" => {:belongs_to => "Portfolio"},
37
- # "milestones => {:has_many" => "Milestone"}
35
+ # "Project" => {
36
+ # "categories" => {"has_and_belongs_to_many" => "Category"},
37
+ # "project_manager" => {"has_one" => "ProjectManager"},
38
+ # "portfolio" => {"belongs_to" => "Portfolio"},
39
+ # "milestones => {"has_many" => "Milestone"}
38
40
  # }
39
41
  # }
40
42
  def start_command(node)
@@ -50,7 +52,7 @@ module RailsBestPractices
50
52
  association_class = arguments_node.hash_value("class_name").to_s
51
53
  end
52
54
  association_class ||= association_name.classify
53
- @model_associations.add_association(@last_klazz, association_name, association_meta, association_class)
55
+ @model_associations.add_association(@class_name, association_name, association_meta, association_class)
54
56
  end
55
57
  end
56
58
  end
@@ -25,3 +25,4 @@ require 'rails_best_practices/reviews/use_multipart_alternative_as_content_type_
25
25
  require 'rails_best_practices/reviews/simplify_render_in_views_review'
26
26
  require 'rails_best_practices/reviews/simplify_render_in_controllers_review'
27
27
  require 'rails_best_practices/reviews/remove_empty_helpers_review'
28
+ require 'rails_best_practices/reviews/restrict_auto_generated_routes_review'
@@ -33,7 +33,7 @@ module RailsBestPractices
33
33
  end
34
34
 
35
35
  def interesting_files
36
- ROUTE_FILE
36
+ ROUTE_FILES
37
37
  end
38
38
 
39
39
  def initialize(options = {})
@@ -28,7 +28,7 @@ module RailsBestPractices
28
28
  end
29
29
 
30
30
  def interesting_files
31
- ROUTE_FILE
31
+ ROUTE_FILES
32
32
  end
33
33
 
34
34
  # check all command call nodes, compare with rails2 default route
@@ -40,7 +40,7 @@ module RailsBestPractices
40
40
  end
41
41
 
42
42
  def interesting_files
43
- ROUTE_FILE
43
+ ROUTE_FILES
44
44
  end
45
45
 
46
46
  def initialize(options = {})
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/reviews/review'
3
+
4
+ module RailsBestPractices
5
+ module Reviews
6
+ # Review a route file to make sure all auto-generated routes have corresponding actions in controller.
7
+ #
8
+ # See the best practice details here http://rails-bestpractices.com/posts/86-restrict-auto-generated-routes
9
+ #
10
+ # Implementation:
11
+ #
12
+ # Review process:
13
+ # check all resources and resource method calls,
14
+ # compare the generated routes and corresponding actions in controller,
15
+ # if there is a route generated, but there is not action in that controller,
16
+ # then you should restrict your routes.
17
+ class RestrictAutoGeneratedRoutesReview < Review
18
+ RESOURCE_METHODS = ["show", "new", "create", "edit", "update", "destroy"]
19
+ RESOURCES_METHODS = RESOURCE_METHODS + ["index"]
20
+
21
+ def url
22
+ "http://rails-bestpractices.com/posts/86-restrict-auto-generated-routes"
23
+ end
24
+
25
+ def interesting_nodes
26
+ [:command, :command_call, :method_add_block]
27
+ end
28
+
29
+ def interesting_files
30
+ ROUTE_FILES
31
+ end
32
+
33
+ def initialize
34
+ super
35
+ @namespaces = []
36
+ end
37
+
38
+ # check if the generated routes have the corresponding actions in controller for rails3 routes.
39
+ def start_command(node)
40
+ if "resources" == node.message.to_s
41
+ check_resources(node)
42
+ elsif "resource" == node.message.to_s
43
+ check_resource(node)
44
+ end
45
+ end
46
+
47
+ # remember the namespace.
48
+ def start_method_add_block(node)
49
+ if "namespace" == node.message.to_s
50
+ @namespaces << node.arguments.all[0].to_s
51
+ end
52
+ end
53
+
54
+ # end of namespace call.
55
+ def end_method_add_block(node)
56
+ if "namespace" == node.message.to_s
57
+ @namespaces.pop
58
+ end
59
+ end
60
+
61
+ # check if the generated routes have the corresponding actions in controller for rails2 routes.
62
+ alias_method :start_command_call, :start_command
63
+
64
+ private
65
+ # check resources call, if the routes generated by resources does not exist in the controller.
66
+ def check_resources(node)
67
+ controller_name = controller_name(node)
68
+ return unless Prepares.controllers.include? controller_name
69
+ resources_methods = resources_methods(node)
70
+ unless resources_methods.all? { |meth| Prepares.controller_methods.has_method?(controller_name, meth) }
71
+ only_methods = (resources_methods & Prepares.controller_methods.get_methods(controller_name)).map { |meth| ":#{meth}" }.join(", ")
72
+ add_error "restrict auto-generated routes (:only => [#{only_methods}])"
73
+ end
74
+ end
75
+
76
+ # check resource call, if the routes generated by resources does not exist in the controller.
77
+ def check_resource(node)
78
+ controller_name = controller_name(node)
79
+ return unless Prepares.controllers.include? controller_name
80
+ resource_methods = resource_methods(node)
81
+ unless resource_methods.all? { |meth| Prepares.controller_methods.has_method?(controller_name, meth) }
82
+ only_methods = (resource_methods & Prepares.controller_methods.get_methods(controller_name)).map { |meth| ":#{meth}" }.join(", ")
83
+ add_error "restrict auto-generated routes (:only => [#{only_methods}])"
84
+ end
85
+ end
86
+
87
+ # get the controller name.
88
+ def controller_name(node)
89
+ if node.arguments.all.size > 1
90
+ options = node.arguments.all[1]
91
+ if options.hash_keys.include?("controller")
92
+ name = options.hash_value("controller").to_s
93
+ else
94
+ name = node.arguments.all[0].to_s.tableize
95
+ end
96
+ else
97
+ name = node.arguments.all[0].to_s.tableize
98
+ end
99
+ namespaced_class_name(name)
100
+ end
101
+
102
+ # get the class name with namespace.
103
+ def namespaced_class_name(name)
104
+ class_name = "#{name.split("/").map(&:camelize).join("::")}Controller"
105
+ if @namespaces.empty?
106
+ class_name
107
+ else
108
+ @namespaces.map { |namespace| "#{namespace.camelize}::" }.join("") + class_name
109
+ end
110
+ end
111
+
112
+ # get the route actions that should be generated by resources call.
113
+ def resources_methods(node)
114
+ resources_methods = RESOURCES_METHODS
115
+
116
+ if node.arguments.all.size > 1
117
+ options = node.arguments.all[1]
118
+ if options.hash_keys.include?("only")
119
+ Array(options.hash_value("only").to_object)
120
+ elsif options.hash_keys.include?("except")
121
+ resources_methods - Array(options.hash_value("except").to_object)
122
+ else
123
+ resources_methods
124
+ end
125
+ else
126
+ resources_methods
127
+ end
128
+ end
129
+
130
+ # get the route actions that should be generated by resource call.
131
+ def resource_methods(node)
132
+ resource_methods = RESOURCE_METHODS
133
+
134
+ if node.arguments.all.size > 1
135
+ options = node.arguments.all[1]
136
+ if options.hash_keys.include?("only")
137
+ Array(options.hash_value("only").to_object)
138
+ elsif options.hash_keys.include?("except")
139
+ resource_methods - Array(options.hash_value("except").to_object)
140
+ else
141
+ resource_methods
142
+ end
143
+ else
144
+ resource_methods
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module RailsBestPractices
3
- VERSION = "1.0.1"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
 
@@ -13,17 +13,17 @@ Gem::Specification.new do |s|
13
13
 
14
14
  s.required_rubygems_version = ">= 1.3.6"
15
15
 
16
- s.add_dependency("ruby_parser")
17
- s.add_dependency("ruby-progressbar")
16
+ s.add_dependency("sexp_processor")
17
+ s.add_dependency("progressbar")
18
18
  s.add_dependency("colored")
19
19
  s.add_dependency("erubis")
20
- s.add_dependency("haml")
21
20
  s.add_dependency("i18n")
22
21
  s.add_dependency("activesupport")
23
22
 
24
23
  s.add_development_dependency("rake")
25
24
  s.add_development_dependency("rspec")
26
25
  s.add_development_dependency("watchr")
26
+ s.add_development_dependency("haml")
27
27
  s.add_development_dependency("bundler")
28
28
 
29
29
  s.files = `git ls-files`.split("\n")
@@ -26,3 +26,4 @@ SimplifyRenderInViewsCheck: { }
26
26
  SimplifyRenderInControllersCheck: { }
27
27
  RemoveEmptyHelpersCheck: { }
28
28
  RemoveTabCheck: { }
29
+ RestrictAutoGeneratedRoutesCheck: { }
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBestPractices::Core::Controllers do
4
+ it { should be_a_kind_of Array }
5
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBestPractices::Core::Methods do
4
+ let(:methods) { RailsBestPractices::Core::Methods.new }
5
+
6
+ before :each do
7
+ methods.add_method("Post", "create")
8
+ methods.add_method("Post", "destroy")
9
+ methods.add_method("Comment", "create")
10
+ end
11
+
12
+ it "should get_methods" do
13
+ methods.get_methods("Post").to_a.should == ["create", "destroy"]
14
+ methods.get_methods("Comment").to_a.should == ["create"]
15
+ end
16
+
17
+ it "should has_method?" do
18
+ methods.should be_has_method("Post", "create")
19
+ methods.should be_has_method("Post", "destroy")
20
+ methods.should_not be_has_method("Comment", "destroy")
21
+ end
22
+ end
@@ -128,6 +128,13 @@ describe Sexp do
128
128
  end
129
129
  end
130
130
 
131
+ describe "module_name" do
132
+ it "should get module name of module node" do
133
+ node = parse_content("module Admin; end").grep_node(:sexp_type => :module)
134
+ node.module_name.to_s.should == "Admin"
135
+ end
136
+ end
137
+
131
138
  describe "class_name" do
132
139
  it "should get class name of class node" do
133
140
  node = parse_content("class User; end").grep_node(:sexp_type => :class)
@@ -213,6 +220,11 @@ describe Sexp do
213
220
  node = parse_content("User.find(:all)").grep_node(:sexp_type => :method_add_arg)
214
221
  node.arguments.sexp_type.should == :args_add_block
215
222
  end
223
+
224
+ it "should get the arguments of method_add_block" do
225
+ node = parse_content("Post.save(false) do; end").grep_node(:sexp_type => :method_add_block)
226
+ node.arguments.sexp_type.should == :args_add_block
227
+ end
216
228
  end
217
229
 
218
230
  describe "argument" do
@@ -369,13 +381,28 @@ describe Sexp do
369
381
  node = parse_content("['first_name', 'last_name']").grep_node(:sexp_type => :array)
370
382
  node.array_size.should == 2
371
383
  end
384
+
385
+ it "should get 0" do
386
+ node = parse_content("[]").grep_node(:sexp_type => :array)
387
+ node.array_size.should == 0
388
+ end
372
389
  end
373
390
 
374
391
  describe "to_object" do
375
- it "should to_object" do
392
+ it "should to array" do
376
393
  node = parse_content("['first_name', 'last_name']").grep_node(:sexp_type => :array)
377
394
  node.to_object.should == ["first_name", "last_name"]
378
395
  end
396
+
397
+ it "should to empty array" do
398
+ node = parse_content("[]").grep_node(:sexp_type => :array)
399
+ node.to_object.should == []
400
+ end
401
+
402
+ it "should to string" do
403
+ node = parse_content("'richard'").grep_node(:sexp_type => :string_literal)
404
+ node.to_object.should == "richard"
405
+ end
379
406
  end
380
407
 
381
408
  describe "to_s" do
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+
3
+ describe RailsBestPractices::Prepares::ControllerPrepare do
4
+ let(:runner) { RailsBestPractices::Core::Runner.new(:prepares => RailsBestPractices::Prepares::ControllerPrepare.new) }
5
+
6
+ before :each do
7
+ runner.whiny = true
8
+ end
9
+
10
+ it "should parse controller methods" do
11
+ content =<<-EOF
12
+ class PostsController < ApplicationController
13
+ def index
14
+ end
15
+
16
+ def show
17
+ end
18
+ end
19
+ EOF
20
+ runner.prepare('app/controllers/posts_controller.rb', content)
21
+ methods = RailsBestPractices::Prepares.controller_methods
22
+ methods.get_methods("PostsController").should == ["index", "show"]
23
+ end
24
+
25
+ it "should parse controller methods with module ::" do
26
+ content =<<-EOF
27
+ class Admin::Blog::PostsController < ApplicationController
28
+ def index
29
+ end
30
+
31
+ def show
32
+ end
33
+ end
34
+ EOF
35
+ runner.prepare('app/controllers/admin/posts_controller.rb', content)
36
+ methods = RailsBestPractices::Prepares.controller_methods
37
+ methods.get_methods("Admin::Blog::PostsController").should == ["index", "show"]
38
+ end
39
+
40
+ it "should parse controller methods with module" do
41
+ content =<<-EOF
42
+ module Admin
43
+ module Blog
44
+ class PostsController < ApplicationController
45
+ def index
46
+ end
47
+
48
+ def show
49
+ end
50
+ end
51
+ end
52
+ end
53
+ EOF
54
+ runner.prepare('app/controllers/admin/posts_controller.rb', content)
55
+ methods = RailsBestPractices::Prepares.controller_methods
56
+ methods.get_methods("Admin::Blog::PostsController").should == ["index", "show"]
57
+ end
58
+
59
+ describe "inherited_resources" do
60
+ it "extend inherited_resources" do
61
+ content =<<-EOF
62
+ class PostsController < InheritedResources::Base
63
+ end
64
+ EOF
65
+ runner.prepare('app/controllers/posts_controller.rb', content)
66
+ methods = RailsBestPractices::Prepares.controller_methods
67
+ methods.get_methods("PostsController").should == ["index", "show", "new", "create", "edit", "update", "destroy"]
68
+ end
69
+
70
+ it "extend inherited_resources with actions" do
71
+ content =<<-EOF
72
+ class PostsController < InheritedResources::Base
73
+ actions :index, :show
74
+ end
75
+ EOF
76
+ runner.prepare('app/controllers/posts_controller.rb', content)
77
+ methods = RailsBestPractices::Prepares.controller_methods
78
+ methods.get_methods("PostsController").should == ["index", "show"]
79
+ end
80
+
81
+ it "DSL inherit_resources" do
82
+ content =<<-EOF
83
+ class PostsController
84
+ inherit_resources
85
+ end
86
+ EOF
87
+ runner.prepare('app/controllers/posts_controller.rb', content)
88
+ methods = RailsBestPractices::Prepares.controller_methods
89
+ methods.get_methods("PostsController").should == ["index", "show", "new", "create", "edit", "update", "destroy"]
90
+ end
91
+ end
92
+ end