rails_best_practices 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile CHANGED
@@ -89,6 +89,7 @@ MoveCodeIntoHelperCheck: { array_count: 3 }
89
89
  ReplaceInstanceVariableWithLocalVariableCheck: { }
90
90
  DryBundlerInCapistranoCheck: { }
91
91
  UseSayWithTimeInMigrationsCheck: { }
92
+ UseQueryAttributeCheck: { }
92
93
  </code></pre>
93
94
 
94
95
  *************************************************
@@ -112,6 +113,7 @@ h2. Implementation
112
113
  ## Keep Finders on Their Own Model (rails2 only)
113
114
  ## the Law of Demeter
114
115
  ## Use Observer
116
+ ## Use Query Attribute
115
117
 
116
118
  * Migration
117
119
  ## Isolating Seed Data
@@ -50,7 +50,7 @@ module RailsBestPractices
50
50
  def call_assignment(node)
51
51
  if node.message == :save
52
52
  variable = node.subject
53
- add_error "add model virtual attribute (for #{node.subject.to_ruby})" if params_dup?(@variables[variable].collect {|h| h[:arguments]})
53
+ add_error "add model virtual attribute (for #{variable})" if params_dup?(@variables[variable].collect {|h| h[:arguments]})
54
54
  end
55
55
  end
56
56
 
@@ -27,9 +27,9 @@ module RailsBestPractices
27
27
  elsif :call == node.node_type
28
28
  case node.message
29
29
  when :create_table
30
- @table_name = node.arguments[1].to_ruby_string
30
+ @table_name = node.arguments[1].to_s
31
31
  when :integer
32
- column_name = node.arguments[1].to_ruby_string
32
+ column_name = node.arguments[1].to_s
33
33
  if column_name =~ /_id$/ and !indexed?(@table_name, column_name)
34
34
  add_error "always add db index (#@table_name => #{column_name})", node.file, node.line
35
35
  end
@@ -40,8 +40,8 @@ module RailsBestPractices
40
40
  private
41
41
  def find_index_columns(node)
42
42
  node.grep_nodes({:node_type => :call, :message => :add_index}).each do |index_node|
43
- table_name = index_node.arguments[1].to_ruby_string
44
- reference_column = eval(index_node.arguments[2].to_ruby)
43
+ table_name = index_node.arguments[1].to_s
44
+ reference_column = eval(index_node.arguments[2].to_s)
45
45
  @index_columns << [table_name, reference_column]
46
46
  end
47
47
  end
@@ -4,7 +4,7 @@ require 'rails_best_practices/core/error'
4
4
  module RailsBestPractices
5
5
  module Checks
6
6
  class Check
7
- NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :ivar, :block, :iter]
7
+ NODE_TYPES = [:call, :defn, :defs, :if, :unless, :class, :lasgn, :iasgn, :ivar, :lvar, :block, :iter]
8
8
 
9
9
  CONTROLLER_FILES = /_controller\.rb$/
10
10
  MIGRATION_FILES = /db\/migrate\/.*\.rb$/
@@ -60,6 +60,10 @@ module RailsBestPractices
60
60
  line ||= @node.line
61
61
  @errors << RailsBestPractices::Core::Error.new("#{file}", "#{line}", error)
62
62
  end
63
+
64
+ def equal?(node, expected)
65
+ node.to_s == expected or node.to_s == ':' + expected.to_s
66
+ end
63
67
  end
64
68
  end
65
69
  end
@@ -18,7 +18,7 @@ module RailsBestPractices
18
18
  end
19
19
 
20
20
  def evaluate_start(node)
21
- if :namespace == node.message and "bundler" == node.arguments.to_ruby_string
21
+ if :namespace == node.message and equal?(node.arguments[1], "bundler")
22
22
  add_error "dry bundler in capistrano"
23
23
  end
24
24
  end
@@ -9,7 +9,7 @@ module RailsBestPractices
9
9
  class IsolateSeedDataCheck < Check
10
10
 
11
11
  def interesting_nodes
12
- [:call, :lasgn]
12
+ [:call, :lasgn, :iasgn]
13
13
  end
14
14
 
