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.
- data/.gitignore +0 -1
- data/.travis.yml +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +45 -0
- data/README.md +2 -0
- data/assets/result.html.erb +78 -0
- data/lib/rails_best_practices.rb +5 -4
- data/lib/rails_best_practices/core.rb +2 -0
- data/lib/rails_best_practices/core/check.rb +27 -1
- data/lib/rails_best_practices/core/controllers.rb +7 -0
- data/lib/rails_best_practices/core/methods.rb +26 -0
- data/lib/rails_best_practices/core/runner.rb +3 -1
- data/lib/rails_best_practices/core_ext/sexp.rb +33 -8
- data/lib/rails_best_practices/prepares.rb +13 -0
- data/lib/rails_best_practices/prepares/controller_prepare.rb +74 -0
- data/lib/rails_best_practices/prepares/mailer_prepare.rb +3 -2
- data/lib/rails_best_practices/prepares/model_prepare.rb +12 -10
- data/lib/rails_best_practices/reviews.rb +1 -0
- data/lib/rails_best_practices/reviews/needless_deep_nesting_review.rb +1 -1
- data/lib/rails_best_practices/reviews/not_use_default_route_review.rb +1 -1
- data/lib/rails_best_practices/reviews/overuse_route_customizations_review.rb +1 -1
- data/lib/rails_best_practices/reviews/restrict_auto_generated_routes_review.rb +149 -0
- data/lib/rails_best_practices/version.rb +1 -1
- data/rails_best_practices.gemspec +3 -3
- data/rails_best_practices.yml +1 -0
- data/spec/rails_best_practices/core/controllers_spec.rb +5 -0
- data/spec/rails_best_practices/core/methods_spec.rb +22 -0
- data/spec/rails_best_practices/core_ext/sexp_spec.rb +28 -1
- data/spec/rails_best_practices/prepares/controller_prepare_spec.rb +92 -0
- data/spec/rails_best_practices/prepares/model_prepare_spec.rb +22 -0
- data/spec/rails_best_practices/reviews/overuse_route_customizations_review_spec.rb +4 -4
- data/spec/rails_best_practices/reviews/restrict_auto_generated_routes_review_spec.rb +334 -0
- data/spec/spec_helper.rb +6 -0
- metadata +27 -13
- 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
|
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
|
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
|
-
@
|
26
|
-
@models << @
|
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
|
-
#
|
34
|
-
# "categories" => {
|
35
|
-
# "project_manager" => {
|
36
|
-
# "portfolio" => {
|
37
|
-
# "milestones => {
|
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(@
|
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'
|
@@ -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
|
@@ -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("
|
17
|
-
s.add_dependency("
|
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")
|
data/rails_best_practices.yml
CHANGED
@@ -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
|
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
|