rails_best_practices 0.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/LICENSE +20 -0
- data/README.textile +41 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/bin/rails_best_practices +6 -0
- data/lib/rails_best_practices.rb +5 -0
- data/lib/rails_best_practices/checks.rb +9 -0
- data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +57 -0
- data/lib/rails_best_practices/checks/check.rb +59 -0
- data/lib/rails_best_practices/checks/many_to_many_collection_check.rb +12 -0
- data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +33 -0
- data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +48 -0
- data/lib/rails_best_practices/checks/nested_model_forms_check.rb +12 -0
- data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +55 -0
- data/lib/rails_best_practices/checks/use_model_association_check.rb +47 -0
- data/lib/rails_best_practices/checks/use_model_callback_check.rb +12 -0
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +38 -0
- data/lib/rails_best_practices/command.rb +32 -0
- data/lib/rails_best_practices/core.rb +5 -0
- data/lib/rails_best_practices/core/checking_visitor.rb +26 -0
- data/lib/rails_best_practices/core/core_ext.rb +11 -0
- data/lib/rails_best_practices/core/error.rb +17 -0
- data/lib/rails_best_practices/core/runner.rb +59 -0
- data/lib/rails_best_practices/core/visitable_sexp.rb +102 -0
- data/rails_best_practices.yml +7 -0
- data/spec/rails_best_practices/checks/add_model_virtual_attribute_check_spec.rb +60 -0
- data/spec/rails_best_practices/checks/many_to_many_collection_check_spec.rb +11 -0
- data/spec/rails_best_practices/checks/move_finder_to_named_scope_check_spec.rb +82 -0
- data/spec/rails_best_practices/checks/move_model_logic_into_model_check_spec.rb +49 -0
- data/spec/rails_best_practices/checks/nested_model_forms_check_spec.rb +11 -0
- data/spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb +21 -0
- data/spec/rails_best_practices/checks/replace_complex_creation_with_factory_method_check_spec.rb +76 -0
- data/spec/rails_best_practices/checks/use_model_association_check_spec.rb +71 -0
- data/spec/rails_best_practices/checks/use_model_callback_check_spec.rb +11 -0
- data/spec/rails_best_practices/checks/use_scope_access_check_spec.rb +171 -0
- data/spec/spec.opts +8 -0
- data/spec/spec_helper.rb +5 -0
- metadata +101 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Richard Huang (flyerhzm@gmail.com)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
* Lesson 1. Move code from Controller to Model
|
2
|
+
## [-Move finder to named_scope-]
|
3
|
+
## [-Use model association-]
|
4
|
+
## [-Use scope access-]
|
5
|
+
## [-Add model virtual attribute-]
|
6
|
+
## Use model callback
|
7
|
+
## [-Replace Complex Creation with Factory Method-]
|
8
|
+
## [-Move Model Logic into the Model-]
|
9
|
+
## model.collection_model_ids (many-to-many)
|
10
|
+
## Nested Model Forms (one-to-one)
|
11
|
+
## Nested Model Forms (one-to-many)
|
12
|
+
|
13
|
+
* Lesson 2. RESTful Conventions
|
14
|
+
## Overuse route customizations
|
15
|
+
## Needless deep nesting
|
16
|
+
## Not use default route
|
17
|
+
|
18
|
+
* Lesson 3. Model
|
19
|
+
## Keep Finders on Their Own Model
|
20
|
+
## Love named_scope
|
21
|
+
## the Law of Demeter
|
22
|
+
## DRY: metaprogramming
|
23
|
+
## Extract into Module
|
24
|
+
## Extract to composed class
|
25
|
+
## Use Observer
|
26
|
+
|
27
|
+
* Lesson 4. Migration
|
28
|
+
## Isolating Seed Data
|
29
|
+
## Always add DB index
|
30
|
+
|
31
|
+
* Lesson 5. Controller
|
32
|
+
## Use before_filter
|
33
|
+
## DRY Controller
|
34
|
+
|
35
|
+
* Lesson 6. View
|
36
|
+
## Move code into controller
|
37
|
+
## Move code into model
|
38
|
+
## Move code into helper
|
39
|
+
## Replace instance variable with local variable
|
40
|
+
## Use Form Builder
|
41
|
+
## Organize Helper files
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'jeweler'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => :spec
|
8
|
+
|
9
|
+
desc 'Generate documentation for the sitemap plugin.'
|
10
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'Bullet'
|
13
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
14
|
+
rdoc.rdoc_files.include('README')
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Run all specs in spec directory"
|
19
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
22
|
+
|
23
|
+
Jeweler::Tasks.new do |gemspec|
|
24
|
+
gemspec.name = "rails_best_practices"
|
25
|
+
gemspec.summary = ""
|
26
|
+
gemspec.description = ""
|
27
|
+
gemspec.email = "flyerhzm@gmail.com"
|
28
|
+
gemspec.homepage = "http://github.com/flyerhzm/rails_best_practices"
|
29
|
+
gemspec.authors = ["Richard Huang"]
|
30
|
+
end
|
31
|
+
Jeweler::GemcutterTasks.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rails_best_practices/checks/move_finder_to_named_scope_check'
|
2
|
+
require 'rails_best_practices/checks/use_model_association_check'
|
3
|
+
require 'rails_best_practices/checks/use_scope_access_check'
|
4
|
+
require 'rails_best_practices/checks/add_model_virtual_attribute_check'
|
5
|
+
require 'rails_best_practices/checks/use_model_callback_check'
|
6
|
+
require 'rails_best_practices/checks/replace_complex_creation_with_factory_method_check'
|
7
|
+
require 'rails_best_practices/checks/move_model_logic_into_model_check'
|
8
|
+
require 'rails_best_practices/checks/many_to_many_collection_check'
|
9
|
+
require 'rails_best_practices/checks/nested_model_forms_check'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
# Check a controller to make sure adding a model virual attribute to simplify model creation.
|
6
|
+
#
|
7
|
+
# Implementation: check arguments of params#[]= before calling save,
|
8
|
+
# if they have duplicated arguments, then the model may need to add a model virtual attribute.
|
9
|
+
class AddModelVirtualAttributeCheck < Check
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:defn]
|
13
|
+
end
|
14
|
+
|
15
|
+
def interesting_files
|
16
|
+
/_controller.rb$/
|
17
|
+
end
|
18
|
+
|
19
|
+
def evaluate_start(node)
|
20
|
+
@variables = {}
|
21
|
+
node.recursive_children do |child|
|
22
|
+
case child.node_type
|
23
|
+
when :attrasgn
|
24
|
+
attribute_assignment(child)
|
25
|
+
when :call
|
26
|
+
call_assignment(child)
|
27
|
+
else
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@variables = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def attribute_assignment(node)
|
36
|
+
variable = node.subject
|
37
|
+
return if variable.nil?
|
38
|
+
@variables[variable] ||= []
|
39
|
+
@variables[variable] << {:message => node.message, :arguments => node.arguments}
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_assignment(node)
|
43
|
+
if node.message == :save
|
44
|
+
variable = node.subject
|
45
|
+
add_error "add model virtual attribute" if params_dup?(@variables[variable].collect {|h| h[:arguments] })
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def params_dup?(nodes)
|
50
|
+
return false if nodes.nil?
|
51
|
+
params_nodes = nodes.collect {|node| node.grep_nodes({:subject => s(:call, nil, :params, s(:arglist)), :message => :[]}).first}.compact
|
52
|
+
params_arguments = params_nodes.collect(&:arguments)
|
53
|
+
!params_arguments.dups.empty?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'rails_best_practices/core/error'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
class Check
|
6
|
+
NODE_TYPES = [:call, :defn, :if, :unless]
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@errors = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def interesting_files
|
13
|
+
/.*/
|
14
|
+
end
|
15
|
+
|
16
|
+
NODE_TYPES.each do |node|
|
17
|
+
start_node_method = "evaluate_start_#{node}"
|
18
|
+
end_node_method = "evaluate_end_#{node}"
|
19
|
+
define_method(start_node_method) { } unless self.respond_to?(start_node_method)
|
20
|
+
define_method(end_node_method) { } unless self.respond_to?(end_node_method)
|
21
|
+
end
|
22
|
+
|
23
|
+
def position(offset = 0)
|
24
|
+
"#{@line[2]}:#{@line[1] + offset}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate_start(node)
|
28
|
+
end
|
29
|
+
|
30
|
+
def evaluate_end(node)
|
31
|
+
end
|
32
|
+
|
33
|
+
def evaluate_node(position, node)
|
34
|
+
@node = node
|
35
|
+
eval_method = "evaluate_#{position}_#{node.node_type}"
|
36
|
+
self.send(eval_method, node)
|
37
|
+
end
|
38
|
+
|
39
|
+
def evaluate_node_start(node)
|
40
|
+
evaluate_node(:start, node)
|
41
|
+
evaluate_start(node)
|
42
|
+
end
|
43
|
+
|
44
|
+
def evaluate_node_end(node)
|
45
|
+
evaluate_node(:end, node)
|
46
|
+
evaluate_end(node)
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_error(error, line = nil)
|
50
|
+
line = @node.line if line.nil?
|
51
|
+
@errors << RailsBestPractices::Core::Error.new("#{@node.file}", "#{line}", error)
|
52
|
+
end
|
53
|
+
|
54
|
+
def errors
|
55
|
+
@errors
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
# Check a controller file to make sure finder is simple.
|
6
|
+
#
|
7
|
+
# Complex finder in controller is a code smell, use namd_scope instead.
|
8
|
+
#
|
9
|
+
# Implementation: check method :find, :all, :first, :last with hash parameters.
|
10
|
+
class MoveFinderToNamedScopeCheck < Check
|
11
|
+
|
12
|
+
FINDER = [:find, :all, :first, :last]
|
13
|
+
|
14
|
+
def interesting_nodes
|
15
|
+
[:call]
|
16
|
+
end
|
17
|
+
|
18
|
+
def interesting_files
|
19
|
+
/_controller.rb$/
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate_start(node)
|
23
|
+
add_error "move finder to named_scope" if finder?(node)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def finder?(node)
|
29
|
+
node.subject.node_type == :const && FINDER.include?(node.message) && node.arguments.children.any? {|node| node.node_type == :hash}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
# Check a controller file to make sure that model logic should not exist in controller, move it into a model.
|
6
|
+
#
|
7
|
+
# Implementation: check the count of method calling of a model,
|
8
|
+
# if it is more than defined called count, then it contains model logic.
|
9
|
+
class MoveModelLogicIntoModelCheck < Check
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:defn]
|
13
|
+
end
|
14
|
+
|
15
|
+
def interesting_files
|
16
|
+
/_controller.rb$/
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
super()
|
21
|
+
@called_count = options['called_count'] || 4
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate_start(node)
|
25
|
+
@variables = {}
|
26
|
+
node.recursive_children do |child|
|
27
|
+
case child.node_type
|
28
|
+
when :attrasgn, :call
|
29
|
+
call_node(child)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
if @variables.values.any? { |count| count > @called_count }
|
33
|
+
add_error "move model logic into model"
|
34
|
+
end
|
35
|
+
@variables = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def call_node(node)
|
41
|
+
variable = node.subject
|
42
|
+
return if variable.nil?
|
43
|
+
@variables[variable] ||= 0
|
44
|
+
@variables[variable] += 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
# Check a controller file to make sure that complex model creation should not exist in controller, move it to model factory method
|
6
|
+
#
|
7
|
+
# Implementation: check the count of variable attribute assignment calling before saving,
|
8
|
+
# if more than defined attribute assignment count, then it's a complex creation.
|
9
|
+
class ReplaceComplexCreationWithFactoryMethodCheck < Check
|
10
|
+
|
11
|
+
def interesting_nodes
|
12
|
+
[:defn]
|
13
|
+
end
|
14
|
+
|
15
|
+
def interesting_files
|
16
|
+
/_controller.rb$/
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
super()
|
21
|
+
@attrasgn_count = options['attribute_assignment_count'] || 2
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate_start(node)
|
25
|
+
@variables = {}
|
26
|
+
node.recursive_children do |child|
|
27
|
+
case child.node_type
|
28
|
+
when :attrasgn
|
29
|
+
attribute_assignment(child)
|
30
|
+
when :call
|
31
|
+
call_assignment(child)
|
32
|
+
else
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@variables = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def attribute_assignment(node)
|
41
|
+
variable = node.subject
|
42
|
+
return if variable.nil?
|
43
|
+
@variables[variable] ||= 0
|
44
|
+
@variables[variable] += 1
|
45
|
+
end
|
46
|
+
|
47
|
+
def call_assignment(node)
|
48
|
+
if node.message == :save
|
49
|
+
variable = node.subject
|
50
|
+
add_error "replace complex creation with factory method" if @variables[variable] > @attrasgn_count
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
2
|
+
|
3
|
+
module RailsBestPractices
|
4
|
+
module Checks
|
5
|
+
# Check a model creation to make sure using model association.
|
6
|
+
#
|
7
|
+
# Implementation:
|
8
|
+
# 1. check :attrasgn, if xxx_id is assigned to a variable, set the value of the assigned variable to true.
|
9
|
+
# 2. check :call, if call message :save and caller is included in variables, add error.
|
10
|
+
class UseModelAssociationCheck < Check
|
11
|
+
|
12
|
+
def interesting_nodes
|
13
|
+
[:defn]
|
14
|
+
end
|
15
|
+
|
16
|
+
def evaluate_start(node)
|
17
|
+
@variables = {}
|
18
|
+
node.recursive_children do |child|
|
19
|
+
case child.node_type
|
20
|
+
when :attrasgn
|
21
|
+
attribute_assignment(child)
|
22
|
+
when :call
|
23
|
+
call_assignment(child)
|
24
|
+
else
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@variables = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def attribute_assignment(node)
|
33
|
+
if node.message.to_s =~ /_id=$/
|
34
|
+
variable = node.subject[1]
|
35
|
+
@variables[variable] = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def call_assignment(node)
|
40
|
+
if node.message == :save
|
41
|
+
variable = node.subject[1]
|
42
|
+
add_error "use model association" if @variables[variable]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|