15
15
  def interesting_files
@@ -24,7 +24,7 @@ module RailsBestPractices
24
24
  def evaluate_start(node)
25
25
  if [:create, :create!].include? node.message
26
26
  add_error("isolate seed data")
27
- elsif :lasgn == node.node_type
27
+ elsif [:lasgn, :iasgn].include? node.node_type
28
28
  remember_new_variable(node)
29
29
  elsif [:save, :save!].include? node.message
30
30
  add_error("isolate seed data") if new_record?(node)
@@ -35,12 +35,12 @@ module RailsBestPractices
35
35
 
36
36
  def remember_new_variable(node)
37
37
  unless node.grep_nodes({:node_type => :call, :message => :new}).empty?
38
- @new_variables << node.subject.to_s
38
+ @new_variables << node.left_value.to_s
39
39
  end
40
40
  end
41
41
 
42
42
  def new_record?(node)
43
- @new_variables.include? node.subject.to_ruby
43
+ @new_variables.include? node.subject.to_s
44
44
  end
45
45
  end
46
46
  end
@@ -22,7 +22,7 @@ module RailsBestPractices
22
22
  def evaluate_start(node)
23
23
  if node.node_type == :class
24
24
  remember_association(node)
25
- elsif [:lvar, :ivar].include?(node.subject.node_type) and node.subject != s(:lvar, :_erbout)
25
+ elsif [:lvar, :ivar].include?(node.subject.subject.node_type) and node.subject != s(:lvar, :_erbout)
26
26
  add_error "law of demeter" if need_delegate?(node)
27
27
  end
28
28
  end
@@ -34,13 +34,13 @@ module RailsBestPractices
34
34
  (node.body.grep_nodes(:message => :belongs_to) + node.body.grep_nodes(:message => :has_one)).collect do |body_node|
35
35
  class_name = node.subject.to_s.underscore
36
36
  @associations[class_name] ||= []
37
- @associations[class_name] << body_node.arguments[1].to_ruby_string
37
+ @associations[class_name] << body_node.arguments[1].to_s
38
38
  end
39
39
  end
40
40
 
41
41
  def need_delegate?(node)
42
42
  @associations.each do |class_name, associations|
43
- return true if node.subject.to_ruby =~ /#{class_name}$/ and associations.include? node.message.to_s
43
+ return true if node.subject.subject.to_s =~ /#{class_name}$/ and associations.find { |association| equal?(association, node.subject.message) }
44
44
  end
45
45
  false
46
46
  end
@@ -26,7 +26,7 @@ module RailsBestPractices
26
26
 
27
27
  def check_errors
28
28
  @variables.each do |node, count|
29
- add_error "move code into model (#{node.to_ruby})" if count > 2
29
+ add_error "move code into model (#{node})" if count > 2
30
30
  end
31
31
  end
32
32
 
@@ -32,7 +32,7 @@ module RailsBestPractices
32
32
  end
33
33
 
34
34
  @variables.each do |variable, count|
35
- add_error "move model logic into model (#{variable.to_ruby} called_count > #{@called_count})" if count > @called_count
35
+ add_error "move model logic into model (#{variable} called_count > #{@called_count})" if count > @called_count
36
36
  end
37
37
  @variables = nil
38
38
  end
@@ -47,7 +47,7 @@ module RailsBestPractices
47
47
  return 0 if hash_nodes.empty?
48
48
  hash_key_node = hash_nodes.first[1]
49
49
  if :lit == hash_key_node.node_type and [:member, :collection].include? hash_key_node[1]
50
- customize_hash = eval(hash_nodes.first.to_ruby)
50
+ customize_hash = eval(hash_nodes.first.to_s)
51
51
  (customize_hash[:member].size || 0) + (customize_hash[:collection].size || 0)
52
52
  end
53
53
  end
@@ -48,7 +48,7 @@ module RailsBestPractices
48
48
  def call_assignment(node)
49
49
  if node.message == :save
50
50
  variable = node.subject
