Commit ce840fb33b5ab8723870b7e1ad21abd559a84364

Authored by AntonioTerceiro
1 parent 1db5448f

ActionItem170: importing acts_as_list and acts_as_tree plugins with piston



git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1445 3f533792-8f58-4932-b0fe-aaf55b0a4547
vendor/plugins/acts_as_list/README 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +ActsAsList
  2 +==========
  3 +
  4 +This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table.
  5 +
  6 +
  7 +Example
  8 +=======
  9 +
  10 + class TodoList < ActiveRecord::Base
  11 + has_many :todo_items, :order => "position"
  12 + end
  13 +
  14 + class TodoItem < ActiveRecord::Base
  15 + belongs_to :todo_list
  16 + acts_as_list :scope => :todo_list
  17 + end
  18 +
  19 + todo_list.first.move_to_bottom
  20 + todo_list.last.move_higher
  21 +
  22 +
  23 +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
0 24 \ No newline at end of file
... ...
vendor/plugins/acts_as_list/init.rb 0 → 100644
... ... @@ -0,0 +1,3 @@
  1 +$:.unshift "#{File.dirname(__FILE__)}/lib"
  2 +require 'active_record/acts/list'
  3 +ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
... ...
vendor/plugins/acts_as_list/lib/active_record/acts/list.rb 0 → 100644
... ... @@ -0,0 +1,256 @@
  1 +module ActiveRecord
  2 + module Acts #:nodoc:
  3 + module List #:nodoc:
  4 + def self.included(base)
  5 + base.extend(ClassMethods)
  6 + end
  7 +
  8 + # This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
  9 + # The class that has this specified needs to have a +position+ column defined as an integer on
  10 + # the mapped database table.
  11 + #
  12 + # Todo list example:
  13 + #
  14 + # class TodoList < ActiveRecord::Base
  15 + # has_many :todo_items, :order => "position"
  16 + # end
  17 + #
  18 + # class TodoItem < ActiveRecord::Base
  19 + # belongs_to :todo_list
  20 + # acts_as_list :scope => :todo_list
  21 + # end
  22 + #
  23 + # todo_list.first.move_to_bottom
  24 + # todo_list.last.move_higher
  25 + module ClassMethods
  26 + # Configuration options are:
  27 + #
  28 + # * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
  29 + # * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
  30 + # (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
  31 + # to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
  32 + # Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
  33 + def acts_as_list(options = {})
  34 + configuration = { :column => "position", :scope => "1 = 1" }
  35 + configuration.update(options) if options.is_a?(Hash)
  36 +
  37 + configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
  38 +
  39 + if configuration[:scope].is_a?(Symbol)
  40 + scope_condition_method = %(
  41 + def scope_condition
  42 + if #{configuration[:scope].to_s}.nil?
  43 + "#{configuration[:scope].to_s} IS NULL"
  44 + else
  45 + "#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
  46 + end
  47 + end
  48 + )
  49 + else
  50 + scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
  51 + end
  52 +
  53 + class_eval <<-EOV
  54 + include ActiveRecord::Acts::List::InstanceMethods
  55 +
  56 + def acts_as_list_class
  57 + ::#{self.name}
  58 + end
  59 +
  60 + def position_column
  61 + '#{configuration[:column]}'
  62 + end
  63 +
  64 + #{scope_condition_method}
  65 +
  66 + before_destroy :remove_from_list
  67 + before_create :add_to_list_bottom
  68 + EOV
  69 + end
  70 + end
  71 +
  72 + # All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
  73 + # by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
  74 + # lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
  75 + # the first in the list of all chapters.
  76 + module InstanceMethods
  77 + # Insert the item at the given position (defaults to the top position of 1).
  78 + def insert_at(position = 1)
  79 + insert_at_position(position)
  80 + end
  81 +
  82 + # Swap positions with the next lower item, if one exists.
  83 + def move_lower
  84 + return unless lower_item
  85 +
  86 + acts_as_list_class.transaction do
  87 + lower_item.decrement_position
  88 + increment_position
  89 + end
  90 + end
  91 +
  92 + # Swap positions with the next higher item, if one exists.
  93 + def move_higher
  94 + return unless higher_item
  95 +
  96 + acts_as_list_class.transaction do
  97 + higher_item.increment_position
  98 + decrement_position
  99 + end
  100 + end
  101 +
  102 + # Move to the bottom of the list. If the item is already in the list, the items below it have their
  103 + # position adjusted accordingly.
  104 + def move_to_bottom
  105 + return unless in_list?
  106 + acts_as_list_class.transaction do
  107 + decrement_positions_on_lower_items
  108 + assume_bottom_position
  109 + end
  110 + end
  111 +
  112 + # Move to the top of the list. If the item is already in the list, the items above it have their
  113 + # position adjusted accordingly.
  114 + def move_to_top
  115 + return unless in_list?
  116 + acts_as_list_class.transaction do
  117 + increment_positions_on_higher_items
  118 + assume_top_position
  119 + end
  120 + end
  121 +
  122 + # Removes the item from the list.
  123 + def remove_from_list
  124 + if in_list?
  125 + decrement_positions_on_lower_items
  126 + update_attribute position_column, nil
  127 + end
  128 + end
  129 +
  130 + # Increase the position of this item without adjusting the rest of the list.
  131 + def increment_position
  132 + return unless in_list?
  133 + update_attribute position_column, self.send(position_column).to_i + 1
  134 + end
  135 +
  136 + # Decrease the position of this item without adjusting the rest of the list.
  137 + def decrement_position
  138 + return unless in_list?
  139 + update_attribute position_column, self.send(position_column).to_i - 1
  140 + end
  141 +
  142 + # Return +true+ if this object is the first in the list.
  143 + def first?
  144 + return false unless in_list?
  145 + self.send(position_column) == 1
  146 + end
  147 +
  148 + # Return +true+ if this object is the last in the list.
  149 + def last?
  150 + return false unless in_list?
  151 + self.send(position_column) == bottom_position_in_list
  152 + end
  153 +
  154 + # Return the next higher item in the list.
  155 + def higher_item
  156 + return nil unless in_list?
  157 + acts_as_list_class.find(:first, :conditions =>
  158 + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
  159 + )
  160 + end
  161 +
  162 + # Return the next lower item in the list.
  163 + def lower_item
  164 + return nil unless in_list?
  165 + acts_as_list_class.find(:first, :conditions =>
  166 + "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
  167 + )
  168 + end
  169 +
  170 + # Test if this record is in a list
  171 + def in_list?
  172 + !send(position_column).nil?
  173 + end
  174 +
  175 + private
  176 + def add_to_list_top
  177 + increment_positions_on_all_items
  178 + end
  179 +
  180 + def add_to_list_bottom
  181 + self[position_column] = bottom_position_in_list.to_i + 1
  182 + end
  183 +
  184 + # Overwrite this method to define the scope of the list changes
  185 + def scope_condition() "1" end
  186 +
  187 + # Returns the bottom position number in the list.
  188 + # bottom_position_in_list # => 2
  189 + def bottom_position_in_list(except = nil)
  190 + item = bottom_item(except)
  191 + item ? item.send(position_column) : 0
  192 + end
  193 +
  194 + # Returns the bottom item
  195 + def bottom_item(except = nil)
  196 + conditions = scope_condition
  197 + conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
  198 + acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
  199 + end
  200 +
  201 + # Forces item to assume the bottom position in the list.
  202 + def assume_bottom_position
  203 + update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
  204 + end
  205 +
  206 + # Forces item to assume the top position in the list.
  207 + def assume_top_position
  208 + update_attribute(position_column, 1)
  209 + end
  210 +
  211 + # This has the effect of moving all the higher items up one.
  212 + def decrement_positions_on_higher_items(position)
  213 + acts_as_list_class.update_all(
  214 + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
  215 + )
  216 + end
  217 +
  218 + # This has the effect of moving all the lower items up one.
  219 + def decrement_positions_on_lower_items
  220 + return unless in_list?
  221 + acts_as_list_class.update_all(
  222 + "#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
  223 + )
  224 + end
  225 +
  226 + # This has the effect of moving all the higher items down one.
  227 + def increment_positions_on_higher_items
  228 + return unless in_list?
  229 + acts_as_list_class.update_all(
  230 + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
  231 + )
  232 + end
  233 +
  234 + # This has the effect of moving all the lower items down one.
  235 + def increment_positions_on_lower_items(position)
  236 + acts_as_list_class.update_all(
  237 + "#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
  238 + )
  239 + end
  240 +
  241 + # Increments position (<tt>position_column</tt>) of all items in the list.
  242 + def increment_positions_on_all_items
  243 + acts_as_list_class.update_all(
  244 + "#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
  245 + )
  246 + end
  247 +
  248 + def insert_at_position(position)
  249 + remove_from_list
  250 + increment_positions_on_lower_items(position)
  251 + self.update_attribute(position_column, position)
  252 + end
  253 + end
  254 + end
  255 + end
  256 +end
