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.
Files changed (38) hide show
  1. data/LICENSE +20 -0
  2. data/README.textile +41 -0
  3. data/Rakefile +31 -0
  4. data/VERSION +1 -0
  5. data/bin/rails_best_practices +6 -0
  6. data/lib/rails_best_practices.rb +5 -0
  7. data/lib/rails_best_practices/checks.rb +9 -0
  8. data/lib/rails_best_practices/checks/add_model_virtual_attribute_check.rb +57 -0
  9. data/lib/rails_best_practices/checks/check.rb +59 -0
  10. data/lib/rails_best_practices/checks/many_to_many_collection_check.rb +12 -0
  11. data/lib/rails_best_practices/checks/move_finder_to_named_scope_check.rb +33 -0
  12. data/lib/rails_best_practices/checks/move_model_logic_into_model_check.rb +48 -0
  13. data/lib/rails_best_practices/checks/nested_model_forms_check.rb +12 -0
  14. data/lib/rails_best_practices/checks/replace_complex_creation_with_factory_method_check.rb +55 -0
  15. data/lib/rails_best_practices/checks/use_model_association_check.rb +47 -0
  16. data/lib/rails_best_practices/checks/use_model_callback_check.rb +12 -0
  17. data/lib/rails_best_practices/checks/use_scope_access_check.rb +38 -0
  18. data/lib/rails_best_practices/command.rb +32 -0
  19. data/lib/rails_best_practices/core.rb +5 -0
  20. data/lib/rails_best_practices/core/checking_visitor.rb +26 -0
  21. data/lib/rails_best_practices/core/core_ext.rb +11 -0
  22. data/lib/rails_best_practices/core/error.rb +17 -0
  23. data/lib/rails_best_practices/core/runner.rb +59 -0
  24. data/lib/rails_best_practices/core/visitable_sexp.rb +102 -0
  25. data/rails_best_practices.yml +7 -0
  26. data/spec/rails_best_practices/checks/add_model_virtual_attribute_check_spec.rb +60 -0
  27. data/spec/rails_best_practices/checks/many_to_many_collection_check_spec.rb +11 -0
  28. data/spec/rails_best_practices/checks/move_finder_to_named_scope_check_spec.rb +82 -0
  29. data/spec/rails_best_practices/checks/move_model_logic_into_model_check_spec.rb +49 -0
  30. data/spec/rails_best_practices/checks/nested_model_forms_check_spec.rb +11 -0
  31. data/spec/rails_best_practices/checks/overuse_route_customizations_check_spec.rb +21 -0
  32. data/spec/rails_best_practices/checks/replace_complex_creation_with_factory_method_check_spec.rb +76 -0
  33. data/spec/rails_best_practices/checks/use_model_association_check_spec.rb +71 -0
  34. data/spec/rails_best_practices/checks/use_model_callback_check_spec.rb +11 -0
  35. data/spec/rails_best_practices/checks/use_scope_access_check_spec.rb +171 -0
  36. data/spec/spec.opts +8 -0
  37. data/spec/spec_helper.rb +5 -0
  38. 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,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
4
+
5
+ require 'rails_best_practices'
6
+ require 'rails_best_practices/command'
@@ -0,0 +1,5 @@
1
+ require 'rails_best_practices/checks'
2
+ require 'rails_best_practices/core'
3
+
4
+ module RailsBestPractices
5
+ end
@@ -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,12 @@
1
+ require 'rails_best_practices/checks/check'
2
+
3
+ module RailsBestPractices
4
+ module Checks
5
+ # TODO: unknown how to realize it.
6
+ class ManyToManyCollectionCheck < Check
7
+
8
+ def interesting_nodes
9
+ end
10
+ end
11
+ end
12
+ 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,12 @@
1
+ require 'rails_best_practices/checks/check'
2
+
3
+ module RailsBestPractices
4
+ module Checks
5
+ # TODO: unknown how to realize it.
6
+ class NestedModelFormsCheck < Check
7
+
8
+ def interesting_nodes
9
+ end
10
+ end
11
+ end
12
+ 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