rails_best_practices 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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