51
- add_error "replace complex creation with factory method (#{variable.to_ruby} attribute_assignment_count > #{@attrasgn_count})" if @variables[variable] > @attrasgn_count
51
+ add_error "replace complex creation with factory method (#{variable} attribute_assignment_count > #{@attrasgn_count})" if @variables[variable] > @attrasgn_count
52
52
  end
53
53
  end
54
54
  end
@@ -40,7 +40,7 @@ module RailsBestPractices
40
40
  def call_assignment(node)
41
41
  if node.message == :save
42
42
  variable = node.subject[1]
43
- add_error "use model association (for #{node.subject.to_ruby})" if @variables[variable]
43
+ add_error "use model association (for #{node.subject})" if @variables[variable]
44
44
  end
45
45
  end
46
46
  end
@@ -26,7 +26,7 @@ module RailsBestPractices
26
26
  def evaluate_start(node)
27
27
  if :after_create == node.message
28
28
  remember_callbacks(node)
29
- elsif :defn == node.node_type and @callbacks.include?(node.message_name.to_s)
29
+ elsif :defn == node.node_type and @callbacks.find { |callback| equal?(callback, node.message_name) }
30
30
  add_error "use observer" if use_observer?(node)
31
31
  end
32
32
  end
@@ -37,7 +37,7 @@ module RailsBestPractices
37
37
  node.arguments[1..-1].each do |argument|
38
38
  # ignore callback like after_create Comment.new
39
39
  if :lit == argument.node_type
