Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #9514: acts_as_tree.patch

File acts_as_tree.patch, 15.6 kB (added by lifofifo, 1 year ago)

acts_as_tree to svn/plugins

  • acts_as_tree/test/schema.rb

    old new  
     1ActiveRecord::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 
     12end 
  • 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 
     5require 'test/unit' 
     6require 'active_support' 
     7require 'active_record' 
     8require 'active_record/fixtures' 
     9require 'acts_as_tree' 
     10 
     11config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml')) 
     12ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log") 
     13ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']} 
     14ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) 
     15 
     16load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb") 
     17 
     18class 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   
     38end 
  • acts_as_tree/test/database.yml

    old new  
     1sqlite: 
     2  :adapter: sqlite 
     3  :dbfile: acts_as_tree_plugin.sqlite.db 
     4sqlite3: 
     5  :adapter: sqlite3 
     6  :dbfile: acts_as_tree_plugin.sqlite3.db 
     7postgresql: 
     8  :adapter: postgresql 
     9  :username: postgres 
     10  :password: postgres 
     11  :database: acts_as_tree_plugin_test 
     12  :min_messages: ERROR 
     13mysql: 
     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  
     1class Mixin < ActiveRecord::Base 
     2end 
     3 
     4class TreeMixin < Mixin  
     5  acts_as_tree :foreign_key => "parent_id", :order => "id" 
     6end 
     7 
     8class TreeMixinWithoutOrder < Mixin 
     9  acts_as_tree :foreign_key => "parent_id" 
     10end 
     11 
     12class RecursivelyCascadedTreeMixin < Mixin 
     13  acts_as_tree :foreign_key => "parent_id" 
     14  has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id 
     15end 
  • acts_as_tree/test/fixtures/mixins.yml

    old new  
     1tree_1: 
     2  id: 1001 
     3  type: TreeMixin 
     4  parent_id: 
     5   
     6tree_2: 
     7  id: 1002 
     8  type: TreeMixin 
     9  parent_id: 1001 
     10 
     11tree_3: 
     12  id: 1003 
     13  type: TreeMixin   
     14  parent_id: 1002 
     15 
     16tree_4: 
     17  id: 1004 
     18  type: TreeMixin   
     19  parent_id: 1001 
     20 
     21tree2_1: 
     22  id: 1005 
     23  type: TreeMixin 
     24  parent_id: 
     25 
     26tree3_1: 
     27  id: 1006 
     28  type: TreeMixin 
     29  parent_id: 
     30 
     31tree_without_order_1: 
     32  id: 1101 
     33  type: TreeMixinWithoutOrder 
     34  parent_id: 
     35 
     36tree_without_order_2: 
     37  id: 1100 
     38  type: TreeMixinWithoutOrder 
     39  parent_id: 
     40 
     41recursively_cascaded_tree_1: 
     42  id: 5005 
     43  type: RecursivelyCascadedTreeMixin 
     44  parent_id: 
     45 
     46recursively_cascaded_tree_2: 
     47  id: 5006 
     48  type: RecursivelyCascadedTreeMixin 
     49  parent_id: 5005 
     50 
     51recursively_cascaded_tree_3: 
     52  id: 5007 
     53  type: RecursivelyCascadedTreeMixin 
     54  parent_id: 5006 
     55 
     56recursively_cascaded_tree_4: 
     57  id: 5008 
     58  type: RecursivelyCascadedTreeMixin 
     59  parent_id: 5007 
  • acts_as_tree/test/acts_as_tree_test.rb

    old new  
     1require File.join(File.dirname(__FILE__), 'abstract_unit') 
     2require File.join(File.dirname(__FILE__), 'fixtures/mixin') 
     3 
     4class 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            
     82end 
     83 
     84class 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 
     111end 
     112 
     113class 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 
     123end 
  • acts_as_tree/Rakefile

    old new  
     1require 'rake' 
     2require 'rake/testtask' 
     3require 'rake/rdoctask' 
     4 
     5desc 'Default: run unit tests.' 
     6task :default => :test 
     7 
     8desc 'Test acts_as_tree plugin.' 
     9Rake::TestTask.new(:test) do |t| 
     10  t.libs << 'lib' 
     11  t.pattern = 'test/**/*_test.rb' 
     12  t.verbose = true 
     13end 
     14 
     15desc 'Generate documentation for in_place_editing plugin.' 
     16Rake::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') 
     22end 
  • acts_as_tree/init.rb

    old new  
  • acts_as_tree/lib/acts_as_tree.rb

    old new  
     1module 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 
     92end 
     93  
     94ActiveRecord::Base.send(:include, ActsAsTree) 
  • acts_as_tree/README

    old new  
     1acts_as_tree 
     2============ 
     3 
     4Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children 
     5association. 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 
     26Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license