rails_best_practices 0.2.0 → 0.3.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/README.textile +32 -14
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +12 -7
- data/lib/rails_best_practices/checks/always_add_db_index_check.rb +90 -0
- data/lib/rails_best_practices/checks/check.rb +9 -3
- data/lib/rails_best_practices/checks/isolate_seed_data_check.rb +59 -0
- data/lib/rails_best_practices/checks/keep_finders_on_their_own_model_check.rb +29 -0
- data/lib/rails_best_practices/checks/law_of_demeter_check.rb +47 -0
- data/lib/rails_best_practices/checks/move_code_into_controller_check.rb +31 -0
- data/lib/rails_best_practices/checks/move_code_into_helper_check.rb +29 -0
- data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +1 -1
- data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +1 -1
- data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +1 -1
- data/lib/rails_best_practices/checks/replace_instance_variable_with_local_variable_check.rb +23 -0
- data/lib/rails_best_practices/checks/use_filter_check.rb +38 -0
- data/lib/rails_best_practices/checks/use_observer_check.rb +49 -0
- data/lib/rails_best_practices/checks/use_scope_access_check.rb +3 -3
- data/lib/rails_best_practices/checks.rb +10 -1
- data/lib/rails_best_practices/command.rb +50 -5
- data/lib/rails_best_practices/core/core_ext.rb +12 -1
- data/lib/rails_best_practices/core/runner.rb +12 -3
- data/lib/rails_best_practices/core/visitable_sexp.rb +21 -5
- data/rails_best_practices.gemspec +43 -16
- data/rails_best_practices.yml +9 -0
- data/spec/rails_best_practices/checks/add_model_virtual_attribute_check_spec.rb +57 -4
- data/spec/rails_best_practices/checks/always_add_db_index_check_spec.rb +153 -0
- data/spec/rails_best_practices/checks/isolate_seed_data_check_spec.rb +79 -0
- data/spec/rails_best_practices/checks/keep_finders_on_their_own_model_check_spec.rb +71 -0
- data/spec/rails_best_practices/checks/law_of_demeter_check_spec.rb +37 -0
- data/spec/rails_best_practices/checks/move_code_into_controller_check_spec.rb +33 -0
- data/spec/rails_best_practices/checks/move_code_into_helper_check_spec.rb +29 -0
- data/spec/rails_best_practices/checks/move_finder_to_named_scope_check_spec.rb +6 -6
- data/spec/rails_best_practices/checks/move_model_logic_into_model_check_spec.rb +4 -4
- data/spec/rails_best_practices/checks/replace_complex_creation_with_factory_method_check_spec.rb +5 -5
- data/spec/rails_best_practices/checks/replace_instance_variable_with_local_variable_check_spec.rb +26 -0
- data/spec/rails_best_practices/checks/use_filter_check_spec.rb +85 -0
- data/spec/rails_best_practices/checks/use_model_association_check_spec.rb +6 -6
- data/spec/rails_best_practices/checks/use_observer_check_spec.rb +67 -0
- data/spec/rails_best_practices/checks/use_scope_access_check_spec.rb +17 -17
- metadata +38 -11
data/README.textile
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
h1. rails_best_practices
|
|
2
2
|
|
|
3
|
-
rails_best_practices is a gem to check quality of rails app files according to ihower's presentation
|
|
4
|
-
rails_best_practices is a static
|
|
3
|
+
rails_best_practices is a gem to check quality of rails app files according to ihower's presentation Rails Best Practices from Kungfu RailsConf in Shanghai China.
|
|
4
|
+
rails_best_practices is a code static parser tool.
|
|
5
|
+
|
|
6
|
+
*************************************************
|
|
7
|
+
|
|
8
|
+
h2. Resources
|
|
9
|
+
|
|
10
|
+
"ihower's presentation":http://www.slideshare.net/ihower/rails-best-practices
|
|
11
|
+
"Wiki":http://wiki.github.com/flyerhzm/rails_best_practices
|
|
5
12
|
|
|
6
13
|
*************************************************
|
|
7
14
|
|
|
@@ -21,6 +28,8 @@ at the root directory of rails app
|
|
|
21
28
|
rails_best_practices .
|
|
22
29
|
</code></pre>
|
|
23
30
|
|
|
31
|
+
notice the period at the end, it can be the relative or absolute path of your rails app.
|
|
32
|
+
|
|
24
33
|
*************************************************
|
|
25
34
|
|
|
26
35
|
h2. Customize Configuration
|
|
@@ -41,6 +50,15 @@ MoveModelLogicIntoModelCheck: { called_count: 4 }
|
|
|
41
50
|
OveruseRouteCustomizationsCheck: { customize_count: 3 }
|
|
42
51
|
NeedlessDeepNestingCheck: { nested_count: 2 }
|
|
43
52
|
NotUseDefaultRouteCheck: { }
|
|
53
|
+
KeepFindersOnTheirOwnModelCheck: { }
|
|
54
|
+
LawOfDemeterCheck: { }
|
|
55
|
+
UseObserverCheck: { }
|
|
56
|
+
IsolateSeedDataCheck: { }
|
|
57
|
+
AlwaysAddDbIndexCheck: { }
|
|
58
|
+
UseFilterCheck: { }
|
|
59
|
+
MoveCodeIntoControllerCheck: { }
|
|
60
|
+
MoveCodeIntoHelperCheck: { }
|
|
61
|
+
ReplaceInstanceVariableWithLocalVariableCheck: { }
|
|
44
62
|
</code></pre>
|
|
45
63
|
|
|
46
64
|
*************************************************
|
|
@@ -65,29 +83,29 @@ h2. Progress
|
|
|
65
83
|
## [-Not use default route-]
|
|
66
84
|
|
|
67
85
|
* Lesson 3. Model
|
|
68
|
-
## Keep Finders on Their Own Model
|
|
69
|
-
## Love named_scope
|
|
70
|
-
## the Law of Demeter
|
|
86
|
+
## [-Keep Finders on Their Own Model-]
|
|
87
|
+
## [-Love named_scope-] # same as Move finder to named_scope
|
|
88
|
+
## [-the Law of Demeter-]
|
|
71
89
|
## DRY: metaprogramming
|
|
72
90
|
## Extract into Module
|
|
73
91
|
## Extract to composed class
|
|
74
|
-
## Use Observer
|
|
92
|
+
## [-Use Observer-]
|
|
75
93
|
|
|
76
94
|
* Lesson 4. Migration
|
|
77
|
-
## Isolating Seed Data
|
|
78
|
-
## Always add DB index
|
|
95
|
+
## [-Isolating Seed Data-]
|
|
96
|
+
## [-Always add DB index-]
|
|
79
97
|
|
|
80
98
|
* Lesson 5. Controller
|
|
81
|
-
## Use before_filter
|
|
99
|
+
## [-Use before_filter-]
|
|
82
100
|
## DRY Controller
|
|
83
101
|
|
|
84
102
|
* Lesson 6. View
|
|
85
|
-
## Move code into controller
|
|
103
|
+
## [-Move code into controller-]
|
|
86
104
|
## Move code into model
|
|
87
|
-
## Move code into helper
|
|
88
|
-
## Replace instance variable with local variable
|
|
89
|
-
## Use Form Builder
|
|
90
|
-
## Organize Helper files
|
|
105
|
+
## [-Move code into helper-]
|
|
106
|
+
## [-Replace instance variable with local variable-]
|
|
107
|
+
## [-Use Form Builder-] # not implement, use http://github.com/justinfrench/formtastic
|
|
108
|
+
## [-Organize Helper files-] # not implement, it's rails default behaviour
|
|
91
109
|
|
|
92
110
|
*************************************************
|
|
93
111
|
|
data/Rakefile
CHANGED
|
@@ -27,7 +27,7 @@ Jeweler::Tasks.new do |gemspec|
|
|
|
27
27
|
gemspec.email = "flyerhzm@gmail.com"
|
|
28
28
|
gemspec.homepage = "http://github.com/flyerhzm/rails_best_practices"
|
|
29
29
|
gemspec.authors = ["Richard Huang"]
|
|
30
|
-
gemspec.add_dependency 'ruby_parser'
|
|
31
|
-
gemspec.add_dependency 'ruby2ruby'
|
|
30
|
+
gemspec.add_dependency 'ruby_parser', '>= 2.0.4'
|
|
31
|
+
gemspec.add_dependency 'ruby2ruby', '>= 1.2.4'
|
|
32
32
|
end
|
|
33
33
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.3.0
|
|
@@ -13,7 +13,7 @@ module RailsBestPractices
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def interesting_files
|
|
16
|
-
|
|
16
|
+
CONTROLLER_FILES
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def evaluate_start(node)
|
|
@@ -34,23 +34,28 @@ module RailsBestPractices
|
|
|
34
34
|
|
|
35
35
|
def attribute_assignment(node)
|
|
36
36
|
variable = node.subject
|
|
37
|
-
|
|
37
|
+
arguments_node = nil
|
|
38
|
+
node.arguments.recursive_children do |child|
|
|
39
|
+
if :[] == child.message
|
|
40
|
+
arguments_node = child
|
|
41
|
+
break
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
return if variable.nil? or arguments_node.nil?
|
|
38
45
|
@variables[variable] ||= []
|
|
39
|
-
@variables[variable] << {:message => node.message, :arguments =>
|
|
46
|
+
@variables[variable] << {:message => node.message, :arguments => arguments_node}
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
def call_assignment(node)
|
|
43
50
|
if node.message == :save
|
|
44
51
|
variable = node.subject
|
|
45
|
-
add_error "add model virtual attribute (for #{node.subject.to_ruby})" if params_dup?(@variables[variable].collect {|h| h[:arguments]
|
|
52
|
+
add_error "add model virtual attribute (for #{node.subject.to_ruby})" if params_dup?(@variables[variable].collect {|h| h[:arguments]})
|
|
46
53
|
end
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
def params_dup?(nodes)
|
|
50
57
|
return false if nodes.nil?
|
|
51
|
-
|
|
52
|
-
params_arguments = params_nodes.collect(&:arguments)
|
|
53
|
-
!params_arguments.dups.empty?
|
|
58
|
+
!nodes.dups.empty?
|
|
54
59
|
end
|
|
55
60
|
end
|
|
56
61
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a migration file to make sure every reference key has a database index.
|
|
6
|
+
#
|
|
7
|
+
# Implementation:
|
|
8
|
+
# Parse migration files twice.
|
|
9
|
+
# First, remember all reference keys and index keys.
|
|
10
|
+
# Second, compare reference keys and index keys, and add error when reference keys are not in index keys.
|
|
11
|
+
class AlwaysAddDbIndexCheck < Check
|
|
12
|
+
|
|
13
|
+
def interesting_nodes
|
|
14
|
+
[:defs]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def interesting_files
|
|
18
|
+
MIGRATION_FILES
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
super
|
|
23
|
+
@files = []
|
|
24
|
+
@references = {}
|
|
25
|
+
@indexes = {}
|
|
26
|
+
@parse = false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def evaluate_start(node)
|
|
30
|
+
if @files.include? node.file
|
|
31
|
+
@parse = true if :up == node.message
|
|
32
|
+
else
|
|
33
|
+
@files << node.file
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if @parse and :up == node.message
|
|
37
|
+
@references.each do |table_name, column_names|
|
|
38
|
+
differences = column_names - (@indexes[table_name] || [])
|
|
39
|
+
@references[table_name] = column_names - differences
|
|
40
|
+
hint = differences.collect {|column_name| "#{table_name} => #{column_name}"}.join(', ')
|
|
41
|
+
add_error "always add db index (#{hint})" unless differences.empty?
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
remember_references(node.body)
|
|
45
|
+
remember_indexes(node.body)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def remember_references(node)
|
|
52
|
+
create_table_node = node.grep_nodes({:node_type => :call, :message => :create_table}).first
|
|
53
|
+
if create_table_node
|
|
54
|
+
table_name = create_table_node.arguments[1].to_ruby_string
|
|
55
|
+
node.grep_nodes({:node_type => :call, :message => :integer}).each do |integer_node|
|
|
56
|
+
column_name = integer_node.arguments[1].to_ruby_string
|
|
57
|
+
if column_name =~ /_id$/
|
|
58
|
+
@references[table_name] ||= []
|
|
59
|
+
@references[table_name] << column_name
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
node.grep_nodes({:node_type => :call, :message => :references}).each do |references_node|
|
|
63
|
+
column_name = references_node.arguments[1].to_ruby_string + "_id"
|
|
64
|
+
@references[table_name] ||= []
|
|
65
|
+
@references[table_name] << column_name
|
|
66
|
+
end
|
|
67
|
+
node.grep_nodes({:node_type => :call, :message => :column}).each do |column_node|
|
|
68
|
+
if 'integer' == column_node.arguments[2].to_ruby_string
|
|
69
|
+
column_name = column_node.arguments[1].to_ruby_string
|
|
70
|
+
if column_name =~ /_id$/
|
|
71
|
+
@references[table_name] ||= []
|
|
72
|
+
@references[table_name] << column_name
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def remember_indexes(node)
|
|
80
|
+
add_index_nodes = node.grep_nodes({:node_type => :call, :message => :add_index})
|
|
81
|
+
add_index_nodes.each do |add_index_node|
|
|
82
|
+
table_name = add_index_node.arguments[1].to_ruby_string
|
|
83
|
+
column_name = add_index_node.arguments[2].to_ruby_string
|
|
84
|
+
@indexes[table_name] ||= []
|
|
85
|
+
@indexes[table_name] << column_name
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -3,7 +3,13 @@ require 'rails_best_practices/core/error'
|
|
|
3
3
|
module RailsBestPractices
|
|
4
4
|
module Checks
|
|
5
5
|
class Check
|
|
6
|
-
NODE_TYPES = [:call, :defn, :if, :unless]
|
|
6
|
+
NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar]
|
|
7
|
+
|
|
8
|
+
CONTROLLER_FILES = /_controller.rb$/
|
|
9
|
+
MIGRATION_FILES = /db\/migrate\/.*rb/
|
|
10
|
+
MODLE_FILES = /models\/.*rb/
|
|
11
|
+
VIEW_FILES = /views\/.*erb/
|
|
12
|
+
PARTIAL_VIEW_FILES = /views\/.*\/_.*erb/
|
|
7
13
|
|
|
8
14
|
def initialize
|
|
9
15
|
@errors = []
|
|
@@ -16,8 +22,8 @@ module RailsBestPractices
|
|
|
16
22
|
NODE_TYPES.each do |node|
|
|
17
23
|
start_node_method = "evaluate_start_#{node}"
|
|
18
24
|
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)
|
|
25
|
+
define_method(start_node_method) { |node| } unless self.respond_to?(start_node_method)
|
|
26
|
+
define_method(end_node_method) { |node| } unless self.respond_to?(end_node_method)
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def position(offset = 0)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a migration file to make sure not to insert data in migration, move them to seed file.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: check if there are :create, :create!, and :new with :save or :save! exist, the migration file needs isolate seed data.
|
|
8
|
+
class IsolateSeedDataCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:defs, :call, :lasgn]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
MIGRATION_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
super
|
|
20
|
+
@new_variables = []
|
|
21
|
+
@files = []
|
|
22
|
+
@parse = false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def evaluate_start(node)
|
|
26
|
+
# check duplicate migration because of always_add_db_index_check.
|
|
27
|
+
if :defs == node.node_type
|
|
28
|
+
if @files.include? node.file
|
|
29
|
+
@parse = true if :up == node.message
|
|
30
|
+
else
|
|
31
|
+
@files << node.file
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if @parse
|
|
36
|
+
if [:create, :create!].include? node.message
|
|
37
|
+
add_error("isolate seed data")
|
|
38
|
+
elsif :lasgn == node.node_type
|
|
39
|
+
remember_new_variable(node)
|
|
40
|
+
elsif [:save, :save!].include? node.message
|
|
41
|
+
add_error("isolate seed data") if new_record?(node)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def remember_new_variable(node)
|
|
49
|
+
unless node.grep_nodes({:node_type => :call, :message => :new}).empty?
|
|
50
|
+
@new_variables << node.subject.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def new_record?(node)
|
|
55
|
+
@new_variables.include? node.subject.to_ruby
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a model to make sure finders are on their own model.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: check if :find is called by other model.
|
|
8
|
+
class KeepFindersOnTheirOwnModelCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:call]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
MODLE_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def evaluate_start(node)
|
|
19
|
+
add_error "keep finders on their own model" if others_finder?(node)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def others_finder?(node)
|
|
25
|
+
node.message == :find and node.subject.node_type == :call
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check to make sure not avoid the law of demeter.
|
|
6
|
+
#
|
|
7
|
+
# Implementation:
|
|
8
|
+
# 1. check all models to record belongs_to associations
|
|
9
|
+
# 2. check if calling belongs_to association's method or attribute
|
|
10
|
+
class LawOfDemeterCheck < Check
|
|
11
|
+
|
|
12
|
+
def interesting_nodes
|
|
13
|
+
[:call, :class]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize
|
|
17
|
+
super
|
|
18
|
+
@associations = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def evaluate_start(node)
|
|
22
|
+
if node.node_type == :class
|
|
23
|
+
remember_belongs_to(node)
|
|
24
|
+
elsif [:lvar, :ivar].include?(node.subject.node_type) and node.subject != s(:lvar, :_erbout)
|
|
25
|
+
add_error "law of demeter" if need_delegate?(node)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def remember_belongs_to(node)
|
|
32
|
+
node.body.grep_nodes(:message => :belongs_to).collect do |body_node|
|
|
33
|
+
class_name = node.subject.to_s.underscore
|
|
34
|
+
@associations[class_name] ||= []
|
|
35
|
+
@associations[class_name] << body_node.arguments[1].to_ruby_string
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def need_delegate?(node)
|
|
40
|
+
@associations.each do |class_name, associations|
|
|
41
|
+
return true if node.subject.to_ruby =~ /#{class_name}$/ and associations.include? node.message.to_s
|
|
42
|
+
end
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a view file to make sure there is no finder.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: Check if view file contains finder, then the code should move to controller.
|
|
8
|
+
class MoveCodeIntoControllerCheck < Check
|
|
9
|
+
|
|
10
|
+
FINDER = [:find, :all, :first, :last]
|
|
11
|
+
|
|
12
|
+
def interesting_nodes
|
|
13
|
+
[:call]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def interesting_files
|
|
17
|
+
VIEW_FILES
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def evaluate_start(node)
|
|
21
|
+
add_error "move code into controller" if finder?(node)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def finder?(node)
|
|
27
|
+
node.subject.node_type == :const && FINDER.include?(node.message)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a view file to make sure there is no complex options_for_select message call.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: Check if arguments of options_for_select message call contain more than 2 method call, then move it into helper.
|
|
8
|
+
class MoveCodeIntoHelperCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:call]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
VIEW_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def evaluate_start(node)
|
|
19
|
+
add_error "move code into helper" if complex_select_options?(node)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def complex_select_options?(node)
|
|
25
|
+
:options_for_select == node.message and node.arguments.grep_nodes(:node_type => :call).size > 2
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a partail view file to make sure there is no instance variable.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: Check all instance variable, if exists, then it should be replaced with local variable
|
|
8
|
+
class ReplaceInstanceVariableWithLocalVariableCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:ivar]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
PARTIAL_VIEW_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def evaluate_start(node)
|
|
19
|
+
add_error "replace instance variable with local variable"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a controller file to make sure to use filter to remove duplicate call in different action.
|
|
6
|
+
#
|
|
7
|
+
# Implementation: Check all methods' first call, if they are duplicate, then should use filter.
|
|
8
|
+
class UseFilterCheck < Check
|
|
9
|
+
|
|
10
|
+
def interesting_nodes
|
|
11
|
+
[:class]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def interesting_files
|
|
15
|
+
CONTROLLER_FILES
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def evaluate_start(node)
|
|
19
|
+
@methods = {}
|
|
20
|
+
node.grep_nodes({:node_type => :defn}).each { |method_node| remember_method(method_node) }
|
|
21
|
+
@methods.each do |first_call, method_names|
|
|
22
|
+
add_error "use filter for #{first_call.to_ruby} in #{method_names.join(',')}" if method_names.size > 1
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def remember_method(method_node)
|
|
29
|
+
method_name = method_node.message_name
|
|
30
|
+
first_call = method_node.body[1]
|
|
31
|
+
unless first_call == s(:nil)
|
|
32
|
+
@methods[first_call] ||= []
|
|
33
|
+
@methods[first_call] << method_name
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'rails_best_practices/checks/check'
|
|
2
|
+
|
|
3
|
+
module RailsBestPractices
|
|
4
|
+
module Checks
|
|
5
|
+
# Check a model file to make sure mail deliver method is in observer not callback.
|
|
6
|
+
#
|
|
7
|
+
# Implementation:
|
|
8
|
+
# Record :after_create callback
|
|
9
|
+
# Check method define, if it is a callback and call deliver_xxx message in method body, then it should use observer.
|
|
10
|
+
class UseObserverCheck < Check
|
|
11
|
+
|
|
12
|
+
def interesting_nodes
|
|
13
|
+
[:defn, :call]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def interesting_files
|
|
17
|
+
MODLE_FILES
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize
|
|
21
|
+
super
|
|
22
|
+
@callbacks = []
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def evaluate_start(node)
|
|
26
|
+
if :after_create == node.message
|
|
27
|
+
remember_callbacks(node)
|
|
28
|
+
elsif :defn == node.node_type and @callbacks.include?(node.message_name.to_s)
|
|
29
|
+
add_error "use observer" if use_observer?(node)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def remember_callbacks(node)
|
|
36
|
+
node.arguments[1..-1].each do |argument|
|
|
37
|
+
@callbacks << argument.to_ruby_string
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def use_observer?(node)
|
|
42
|
+
node.recursive_children do |child|
|
|
43
|
+
return true if :call == child.node_type and :const == child.subject.node_type and child.message.to_s =~ /^deliver_/
|
|
44
|
+
end
|
|
45
|
+
false
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -12,7 +12,7 @@ module RailsBestPractices
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def interesting_files
|
|
15
|
-
|
|
15
|
+
CONTROLLER_FILES
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def evaluate_start(node)
|
|
@@ -26,7 +26,7 @@ module RailsBestPractices
|
|
|
26
26
|
|
|
27
27
|
condition_node.message == :== and
|
|
28
28
|
(current_user?(condition_node.arguments.call) or current_user?(condition_node.subject)) and
|
|
29
|
-
(node.false_node.
|
|
29
|
+
(node.false_node.body.any? {|n| n.message == :redirect_to} or node.true_node.method_body.any? {|n| n.message == :redirect_to})
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def current_user?(call_node)
|
|
@@ -35,4 +35,4 @@ module RailsBestPractices
|
|
|
35
35
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
|
-
end
|
|
38
|
+
end
|
|
@@ -9,4 +9,13 @@ require 'rails_best_practices/checks/many_to_many_collection_check'
|
|
|
9
9
|
require 'rails_best_practices/checks/nested_model_forms_check'
|
|
10
10
|
require 'rails_best_practices/checks/overuse_route_customizations_check'
|
|
11
11
|
require 'rails_best_practices/checks/needless_deep_nesting_check'
|
|
12
|
-
require 'rails_best_practices/checks/not_use_default_route_check'
|
|
12
|
+
require 'rails_best_practices/checks/not_use_default_route_check'
|
|
13
|
+
require 'rails_best_practices/checks/keep_finders_on_their_own_model_check'
|
|
14
|
+
require 'rails_best_practices/checks/law_of_demeter_check'
|
|
15
|
+
require 'rails_best_practices/checks/use_observer_check'
|
|
16
|
+
require 'rails_best_practices/checks/isolate_seed_data_check'
|
|
17
|
+
require 'rails_best_practices/checks/always_add_db_index_check'
|
|
18
|
+
require 'rails_best_practices/checks/use_filter_check'
|
|
19
|
+
require 'rails_best_practices/checks/move_code_into_controller_check'
|
|
20
|
+
require 'rails_best_practices/checks/move_code_into_helper_check'
|
|
21
|
+
require 'rails_best_practices/checks/replace_instance_variable_with_local_variable_check'
|