... ...
vendor/plugins/acts_as_list/test/list_test.rb 0 → 100644
... ... @@ -0,0 +1,332 @@
  1 +require 'test/unit'
  2 +
  3 +require 'rubygems'
  4 +gem 'activerecord', '>= 1.15.4.7794'
  5 +require 'active_record'
  6 +
  7 +require "#{File.dirname(__FILE__)}/../init"
  8 +
  9 +ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
  10 +
  11 +def setup_db
  12 + ActiveRecord::Schema.define(:version => 1) do
  13 + create_table :mixins do |t|
  14 + t.column :pos, :integer
  15 + t.column :parent_id, :integer
  16 + t.column :created_at, :datetime
  17 + t.column :updated_at, :datetime
  18 + end
  19 + end
  20 +end
  21 +
  22 +def teardown_db
  23 + ActiveRecord::Base.connection.tables.each do |table|
  24 + ActiveRecord::Base.connection.drop_table(table)
  25 + end
  26 +end
  27 +
  28 +class Mixin < ActiveRecord::Base
  29 +end
  30 +
  31 +class ListMixin < Mixin
  32 + acts_as_list :column => "pos", :scope => :parent
  33 +
  34 + def self.table_name() "mixins" end
  35 +end
  36 +
  37 +class ListMixinSub1 < ListMixin
  38 +end
  39 +
  40 +class ListMixinSub2 < ListMixin
  41 +end
  42 +
  43 +class ListWithStringScopeMixin < ActiveRecord::Base
  44 + acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
  45 +
  46 + def self.table_name() "mixins" end
  47 +end
  48 +
  49 +
  50 +class ListTest < Test::Unit::TestCase
  51 +
  52 + def setup
  53 + setup_db
  54 + (1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
  55 + end
  56 +
  57 + def teardown
  58 + teardown_db
  59 + end
  60 +
  61 + def test_reordering
  62 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  63 +
  64 + ListMixin.find(2).move_lower
  65 + assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  66 +
  67 + ListMixin.find(2).move_higher
  68 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  69 +
  70 + ListMixin.find(1).move_to_bottom
  71 + assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  72 +
  73 + ListMixin.find(1).move_to_top
  74 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  75 +
  76 + ListMixin.find(2).move_to_bottom
  77 + assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  78 +
  79 + ListMixin.find(4).move_to_top
  80 + assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  81 + end
  82 +
  83 + def test_move_to_bottom_with_next_to_last_item
  84 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  85 + ListMixin.find(3).move_to_bottom
  86 + assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  87 + end
  88 +
  89 + def test_next_prev
  90 + assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
  91 + assert_nil ListMixin.find(1).higher_item
  92 + assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
  93 + assert_nil ListMixin.find(4).lower_item
  94 + end
  95 +
  96 + def test_injection
  97 + item = ListMixin.new(:parent_id => 1)
  98 + assert_equal "parent_id = 1", item.scope_condition
  99 + assert_equal "pos", item.position_column
  100 + end
  101 +
  102 + def test_insert
  103 + new = ListMixin.create(:parent_id => 20)
  104 + assert_equal 1, new.pos
  105 + assert new.first?
  106 + assert new.last?
  107 +
  108 + new = ListMixin.create(:parent_id => 20)
  109 + assert_equal 2, new.pos
  110 + assert !new.first?
  111 + assert new.last?
  112 +
  113 + new = ListMixin.create(:parent_id => 20)
  114 + assert_equal 3, new.pos
  115 + assert !new.first?
  116 + assert new.last?
  117 +
  118 + new = ListMixin.create(:parent_id => 0)
  119 + assert_equal 1, new.pos
  120 + assert new.first?
  121 + assert new.last?
  122 + end
  123 +
  124 + def test_insert_at
  125 + new = ListMixin.create(:parent_id => 20)
  126 + assert_equal 1, new.pos
  127 +
  128 + new = ListMixin.create(:parent_id => 20)
  129 + assert_equal 2, new.pos
  130 +
  131 + new = ListMixin.create(:parent_id => 20)
  132 + assert_equal 3, new.pos
  133 +
  134 + new4 = ListMixin.create(:parent_id => 20)
  135 + assert_equal 4, new4.pos
  136 +
  137 + new4.insert_at(3)
  138 + assert_equal 3, new4.pos
  139 +
  140 + new.reload
  141 + assert_equal 4, new.pos
  142 +
  143 + new.insert_at(2)
  144 + assert_equal 2, new.pos
  145 +
  146 + new4.reload
  147 + assert_equal 4, new4.pos
  148 +
  149 + new5 = ListMixin.create(:parent_id => 20)
  150 + assert_equal 5, new5.pos
  151 +
  152 + new5.insert_at(1)
  153 + assert_equal 1, new5.pos
  154 +
  155 + new4.reload
  156 + assert_equal 5, new4.pos
  157 + end
  158 +
  159 + def test_delete_middle
  160 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  161 +
  162 + ListMixin.find(2).destroy
  163 +
  164 + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  165 +
  166 + assert_equal 1, ListMixin.find(1).pos
  167 + assert_equal 2, ListMixin.find(3).pos
  168 + assert_equal 3, ListMixin.find(4).pos
  169 +
  170 + ListMixin.find(1).destroy
  171 +
  172 + assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  173 +
  174 + assert_equal 1, ListMixin.find(3).pos
  175 + assert_equal 2, ListMixin.find(4).pos
  176 + end
  177 +
  178 + def test_with_string_based_scope
  179 + new = ListWithStringScopeMixin.create(:parent_id => 500)
  180 + assert_equal 1, new.pos
  181 + assert new.first?
  182 + assert new.last?
  183 + end
  184 +
  185 + def test_nil_scope
  186 + new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
  187 + new2.move_higher
  188 + assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
  189 + end
  190 +
  191 +
  192 + def test_remove_from_list_should_then_fail_in_list?
  193 + assert_equal true, ListMixin.find(1).in_list?
  194 + ListMixin.find(1).remove_from_list
  195 + assert_equal false, ListMixin.find(1).in_list?
  196 + end
  197 +
  198 + def test_remove_from_list_should_set_position_to_nil
  199 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  200 +
  201 + ListMixin.find(2).remove_from_list
  202 +
  203 + assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  204 +
  205 + assert_equal 1, ListMixin.find(1).pos
  206 + assert_equal nil, ListMixin.find(2).pos
  207 + assert_equal 2, ListMixin.find(3).pos
  208 + assert_equal 3, ListMixin.find(4).pos
  209 + end
  210 +
  211 + def test_remove_before_destroy_does_not_shift_lower_items_twice
  212 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  213 +
  214 + ListMixin.find(2).remove_from_list
  215 + ListMixin.find(2).destroy
  216 +
  217 + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
  218 +
  219 + assert_equal 1, ListMixin.find(1).pos
  220 + assert_equal 2, ListMixin.find(3).pos
  221 + assert_equal 3, ListMixin.find(4).pos
  222 + end
  223 +
  224 +end
  225 +
  226 +class ListSubTest < Test::Unit::TestCase
  227 +
  228 + def setup
  229 + setup_db
  230 + (1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
  231 + end
  232 +
  233 + def teardown
  234 + teardown_db
  235 + end
  236 +
  237 + def test_reordering
  238 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  239 +
  240 + ListMixin.find(2).move_lower
  241 + assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  242 +
  243 + ListMixin.find(2).move_higher
  244 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  245 +
  246 + ListMixin.find(1).move_to_bottom
  247 + assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  248 +
  249 + ListMixin.find(1).move_to_top
  250 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  251 +
  252 + ListMixin.find(2).move_to_bottom
  253 + assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  254 +
  255 + ListMixin.find(4).move_to_top
  256 + assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  257 + end
  258 +
  259 + def test_move_to_bottom_with_next_to_last_item
  260 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  261 + ListMixin.find(3).move_to_bottom
  262 + assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  263 + end
  264 +
  265 + def test_next_prev
  266 + assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
  267 + assert_nil ListMixin.find(1).higher_item
  268 + assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
  269 + assert_nil ListMixin.find(4).lower_item
  270 + end
  271 +
  272 + def test_injection
  273 + item = ListMixin.new("parent_id"=>1)
  274 + assert_equal "parent_id = 1", item.scope_condition
  275 + assert_equal "pos", item.position_column
  276 + end
  277 +
  278 + def test_insert_at
  279 + new = ListMixin.create("parent_id" => 20)
  280 + assert_equal 1, new.pos
  281 +
  282 + new = ListMixinSub1.create("parent_id" => 20)
  283 + assert_equal 2, new.pos
  284 +
  285 + new = ListMixinSub2.create("parent_id" => 20)
  286 + assert_equal 3, new.pos
  287 +
  288 + new4 = ListMixin.create("parent_id" => 20)
  289 + assert_equal 4, new4.pos
  290 +
  291 + new4.insert_at(3)
  292 + assert_equal 3, new4.pos
  293 +
  294 + new.reload
  295 + assert_equal 4, new.pos
  296 +
  297 + new.insert_at(2)
  298 + assert_equal 2, new.pos
  299 +
  300 + new4.reload
  301 + assert_equal 4, new4.pos
  302 +
  303 + new5 = ListMixinSub1.create("parent_id" => 20)
  304 + assert_equal 5, new5.pos
  305 +
  306 + new5.insert_at(1)
  307 + assert_equal 1, new5.pos
  308 +
  309 + new4.reload
  310 + assert_equal 5, new4.pos
  311 + end
  312 +
  313 + def test_delete_middle
  314 + assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  315 +
  316 + ListMixin.find(2).destroy
  317 +
  318 + assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  319 +
  320 + assert_equal 1, ListMixin.find(1).pos
  321 + assert_equal 2, ListMixin.find(3).pos
  322 + assert_equal 3, ListMixin.find(4).pos
  323 +
  324 + ListMixin.find(1).destroy
  325 +
  326 + assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
  327 +
  328 + assert_equal 1, ListMixin.find(3).pos
  329 + assert_equal 2, ListMixin.find(4).pos
  330 + end
  331 +
  332 +end
... ...
vendor/plugins/acts_as_tree/README 0 → 100644
... ... @@ -0,0 +1,26 @@
  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
0 27 \ No newline at end of file
... ...
vendor/plugins/acts_as_tree/Rakefile 0 → 100644
... ... @@ -0,0 +1,22 @@
  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 acts_as_tree plugin.'
  16 +Rake::RDocTask.new(:rdoc) do |rdoc|
  17 + rdoc.rdoc_dir = 'rdoc'
  18 + rdoc.title = 'acts_as_tree'
  19 + rdoc.options << '--line-numbers' << '--inline-source'
  20 + rdoc.rdoc_files.include('README')
  21 + rdoc.rdoc_files.include('lib/**/*.rb')
  22 +end
... ...
vendor/plugins/acts_as_tree/init.rb 0 → 100644
... ... @@ -0,0 +1 @@
  1 +ActiveRecord::Base.send :include, ActiveRecord::Acts::Tree
... ...
vendor/plugins/acts_as_tree/lib/active_record/acts/tree.rb 0 → 100644
... ... @@ -0,0 +1,96 @@
  1 +module ActiveRecord
  2 + module Acts
  3 + module Tree
  4 + def self.included(base)
  5 + base.extend(ClassMethods)
  6 + end
  7 +
  8 + # Specify this +acts_as+ extension if you want to model a tree structure by providing a parent association and a children
  9 + # association. This requires that you have a foreign key column, which by default is called +parent_id+.
  10 + #
  11 + # class Category < ActiveRecord::Base
  12 + # acts_as_tree :order => "name"
  13 + # end
  14 + #
  15 + # Example:
  16 + # root
  17 + # \_ child1
  18 + # \_ subchild1
  19 + # \_ subchild2
  20 + #
  21 + # root = Category.create("name" => "root")
  22 + # child1 = root.children.create("name" => "child1")
  23 + # subchild1 = child1.children.create("name" => "subchild1")
  24 + #
  25 + # root.parent # => nil
  26 + # child1.parent # => root
  27 + # root.children # => [child1]
  28 + # root.children.first.children.first # => subchild1
  29 + #
  30 + # In addition to the parent and children associations, the following instance methods are added to the class
  31 + # after calling <tt>acts_as_tree</tt>:
  32 + # * <tt>siblings</tt> - Returns all the children of the parent, excluding the current node (<tt>[subchild2]</tt> when called on <tt>subchild1</tt>)
  33 + # * <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>)
  34 + # * <tt>ancestors</tt> - Returns all the ancestors of the current node (<tt>[child1, root]</tt> when called on <tt>subchild2</tt>)
  35 + # * <tt>root</tt> - Returns the root of the current node (<tt>root</tt> when called on <tt>subchild2</tt>)
  36 + module ClassMethods
  37 + # Configuration options are:
  38 + #
  39 + # * <tt>foreign_key</tt> - specifies the column name to use for tracking of the tree (default: +parent_id+)
  40 + # * <tt>order</tt> - makes it possible to sort the children according to this SQL snippet.
  41 + # * <tt>counter_cache</tt> - keeps a count in a +children_count+ column if set to +true+ (default: +false+).
  42 + def acts_as_tree(options = {})
  43 + configuration = { :foreign_key => "parent_id", :order => nil, :counter_cache => nil }
  44 + configuration.update(options) if options.is_a?(Hash)
  45 +
  46 + belongs_to :parent, :class_name => name, :foreign_key => configuration[:foreign_key], :counter_cache => configuration[:counter_cache]
  47 + has_many :children, :class_name => name, :foreign_key => configuration[:foreign_key], :order => configuration[:order], :dependent => :destroy
  48 +
  49 + class_eval <<-EOV
  50 + include ActiveRecord::Acts::Tree::InstanceMethods
  51 +
  52 + def self.roots
  53 + find(:all, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
  54 + end
  55 +
  56 + def self.root
  57 + find(:first, :conditions => "#{configuration[:foreign_key]} IS NULL", :order => #{configuration[:order].nil? ? "nil" : %Q{"#{configuration[:order]}"}})
  58 + end
  59 + EOV
  60 + end
  61 + end
  62 +
  63 + module InstanceMethods
  64 + # Returns list of ancestors, starting from parent until root.
  65 + #
  66 + # subchild1.ancestors # => [child1, root]
  67 + def ancestors
  68 + node, nodes = self, []
  69 + nodes << node = node.parent while node.parent
  70 + nodes
  71 + end
  72 +
  73 + # Returns the root node of the tree.
  74 + def root
  75 + node = self
  76 + node = node.parent while node.parent
  77 + node
  78 + end
  79 +
  80 + # Returns all siblings of the current node.
  81 + #
  82 + # subchild1.siblings # => [subchild2]
  83 + def siblings
  84 + self_and_siblings - [self]
  85 + end
  86 +
  87 + # Returns all siblings and a reference to the current node.
  88 + #
  89 + # subchild1.self_and_siblings # => [subchild1, subchild2]
  90 + def self_and_siblings
  91 + parent ? parent.children : self.class.roots
  92 + end
  93 + end
  94 + end
  95 + end
  96 +end
... ...
vendor/plugins/acts_as_tree/test/abstract_unit.rb 0 → 100644
vendor/plugins/acts_as_tree/test/acts_as_tree_test.rb 0 → 100644
... ... @@ -0,0 +1,219 @@
  1 +require 'test/unit'
  2 +
  3 +require 'rubygems'
  4 +require 'active_record'
  5 +
  6 +$:.unshift File.dirname(__FILE__) + '/../lib'
  7 +require File.dirname(__FILE__) + '/../init'
  8 +
  9 +class Test::Unit::TestCase
  10 + def assert_queries(num = 1)
  11 + $query_count = 0
  12 + yield
  13 + ensure
  14 + assert_equal num, $query_count, "#{$query_count} instead of #{num} queries were executed."
  15 + end
  16 +
  17 + def assert_no_queries(&block)
  18 + assert_queries(0, &block)
  19 + end
  20 +end
  21 +
  22 +ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
  23 +
  24 +# AR keeps printing annoying schema statements
  25 +$stdout = StringIO.new
  26 +
  27 +def setup_db
  28 + ActiveRecord::Base.logger
  29 + ActiveRecord::Schema.define(:version => 1) do
  30 + create_table :mixins do |t|
  31 + t.column :type, :string
  32 + t.column :parent_id, :integer
  33 + end
  34 + end
  35 +end
  36 +
  37 +def teardown_db
  38 + ActiveRecord::Base.connection.tables.each do |table|
  39 + ActiveRecord::Base.connection.drop_table(table)
  40 + end
  41 +end
  42 +
  43 +class Mixin < ActiveRecord::Base
  44 +end
  45 +
  46 +class TreeMixin < Mixin
  47 + acts_as_tree :foreign_key => "parent_id", :order => "id"
  48 +end
  49 +
  50 +class TreeMixinWithoutOrder < Mixin
  51 + acts_as_tree :foreign_key => "parent_id"
  52 +end
  53 +
  54 +class RecursivelyCascadedTreeMixin < Mixin
  55 + acts_as_tree :foreign_key => "parent_id"
  56 + has_one :first_child, :class_name => 'RecursivelyCascadedTreeMixin', :foreign_key => :parent_id
  57 +end
  58 +
  59 +class TreeTest < Test::Unit::TestCase
  60 +
  61 + def setup
  62 + setup_db
  63 + @root1 = TreeMixin.create!
  64 + @root_child1 = TreeMixin.create! :parent_id => @root1.id
  65 + @child1_child = TreeMixin.create! :parent_id => @root_child1.id
  66 + @root_child2 = TreeMixin.create! :parent_id => @root1.id
  67 + @root2 = TreeMixin.create!
  68 + @root3 = TreeMixin.create!
  69 + end
  70 +
  71 + def teardown
  72 + teardown_db
  73 + end
  74 +
  75 + def test_children
  76 + assert_equal @root1.children, [@root_child1, @root_child2]
  77 + assert_equal @root_child1.children, [@child1_child]
  78 + assert_equal @child1_child.children, []
  79 + assert_equal @root_child2.children, []
  80 + end
  81 +
  82 + def test_parent
  83 + assert_equal @root_child1.parent, @root1
  84 + assert_equal @root_child1.parent, @root_child2.parent
  85 + assert_nil @root1.parent
  86 + end
  87 +
  88 + def test_delete
  89 + assert_equal 6, TreeMixin.count
  90 + @root1.destroy
  91 + assert_equal 2, TreeMixin.count
  92 + @root2.destroy
  93 + @root3.destroy
  94 + assert_equal 0, TreeMixin.count
  95 + end
  96 +
  97 + def test_insert
  98 + @extra = @root1.children.create
  99 +
  100 + assert @extra
  101 +
  102 + assert_equal @extra.parent, @root1
  103 +
  104 + assert_equal 3, @root1.children.size
  105 + assert @root1.children.include?(@extra)
  106 + assert @root1.children.include?(@root_child1)
  107 + assert @root1.children.include?(@root_child2)
  108 + end
  109 +
  110 + def test_ancestors
  111 + assert_equal [], @root1.ancestors
  112 + assert_equal [@root1], @root_child1.ancestors
  113 + assert_equal [@root_child1, @root1], @child1_child.ancestors
  114 + assert_equal [@root1], @root_child2.ancestors
  115 + assert_equal [], @root2.ancestors
  116 + assert_equal [], @root3.ancestors
  117 + end
  118 +
  119 + def test_root
  120 + assert_equal @root1, TreeMixin.root
  121 + assert_equal @root1, @root1.root
  122 + assert_equal @root1, @root_child1.root
  123 + assert_equal @root1, @child1_child.root
  124 + assert_equal @root1, @root_child2.root
  125 + assert_equal @root2, @root2.root
  126 + assert_equal @root3, @root3.root
  127 + end
  128 +
  129 + def test_roots
  130 + assert_equal [@root1, @root2, @root3], TreeMixin.roots
  131 + end
  132 +
  133 + def test_siblings
  134 + assert_equal [@root2, @root3], @root1.siblings
  135 + assert_equal [@root_child2], @root_child1.siblings
  136 + assert_equal [], @child1_child.siblings
  137 + assert_equal [@root_child1], @root_child2.siblings
  138 + assert_equal [@root1, @root3], @root2.siblings
  139 + assert_equal [@root1, @root2], @root3.siblings
  140 + end
  141 +
  142 + def test_self_and_siblings
  143 + assert_equal [@root1, @root2, @root3], @root1.self_and_siblings
  144 + assert_equal [@root_child1, @root_child2], @root_child1.self_and_siblings
  145 + assert_equal [@child1_child], @child1_child.self_and_siblings
  146 + assert_equal [@root_child1, @root_child2], @root_child2.self_and_siblings
  147 + assert_equal [@root1, @root2, @root3], @root2.self_and_siblings
  148 + assert_equal [@root1, @root2, @root3], @root3.self_and_siblings
  149 + end
  150 +end
  151 +
  152 +class TreeTestWithEagerLoading < Test::Unit::TestCase
  153 +
  154 + def setup
  155 + teardown_db
  156 + setup_db
  157 + @root1 = TreeMixin.create!
  158 + @root_child1 = TreeMixin.create! :parent_id => @root1.id
  159 + @child1_child = TreeMixin.create! :parent_id => @root_child1.id
  160 + @root_child2 = TreeMixin.create! :parent_id => @root1.id
  161 + @root2 = TreeMixin.create!
  162 + @root3 = TreeMixin.create!
  163 +
  164 + @rc1 = RecursivelyCascadedTreeMixin.create!
  165 + @rc2 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc1.id
  166 + @rc3 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc2.id
  167 + @rc4 = RecursivelyCascadedTreeMixin.create! :parent_id => @rc3.id
  168 + end
  169 +
  170 + def teardown
  171 + teardown_db
  172 + end
  173 +
  174 + def test_eager_association_loading
  175 + roots = TreeMixin.find(:all, :include => :children, :conditions => "mixins.parent_id IS NULL", :order => "mixins.id")
  176 + assert_equal [@root1, @root2, @root3], roots
  177 + assert_no_queries do
  178 + assert_equal 2, roots[0].children.size
  179 + assert_equal 0, roots[1].children.size
  180 + assert_equal 0, roots[2].children.size
  181 + end
  182 + end
  183 +
  184 + def test_eager_association_loading_with_recursive_cascading_three_levels_has_many
  185 + root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :children => { :children => :children } }, :order => 'mixins.id')
  186 + assert_equal @rc4, assert_no_queries { root_node.children.first.children.first.children.first }
  187 + end
  188 +
  189 + def test_eager_association_loading_with_recursive_cascading_three_levels_has_one
  190 + root_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :first_child => { :first_child => :first_child } }, :order => 'mixins.id')
  191 + assert_equal @rc4, assert_no_queries { root_node.first_child.first_child.first_child }
  192 + end
  193 +
  194 + def test_eager_association_loading_with_recursive_cascading_three_levels_belongs_to
  195 + leaf_node = RecursivelyCascadedTreeMixin.find(:first, :include => { :parent => { :parent => :parent } }, :order => 'mixins.id DESC')
  196 + assert_equal @rc1, assert_no_queries { leaf_node.parent.parent.parent }
  197 + end
  198 +end
  199 +
  200 +class TreeTestWithoutOrder < Test::Unit::TestCase
  201 +
  202 + def setup
  203 + setup_db
  204 + @root1 = TreeMixinWithoutOrder.create!
  205 + @root2 = TreeMixinWithoutOrder.create!
  206 + end
  207 +
  208 + def teardown
  209 + teardown_db
  210 + end
  211 +
  212 + def test_root
  213 + assert [@root1, @root2].include?(TreeMixinWithoutOrder.root)
  214 + end
  215 +
  216 + def test_roots
  217 + assert_equal [], [@root1, @root2] - TreeMixinWithoutOrder.roots
  218 + end
  219 +end
... ...
vendor/plugins/acts_as_tree/test/database.yml 0 → 100644
vendor/plugins/acts_as_tree/test/fixtures/mixin.rb 0 → 100644
vendor/plugins/acts_as_tree/test/fixtures/mixins.yml 0 → 100644
vendor/plugins/acts_as_tree/test/schema.rb 0 → 100644