Ticket #9514: acts_as_tree.patch
| File acts_as_tree.patch, 15.6 kB (added by lifofifo, 1 year ago) |
|---|
-
acts_as_tree/test/schema.rb
old new 1 ActiveRecord::Schema.define(:version => 1) do 2 create_table :mixins, :force => true do |t| 3 t.column :parent_id, :integer 4 t.column :pos, :integer 5 t.column :lft, :integer 6 t.column :rgt, :integer 7 t.column :root_id, :integer 8 t.column :type, :string 9 t.column :created_at, :datetime 10 t.column :updated_at, :datetime 11 end 12 end -
acts_as_tree/test/abstract_unit.rb
old new 1 $:.unshift(File.dirname(__FILE__) + '/../../../rails/activesupport/lib') 2 $:.unshift(File.dirname(__FILE__) + '/../../../rails/activerecord/lib') 3 $:.unshift(File.dirname(__FILE__) + '/../lib') 4 5 require 'test/unit' 6 require 'active_support' 7 require 'active_record' 8 require 'active_record/fixtures' 9 require 'acts_as_tree' 10 11 config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) 12 ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") 13 ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']} 14 ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) 15 16 load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb") 17 18 class Test::Unit::TestCase #:nodoc: 19 self.fixture_path = File.dirname(__FILE__) + "/fixtures/" 20 self.use_transactional_fixtures = true 21 self.use_instantiated_fixtures = false 22 23 def create_fixtures(*table_names, &block) 24 Fixtures.create_fixtures(File.dirname(__FILE__) + "/fixtures/", table_names, {}, &block) 25 end 26 27 def assert_queries(num = 1) 28 $query_count = 0 29 yield 30 ensure 31 assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed." 32 end 33 34 def assert_no_queries(&block) 35 assert_queries(0, &block) 36 end 37 38 end -
acts_as_tree/test/database.yml
old new 1 sqlite: 2 :adapter: sqlite 3 :dbfile: acts_as_tree_plugin.sqlite.db 4 sqlite3: 5 :adapter: sqlite3 6 :dbfile: acts_as_tree_plugin.sqlite3.db 7 postgresql: 8 :adapter: postgresql 9 :username: postgres 10 :password: postgres 11 :database: acts_as_tree_plugin_test 12 :min_messages: ERROR 13 mysql: 14 :adapter: mysql 15 :host: localhost 16 :username: rails 17 :password: 18 :database: acts_as_tree_plugin_test -
acts_as_tree/test/fixtures/mixin.rb
old new 1 class Mixin < ActiveRecord::Base 2 end 3 4 class TreeMixin < Mixin 5 acts_as_tree :foreign_key => "parent_id", :order => "id" 6 end 7 8 class TreeMixinWithoutOrder < Mixin 9 acts_as_tree :foreign_key => "parent_id" 10 end 11 12 class RecursivelyCascadedTreeMixin < Mixin 13 acts_as_tree :foreign_key => "parent_id" 14 has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id 15 end -
acts_as_tree/test/fixtures/mixins.yml
old new 1 tree_1: 2 id: 1001 3 type: TreeMixin 4 parent_id: 5 6 tree_2: 7 id: 1002 8 type: TreeMixin 9 parent_id: 1001 10 11 tree_3: 12 id: 1003 13 type: TreeMixin 14 parent_id: 1002 15 16 tree_4: 17 id: 1004 18 type: TreeMixin 19 parent_id: 1001 20 21 tree2_1: 22 id: 1005 23 type: TreeMixin 24 parent_id: 25 26 tree3_1: 27 id: 1006 28 type: TreeMixin 29 parent_id: 30 31 tree_without_order_1: 32 id: 1101 33 type: TreeMixinWithoutOrder 34 parent_id: 35 36 tree_without_order_2: 37 id: 1100 38 type: TreeMixinWithoutOrder 39 parent_id: 40 41 recursively_cascaded_tree_1: 42 id: 5005 43 type: RecursivelyCascadedTreeMixin 44 parent_id: 45 46 recursively_cascaded_tree_2: 47 id: 5006 48 type: RecursivelyCascadedTreeMixin 49 parent_id: 5005 50 51 recursively_cascaded_tree_3: 52 id: 5007 53 type: RecursivelyCascadedTreeMixin 54 parent_id: 5006 55 56 recursively_cascaded_tree_4: 57 id: 5008 58 type: RecursivelyCascadedTreeMixin 59 parent_id: 5007 -
acts_as_tree/test/acts_as_tree_test.rb
old new 1 require File.join(File.dirname(__FILE__), 'abstract_unit') 2 require File.join(File.dirname(__FILE__), 'fixtures/mixin') 3 4 class TreeTest < Test::Unit::TestCase 5 fixtures :mixins 6 7 def test_children 8 assert_equal mixins(:tree_1).children, mixins(:tree_2, :tree_4) 9 assert_equal mixins(:tree_2).children, [mixins(:tree_3)] 10 assert_equal mixins(:tree_3).children, [] 11 assert_equal mixins(:tree_4).children, [] 12 end 13 14 def test_parent 15 assert_equal mixins(:tree_2).parent, mixins(:tree_1) 16 assert_equal mixins(:tree_2).parent, mixins(:tree_4).parent 17 assert_nil mixins(:tree_1).parent 18 end 19 20 def test_delete 21 assert_equal 6, TreeMixin.count 22 mixins(:tree_1).destroy 23 assert_equal 2, TreeMixin.count 24 mixins(:tree2_1).destroy 25 mixins(:tree3_1).destroy 26 assert_equal 0, TreeMixin.count 27 end 28 29 def test_insert 30 @extra = mixins(:tree_1).children.create 31 32 assert @extra 33 34 assert_equal @extra.parent, mixins(:tree_1) 35 36 assert_equal 3, mixins(:tree_1).children.size 37 assert mixins(:tree_1).children.include?(@extra) 38 assert mixins(:tree_1).children.include?(mixins(:tree_2)) 39 assert mixins(:tree_1).children.include?(mixins(:tree_4)) 40 end 41 42 def test_ancestors 43 assert_equal [], mixins(:tree_1).ancestors 44 assert_equal [mixins(:tree_1)], mixins(:tree_2).ancestors 45 assert_equal mixins(:tree_2, :tree_1), mixins(:tree_3).ancestors 46 assert_equal [mixins(:tree_1)], mixins(:tree_4).ancestors 47 assert_equal [], mixins(:tree2_1).ancestors 48 assert_equal [], mixins(:tree3_1).ancestors 49 end 50 51 def test_root 52 assert_equal mixins(:tree_1), TreeMixin.root 53 assert_equal mixins(:tree_1), mixins(:tree_1).root 54 assert_equal mixins(:tree_1), mixins(:tree_2).root 55 assert_equal mixins(:tree_1), mixins(:tree_3).root 56 assert_equal mixins(:tree_1), mixins(:tree_4).root 57 assert_equal mixins(:tree2_1), mixins(:tree2_1).root 58 assert_equal mixins(:tree3_1), mixins(:tree3_1).root 59 end 60 61 def test_roots 62 assert_equal mixins(:tree_1, :tree2_1, :tree3_1), TreeMixin.roots 63 end 64 65 def test_siblings 66 assert_equal mixins(:tree2_1, :tree3_1), mixins(:tree_1).siblings 67 assert_equal [mixins(:tree_4)], mixins(:tree_2).siblings 68 assert_equal [], mixins(:tree_3).siblings 69 assert_equal [mixins(:tree_2)], mixins(:tree_4).siblings 70 assert_equal mixins(:tree_1, :tree3_1), mixins(:tree2_1).siblings 71 assert_equal mixins(:tree_1, :tree2_1), mixins(:tree3_1).siblings 72 end 73 74 def test_self_and_siblings 75 assert_equal mixins(:tree_1, :tree2_1, :tree3_1), mixins(:tree_1).self_and_siblings 76 assert_equal mixins(:tree_2, :tree_4), mixins(:tree_2).self_and_siblings 77 assert_equal [mixins(:tree_3)], mixins(:tree_3).self_and_siblings 78 assert_equal mixins(:tree_2, :tree_4), mixins(:tree_4).self_and_siblings 79 assert_equal mixins(:tree_1, :tree2_1, :tree3_1), mixins(:tree2_1).self_and_siblings 80 assert_equal mixins(:tree_1, :tree2_1, :tree3_1), mixins(:tree3_1).self_and_siblings 81 end 82 end 83 84 class TreeTestWithEagerLoading < Test::Unit::TestCase 85 fixtures :mixins 86 87 def test_eager_association_loading 88 roots = TreeMixin.find(:all, :include=>"children", :conditions=>"mixins.parent_id IS NULL", :order=>"mixins.id") 89 assert_equal mixins(:tree_1, :tree2_1, :tree3_1), roots 90 assert_no_queries do 91 assert_equal 2, roots[0].children.size 92 assert_equal 0, roots[1].children.size 93 assert_equal 0, roots[2].children.size 94 end 95 end 96 97 def test_eager_association_loading_with_recursive_cascading_three_levels_has_many 98 root_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:children=>{:children=>:children}}, :order => 'mixins.id') 99 assert_equal mixins(:recursively_cascaded_tree_4), assert_no_queries { root_node.children.first.children.first.children.first } 100 end 101 102 def test_eager_association_loading_with_recursive_cascading_three_levels_has_one 103 root_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:first_child=>{:first_child=>:first_child}}, :order => 'mixins.id') 104 assert_equal mixins(:recursively_cascaded_tree_4), assert_no_queries { root_node.first_child.first_child.first_child } 105 end 106 107 def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to 108 leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include=>{:parent=>{:parent=>:parent}}, :order => 'mixins.id DESC') 109 assert_equal mixins(:recursively_cascaded_tree_1), assert_no_queries { leaf_node.parent.parent.parent } 110 end 111 end 112 113 class TreeTestWithoutOrder < Test::Unit::TestCase 114 fixtures :mixins 115 116 def test_root 117 assert mixins(:tree_without_order_1, :tree_without_order_2).include?(TreeMixinWithoutOrder.root) 118 end 119 120 def test_roots 121 assert_equal [], mixins(:tree_without_order_1, :tree_without_order_2) - TreeMixinWithoutOrder.roots 122 end 123 end -
acts_as_tree/Rakefile
old new 1 require 'rake' 2 require 'rake/testtask' 3 require 'rake/rdoctask' 4 5 desc 'Default: run unit tests.' 6 task :default => :test 7 8 desc 'Test acts_as_tree plugin.' 9 Rake::TestTask.new(:test) do |t| 10 t.libs << 'lib' 11 t.pattern = 'test/**/*_test.rb' 12 t.verbose = true 13 end 14 15 desc 'Generate documentation for in_place_editing plugin.' 16 Rake::RDocTask.new(:rdoc) do |rdoc| 17 rdoc.rdoc_dir = 'rdoc' 18 rdoc.title = 'InPlaceEditing' 19 rdoc.options << '--line-numbers' << '--inline-source' 20 rdoc.rdoc_files.include('README') 21 rdoc.rdoc_files.include('lib/**/*.rb') 22 end -
acts_as_tree/init.rb
old new -
acts_as_tree/lib/acts_as_tree.rb
old new 1 module ActsAsTree 2 def self.included(base) 3 base.extend(ClassMethods) 4 end 5 6 # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children 7 # association. This requires that you have a foreign key column, which by default is called +parent_id+. 8 # 9 # class Category < ActiveRecord::Base 10 # acts_as_tree :order => "name" 11 # end 12 # 13 # Example: 14 # root 15 # \_ child1 16 # \_ subchild1 17 # \_ subchild2 18 # 19 # root = Category.create("name" => "root") 20 # child1 = root.children.create("name" => "child1") 21 # subchild1 = child1.children.create("name" => "subchild1") 22 # 23 # root.parent # => nil 24 # child1.parent # => root 25 # root.children # => [child1] 26 # root.children.first.children.first # => subchild1 27 # 28 # In addition to the parent and children associations, the following instance methods are added to the class 29 # after calling <tt>acts_as_tree</tt>: 30 # * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>) 31 # * <tt>self_and_siblings</tt> - Returns all the children of the parent, including the current node (<tt>[subchild1, subchild2]</tt> when called on <tt>subchild1</tt>) 32 # * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>) 33 # * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>) 34 module ClassMethods 35 # Configuration options are: 36 # 37 # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+) 38 # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet. 39 # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+). 40 def acts_as_tree(options = {}) 41 configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil } 42 configuration.update(options) if options.is_a?(Hash) 43 44 belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache] 45 has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy 46 47 class_eval <<-EOV 48 include ActsAsTree::InstanceMethods 49 50 def self.roots 51 find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) 52 end 53 54 def self.root 55 find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}}) 56 end 57 EOV 58 end 59 end 60 61 module InstanceMethods 62 # Returns list of ancestors, starting from parent until root. 63 # 64 # subchild1.ancestors # => [child1, root] 65 def ancestors 66 node, nodes = self, [] 67 nodes << node = node.parent while node.parent 68 nodes 69 end 70 71 # Returns the root node of the tree. 72 def root 73 node = self 74 node = node.parent while node.parent 75 node 76 end 77 78 # Returns all siblings of the current node. 79 # 80 # subchild1.siblings # => [subchild2] 81 def siblings 82 self_and_siblings - [self] 83 end 84 85 # Returns all siblings and a reference to the current node. 86 # 87 # subchild1.self_and_siblings # => [subchild1, subchild2] 88 def self_and_siblings 89 parent ? parent.children : self.class.roots 90 end 91 end 92 end 93 94 ActiveRecord::Base.send(:include, ActsAsTree) -
acts_as_tree/README
old new 1 acts_as_tree 2 ============ 3 4 Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children 5 association. This requires that you have a foreign key column, which by default is called +parent_id+. 6 7 class Category < ActiveRecord::Base 8 acts_as_tree :order => "name" 9 end 10 11 Example: 12 root 13 \_ child1 14 \_ subchild1 15 \_ subchild2 16 17 root = Category.create("name" => "root") 18 child1 = root.children.create("name" => "child1") 19 subchild1 = child1.children.create("name" => "subchild1") 20 21 root.parent # => nil 22 child1.parent # => root 23 root.children # => [child1] 24 root.children.first.children.first # => subchild1 25 26 Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license