40
- @callbacks << argument.to_ruby_string
40
+ @callbacks << argument.to_s
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+ require 'rails_best_practices/checks/check'
3
+
4
+ module RailsBestPractices
5
+ module Checks
6
+ # Check to make sure use query attribute instead of nil?, blank? and present?.
7
+ #
8
+ # Implementation:
9
+ # 1. check all models to save model names and association names.
10
+ # model names are used for detecting
11
+ # association name should not be detected as query attribute
12
+ # 2. check all method calls, if their subjects are model names and their messages are one of nil?,
13
+ # blank?, present? or == "", not pluralize and not in the association names,
14
+ # then they need to use query attribute.
15
+ class UseQueryAttributeCheck < Check
16
+
17
+ QUERY_METHODS = [:nil?, :blank?, :present?]
18
+ ASSOCIATION_METHODS = [:belongs_to, :has_one, :has_many, :has_and_belongs_to_many]
19
+
20
+ def interesting_nodes
21
+ [:if, :class, :call]
22
+ end
23
+
24
+ def initialize
25
+ super
26
+ @klazzes = []
27
+ @associations = {}
28
+ end
29
+
30
+ def evaluate_start(node)
31
+ case node.node_type
32
+ when :class
33
+ remember_klazz(node)
34
+ when :call
35
+ remember_association(node) if ASSOCIATION_METHODS.include? node.message
36
+ when :if
37
+ add_error "use query attribute", node.file, node.line if need_query_attribute?(node.conditional_statement)
38
+ else
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def remember_klazz(class_node)
45
+ if class_node.file =~ MODLE_FILES
46
+ @klazzes << class_node.subject
47
+ end
48
+ end
49
+
50
+ def remember_association(association_node)
51
+ @associations[@klazzes.last] ||= []
52
+ @associations[@klazzes.last] << association_node.arguments[1].to_s
53
+ end
54
+
55
+ def need_query_attribute?(conditional_statement_node)
56
+ case conditional_statement_node.node_type
57
+ when :and, :or
58
+ return need_query_attribute?(conditional_statement_node[1]) || need_query_attribute?(conditional_statement_node[2])
59
+ when :not
60
+ return need_query_attribute?(conditional_statement_node[1])
61
+ when :call
62
+ return true if query_method?(conditional_statement_node) or compare_with_empty_string?(conditional_statement_node)
63
+ end
64
+ false
65
+ end
66
+
67
+ def query_method?(node)
68
+ return false unless :call == node.subject.node_type
69
+ subject = node.subject.subject
70
+ message = node.subject.message
71
+ subject_ruby = subject.to_s
72
+
73
+ subject_ruby && @klazzes.find { |klazz| subject_ruby =~ %r|#{klazz.to_s.underscore}| and !@associations[klazz].find { |association| equal?(association, message) } } &&
74
+ message && message.to_s.pluralize != message.to_s &&
75
+ QUERY_METHODS.include?(node.message)
76
+ end
77
+
78
+ def compare_with_empty_string?(node)
79
+ :== == node.message and [:arglist, [:str, ""]] == node.arguments
80
+ end
81
+ end
82
+ end
83
+ end
@@ -20,3 +20,4 @@ require 'rails_best_practices/checks/move_code_into_helper_check'
20
20
  require 'rails_best_practices/checks/replace_instance_variable_with_local_variable_check'
21
21
  require 'rails_best_practices/checks/dry_bundler_in_capistrano_check'
22
22
  require 'rails_best_practices/checks/use_say_with_time_in_migrations_check'
23
+ require 'rails_best_practices/checks/use_query_attribute_check'
@@ -5,17 +5,6 @@ module Enumerable
5
5
  end
6
6
  end
7
7
 
8
- class String
9
- # copy from rails
10
- def underscore
11
- self.gsub(/::/, '/').
12
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
13
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
14
- tr("-", "_").
15
- downcase
16
- end
17
- end
18
-
19
8
  class NilClass
20
9
  def method_missing(method_sym, *arguments, &block)
21
10
  return nil
@@ -3,6 +3,7 @@ require 'rubygems'
3
3
  require 'ruby_parser'
4
4
  require 'erb'
5
5
  require 'yaml'
6
+ require 'active_support/inflector'
6
7
 
7
8
  module RailsBestPractices
8
9
  module Core
@@ -30,10 +31,13 @@ module RailsBestPractices
30
31
  content.gsub!(/#coding:US-ASCII\n/, '')
31
32
  end
32
33
  if filename =~ /.*\.haml$/
33
- require 'haml'
34
- content = Haml::Engine.new(content).precompiled
35
- # remove \xxx characters
36
- content.gsub!(/\\\d{3}/, '')
34
+ begin
35
+ require 'haml'
36
+ content = Haml::Engine.new(content).precompiled
37
+ # remove \xxx characters
38
+ content.gsub!(/\\\d{3}/, '')
39
+ rescue Haml::SyntaxError
40
+ end
37
41
  end
38
42
  node = parse(filename, content)
39
43
  node.accept(@checker) if node
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
  require 'rubygems'
3
3
  require 'sexp'
4
- require 'ruby2ruby'
5
4
 
6
5
  class Sexp
7
6
  def accept(visitor)
@@ -48,7 +47,13 @@ class Sexp
48
47
  end
49
48
 
50
49
  def subject
51
- if [:attrasgn, :call, :iasgn, :lasgn, :class, :iter].include? node_type
50
+ if [:attrasgn, :call, :class, :iter].include? node_type
51
+ self[1]
52
+ end
53
+ end
54
+
55
+ def left_value
56
+ if [:lasgn, :iasgn].include? node_type
52
57
  self[1]
53
58
  end
54
59
  end
@@ -106,13 +111,34 @@ class Sexp
106
111
  self[4][1]
107
112
  end
108
113
  end
109
-
110
- def to_ruby
111
- Ruby2Ruby.new.process(self) unless self.empty?
112
- end
113
114
 
114
- def to_ruby_string
115
- return nil if self.empty?
116
- eval(Ruby2Ruby.new.process(self)).to_s
115
+ def to_s
116
+ if [:lvar, :ivar].include? node_type
117
+ self[1].to_s
118
+ elsif :str == node_type
119
+ self[1]
120
+ elsif :lit == node_type
121
+ ":#{self[1]}"
122
+ elsif :array == node_type
123
+ "[\"#{self.children.collect(&:to_s).join('", "')}\"]"
124
+ elsif :hash == node_type
125
+ key_value = false # false is key, true is value
126
+ result = "{"
127
+ children.each do |child|
128
+ result += "#{child.to_s}#{key_value ? ', ' : ' => '}"
129
+ key_value = !key_value
130
+ end
131
+ result.sub!(/, $/, '')
132
+ result += "}"
133
+ end
117
134
  end
135
+
136
+ #def to_ruby
137
+ #Ruby2Ruby.new.process(self) unless self.empty?
138
+ #end
139
+
140
+ #def to_ruby_string
141
+ #return nil if self.empty?
142
+ #eval(Ruby2Ruby.new.process(self)).to_s
143
+ #end
118
144
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
  module RailsBestPractices
3
- VERSION = "0.4.6"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
 
@@ -19,3 +19,4 @@ MoveCodeIntoHelperCheck: { array_count: 3 }
19
19
  ReplaceInstanceVariableWithLocalVariableCheck: { }
20
20
  DryBundlerInCapistranoCheck: { }
21
21
  UseSayWithTimeInMigrationsCheck: { }
22
+ UseQueryAttributeCheck: { }
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_best_practices
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 11
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 6
10
- version: 0.4.6
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Richard Huang
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-10-15 00:00:00 +08:00
18
+ date: 2010-11-06 00:00:00 +08:00
19
19
  default_executable: rails_best_practices
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -36,22 +36,6 @@ dependencies:
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
38
  requirement: &id002 !ruby/object:Gem::Requirement
39
- none: false
40
- requirements:
41
- - - ~>
42
- - !ruby/object:Gem::Version
43
- hash: 23
44
- segments:
45
- - 1
46
- - 2
47
- - 4
48
- version: 1.2.4
49
- name: ruby2ruby
50
- prerelease: false
51
- type: :runtime
52
- version_requirements: *id002
53
- - !ruby/object:Gem::Dependency
54
- requirement: &id003 !ruby/object:Gem::Requirement
55
39
  none: false
56
40
  requirements:
57
41
  - - ~>
@@ -65,9 +49,9 @@ dependencies:
65
49
  name: progressbar
66
50
  prerelease: false
67
51
  type: :runtime
68
- version_requirements: *id003
52
+ version_requirements: *id002
69
53
  - !ruby/object:Gem::Dependency
70
- requirement: &id004 !ruby/object:Gem::Requirement
54
+ requirement: &id003 !ruby/object:Gem::Requirement
71
55
  none: false
72
56
  requirements:
73
57
  - - ~>
@@ -80,21 +64,33 @@ dependencies:
80
64
  name: colored
81
65
  prerelease: false
82
66
  type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ name: activesupport
79
+ prerelease: false
80
+ type: :runtime
83
81
  version_requirements: *id004
84
82
  - !ruby/object:Gem::Dependency
85
83
  requirement: &id005 !ruby/object:Gem::Requirement
86
84
  none: false
87
85
  requirements:
88
- - - "="
86
+ - - ~>
89
87
  - !ruby/object:Gem::Version
90
- hash: 62196431
88
+ hash: 13
91
89
  segments:
92
90
  - 2
93
91
  - 0
94
- - 0
95
- - beta
96
- - 22
97
- version: 2.0.0.beta.22
92
+ - 1
93
+ version: 2.0.1
98
94
  name: rspec
99
95
  prerelease: false
100
96
  type: :development
@@ -103,7 +99,7 @@ dependencies:
103
99
  requirement: &id006 !ruby/object:Gem::Requirement
104
100
  none: false
105
101
  requirements:
106
- - - "="
102
+ - - ~>
107
103
  - !ruby/object:Gem::Version
108
104
  hash: 35
109
105
  segments:
@@ -119,7 +115,7 @@ dependencies:
119
115
  requirement: &id007 !ruby/object:Gem::Requirement
120
116
  none: false
121
117
  requirements:
122
- - - "="
118
+ - - ~>
123
119
  - !ruby/object:Gem::Version
124
120
  hash: 7
125
121
  segments:
@@ -163,6 +159,7 @@ files:
163
159
  - lib/rails_best_practices/checks/use_observer_check.rb
164
160
  - lib/rails_best_practices/checks/check.rb
165
161
  - lib/rails_best_practices/checks/overuse_route_customizations_check.rb
162
+ - lib/rails_best_practices/checks/use_query_attribute_check.rb
166
163
  - lib/rails_best_practices/checks/use_before_filter_check.rb
167
164
  - lib/rails_best_practices/checks/move_code_into_model_check.rb
168
165
  - lib/rails_best_practices/core.rb