rails_best_practices 0.4.6 → 0.5.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 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