From 95314be65be197b6c38c8c93e3f8d1e8b5b0b674 Mon Sep 17 00:00:00 2001
From: David Heinemeier Hansson <david@loudthinking.com>
Date: Wed, 15 Dec 2004 00:46:26 +0000
Subject: Added tree mixin and unit tests for all the mixins

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@156 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
---
 activerecord/lib/active_record/fixtures.rb    |  7 ++++--
 activerecord/lib/active_record/mixins/list.rb | 32 ++++++++++++++----------
 activerecord/lib/active_record/mixins/tree.rb | 36 +++++++++++++++++++++++++++
 3 files changed, 60 insertions(+), 15 deletions(-)
 create mode 100644 activerecord/lib/active_record/mixins/tree.rb

(limited to 'activerecord/lib')

diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 8447a33c7d..09d0f46390 100755
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -193,8 +193,11 @@ class Fixtures < Hash
     def read_fixture_files
       if File.file?(yaml_file_path)
         # YAML fixtures
-        YAML::load(erb_render(IO.read(yaml_file_path))).each do |name, data|
-          self[name] = Fixture.new(data, @class_name)
+        begin
+          yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
+          yaml.each { |name, data| self[name] = Fixture.new(data, @class_name) } if yaml
+        rescue Exception=>boom
+          raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}"
         end
       elsif File.file?(csv_file_path)
         # CSV fixtures
diff --git a/activerecord/lib/active_record/mixins/list.rb b/activerecord/lib/active_record/mixins/list.rb
index 6910ac771f..bec4b33651 100644
--- a/activerecord/lib/active_record/mixins/list.rb
+++ b/activerecord/lib/active_record/mixins/list.rb
@@ -32,6 +32,12 @@ module ActiveRecord
         base.before_destroy :remove_from_list
         base.after_create   :add_to_list_bottom
       end
+      
+      # can be overriden
+      
+      def position_column
+        "position"
+      end
 
       # Moving around on the list
 
@@ -86,22 +92,22 @@ module ActiveRecord
       # Changing the position
 
       def increment_position
-        update_attribute "position", position.to_i + 1
+        update_attribute position_column, self.send(position_column).to_i + 1
       end
     
       def decrement_position
-        update_attribute "position", position.to_i - 1
+        update_attribute position_column, self.send(position_column).to_i - 1
       end
     
     
       # Querying the position
     
       def first?
-        self.position == 1
+        self.send(position_column) == 1
       end
     
       def last?
-        self.position == bottom_position_in_list
+        self.send(position_column) == bottom_position_in_list
       end
 
       private
@@ -110,51 +116,51 @@ module ActiveRecord
     
         def higher_item
           self.class.find_first(
-            "#{scope_condition} AND position = #{(position.to_i - 1).to_s}"
+            "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
           )
         end
     
         def lower_item
           self.class.find_first(
-            "#{scope_condition} AND position = #{(position.to_i + 1).to_s}"
+            "#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
           )
         end
     
         def bottom_position_in_list
           item = bottom_item
-          item ? item.position : 0
+          item ? item.send(position_column) : 0
         end
     
         def bottom_item
           self.class.find_first(
             "#{scope_condition} ",
-            "position DESC"
+            "#{position_column} DESC"
           )
         end
 
         def assume_bottom_position
-          update_attribute "position", bottom_position_in_list.to_i + 1
+          update_attribute position_column, bottom_position_in_list.to_i + 1
         end
       
         def assume_top_position
-          update_attribute "position", 1
+          update_attribute position_column, 1
         end
       
         def decrement_positions_on_lower_items
           self.class.update_all(
-            "position = (position - 1)",  "#{scope_condition} AND position > #{position.to_i}"
+            "#{position_column} = (#{position_column} - 1)",  "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
           )
         end
       
         def increment_positions_on_higher_items
           self.class.update_all(
-            "position = (position + 1)",  "#{scope_condition} AND position < #{position.to_i}"
+            "#{position_column} = (#{position_column} + 1)",  "#{scope_condition} AND #{position_column} < #{send(position_column)}"
           )
         end
 
         def increment_positions_on_all_items
           self.class.update_all(
-            "position = (position + 1)",  "#{scope_condition}"
+            "#{position_column} = (#{position_column} + 1)",  "#{scope_condition}"
           )
         end
     end
diff --git a/activerecord/lib/active_record/mixins/tree.rb b/activerecord/lib/active_record/mixins/tree.rb
new file mode 100644
index 0000000000..582aa09b4b
--- /dev/null
+++ b/activerecord/lib/active_record/mixins/tree.rb
@@ -0,0 +1,36 @@
+module ActiveRecord
+  module Mixins
+    # Including this mixin if you want to model a tree structure by providing a parent association and an children 
+    # association. This mixin assumes that you have a column called parent_id
+    # 
+    #   class Category < ActiveRecord::Base
+    #     include ActiveRecord::Mixins::Tree
+    #   end
+    #   
+    #   Example : 
+    #   root
+    #    \_ child1 
+    #         \_ sub-child1
+    #
+    #   root      = Category.create("name" => "root")
+    #   child1      = root.children.create("name" => "child1")
+    #   subchild1   = child1.children.create("name" => "subchild1")
+    #
+    #   root.parent # => nil
+    #   child1.parent # => root
+    #   root.children # => [child1]
+    #   root.children.first.children.first # => subchild1
+    module Tree
+
+      def self.append_features(base)
+        super        
+
+        base.module_eval <<-associations
+          belongs_to  :parent,    :class_name => name, :foreign_key => "parent_id"        
+          has_many    :children,  :class_name => name, :foreign_key => "parent_id", :order => "id", :dependent => true
+        associations
+              
+      end  
+    end
+  end
+end
\ No newline at end of file
-- 
cgit v1.2.3