ordered_tree 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm @ordered_tree
data/CHANGELOG ADDED
@@ -0,0 +1,14 @@
1
+
2
+ v.1.2 - Wed May 17 2007
3
+ - renamed syblings to siblings
4
+ - renamed self_and_syblings to self_and_siblings
5
+ - some other minor code changes
6
+ - updated tests
7
+
8
+ Note:
9
+ I attempted to rewrite the tests using fixtures,
10
+ but some of the tests were failing.
11
+ I presume this is due to fixtures not supporting
12
+ nested transactions. The results of this effort
13
+ can be found in the fixtures_not_working/ branch.
14
+
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ gem "activerecord", ">= 3.0.0"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.6.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.2"
12
+ gem "rcov", ">= 0"
13
+
14
+ gem 'sqlite3'
15
+ gem 'guard-rspec'
16
+ gem 'libnotify'
17
+ gem 'rb-inotify'
18
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,57 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.7)
5
+ activesupport (= 3.0.7)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activerecord (3.0.7)
9
+ activemodel (= 3.0.7)
10
+ activesupport (= 3.0.7)
11
+ arel (~> 2.0.2)
12
+ tzinfo (~> 0.3.23)
13
+ activesupport (3.0.7)
14
+ arel (2.0.10)
15
+ builder (2.1.2)
16
+ diff-lcs (1.1.2)
17
+ ffi (1.0.9)
18
+ git (1.2.5)
19
+ guard (0.3.4)
20
+ thor (~> 0.14.6)
21
+ guard-rspec (0.3.1)
22
+ guard (>= 0.2.2)
23
+ i18n (0.5.0)
24
+ jeweler (1.6.2)
25
+ bundler (~> 1.0)
26
+ git (>= 1.2.5)
27
+ rake
28
+ libnotify (0.5.5)
29
+ rake (0.9.1)
30
+ rb-inotify (0.8.5)
31
+ ffi (>= 0.5.0)
32
+ rcov (0.9.9)
33
+ rspec (2.6.0)
34
+ rspec-core (~> 2.6.0)
35
+ rspec-expectations (~> 2.6.0)
36
+ rspec-mocks (~> 2.6.0)
37
+ rspec-core (2.6.3)
38
+ rspec-expectations (2.6.0)
39
+ diff-lcs (~> 1.1.2)
40
+ rspec-mocks (2.6.0)
41
+ sqlite3 (1.3.3)
42
+ thor (0.14.6)
43
+ tzinfo (0.3.27)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ activerecord (>= 3.0.0)
50
+ bundler (~> 1.0.0)
51
+ guard-rspec
52
+ jeweler (~> 1.6.2)
53
+ libnotify
54
+ rb-inotify
55
+ rcov
56
+ rspec (~> 2.6.0)
57
+ sqlite3
data/Guardfile ADDED
@@ -0,0 +1,17 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb})
6
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch('spec/spec_helper.rb') { "spec" }
11
+ watch('config/routes.rb') { "spec/routing" }
12
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
13
+ watch(%r{^spec/.+_spec\.rb})
14
+ watch(%r{^app/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
15
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
16
+ watch(%r{^app/controllers/(.+)_(controller)\.rb}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
17
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ramon Tayag
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.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = ordered_tree
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to ordered_tree
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Ramon Tayag. See LICENSE.txt for
18
+ further details.
19
+
data/README.textile ADDED
@@ -0,0 +1,123 @@
1
+ h1. ActsAsOrderedTree
2
+
3
+ v.1.2
4
+
5
+ <pre>
6
+ +----+-----------+----------+---------+
7
+ node_1 | id | parent_id | position | name |
8
+ \_ node_2 +----+-----------+----------+---------+
9
+ \_ node_3 | 1 | 0 | 1 | Node_1 |
10
+ | \_ node_4 | 2 | 1 | 1 | Node_2 |
11
+ | \_ node_5 | 3 | 1 | 2 | Node_3 |
12
+ | | \_ node_8 | 4 | 3 | 1 | Node_4 |
13
+ | | \_ node_9 | 5 | 3 | 2 | Node_5 |
14
+ | \_ node_10 | 6 | 1 | 3 | Node_6 |
15
+ | \_ node_11 | 7 | 1 | 4 | Node_7 |
16
+ \_ node_6 | 8 | 5 | 1 | Node_8 |
17
+ \_ node_7 | 9 | 5 | 2 | Node_9 |
18
+ | | 10 | 3 | 3 | Node_10 |
19
+ | | 11 | 3 | 4 | Node_11 |
20
+ node_12 | 12 | 0 | 2 | Node_12 |
21
+ \_ node_13 | 13 | 12 | 1 | Node_13 |
22
+ \_ node_14 | 14 | 12 | 2 | Node_14 |
23
+ | \_ node_15 | 15 | 14 | 1 | Node_15 |
24
+ | \_ node_16 | 16 | 14 | 2 | Node_16 |
25
+ | | \_ node_19 | 17 | 12 | 3 | Node_17 |
26
+ | | \_ node_20 | 18 | 12 | 4 | Node_18 |
27
+ | \_ node_21 | 19 | 16 | 1 | Node_19 |
28
+ | \_ node_22 | 20 | 16 | 2 | Node_20 |
29
+ \_ node_17 | 21 | 14 | 3 | Node_21 |
30
+ \_ node_18 | 22 | 14 | 4 | Node_22 |
31
+ +----+-----------+----------+---------+
32
+ </pre>
33
+
34
+ <pre>
35
+ class Person < ActiveRecord::Base
36
+ acts_as_ordered_tree :foreign_key => :parent_id,
37
+ :order => :position
38
+ end
39
+
40
+ class CreatePeople < ActiveRecord::Migration
41
+ def self.up
42
+ create_table :people do |t|
43
+ t.column :parent_id , :integer ,:null => false ,:default => 0
44
+ t.column :position , :integer
45
+ end
46
+ add_index(:people, :parent_id)
47
+ end
48
+ end
49
+ </pre>
50
+
51
+ Which "in effect" sets up the following:
52
+
53
+ <pre>
54
+ belongs_to :parent,
55
+ :class_name => Person,
56
+ :foreign_key => :parent_id
57
+
58
+ has_many :children,
59
+ :class_name => Person,
60
+ :foreign_key => :parent_id,
61
+ :order => :position
62
+ </pre>
63
+
64
+ h2. Overview
65
+
66
+ <pre>
67
+ Actions Tree Methods List Method
68
+ --------------------------------------------------------------------------------------------------
69
+ Create
70
+ To create a child object at a specific position,
71
+ use one of the following:
72
+ Person.create(:parent_id => parent.id, :position => 2)
73
+ parent.children << Person.new(:position => 3)
74
+ parent.children.create(:position => 5)
75
+
76
+ To create a new 'root', use:
77
+ Person.create(:position => 2)
78
+
79
+ :position will default to the bottom of the parent's list
80
+ :parent_id defaults to 0 (Class.roots)
81
+
82
+ Read
83
+ roots (class method) self_and_siblings
84
+
85
+ root siblings
86
+
87
+ parent position_in_list
88
+
89
+ ancestors
90
+
91
+ children
92
+
93
+ descendants
94
+
95
+ Update
96
+ shift_to(parent = nil, position = nil) move_above(sibling = nil)
97
+
98
+ orphan move_higher
99
+
100
+ orphan_children move_lower
101
+
102
+ parent_adopts_children move_to_top
103
+
104
+ orphan_self_and_children move_to_bottom
105
+
106
+ orphan_self_and_parent_adopts_children
107
+
108
+ Destroy
109
+ destroy (deletes all descendants)
110
+
111
+ destroy_and_orphan_children
112
+
113
+ destroy_and_parent_adopts_children
114
+
115
+ </pre>
116
+
117
+ h2. Install
118
+
119
+ gem 'ordered_tree'
120
+
121
+ h2. Demo
122
+
123
+ There is a drag-n-drop demo located at svn://rubyforge.org/var/svn/ordered-tree/demo
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "ordered_tree"
18
+ gem.homepage = "http://github.com/ramontayag/ordered_tree"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Gem version of Wizard's ActsAsTree}
21
+ gem.description = %Q{Uses parent_id and position to create an ordered tree.}
22
+ gem.email = "ramon@tayag.net"
23
+ gem.authors = ["Ramon Tayag"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "ordered_tree #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,26 @@
1
+ module OrderedTree
2
+ module ClassMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ belongs_to :parent_node, :class_name => name, :foreign_key => ordered_tree_config[:foreign_key]
7
+ has_many :child_nodes, :class_name => name, :foreign_key => ordered_tree_config[:foreign_key], :order => ordered_tree_config[:order]
8
+ scope :roots, lambda { { :conditions => {ordered_tree_config[:foreign_key] => 0}, :order => ordered_tree_config[:order].to_s } }
9
+
10
+ def foreign_key_column
11
+ :"#{ordered_tree_config[:foreign_key]}"
12
+ end
13
+
14
+ def order_column
15
+ :"#{ordered_tree_config[:order]}"
16
+ end
17
+
18
+ before_create :add_to_list
19
+ before_update :check_list_changes
20
+ after_update :reorder_old_list
21
+ before_destroy :destroy_descendants
22
+ after_destroy :reorder_old_list
23
+ validate :check_parentage, :on => :update
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ module OrderedTree
2
+ module InstanceMethods
3
+ module Destroy
4
+ ## Destroy Methods
5
+
6
+ # sends immediate children to the 'roots' list, then destroy's self
7
+ def destroy_and_orphan_children
8
+ self.class.transaction do
9
+ orphan_children
10
+ self.destroy
11
+ end
12
+ end
13
+
14
+ # hands immediate children of to it's parent, then destroy's self
15
+ def destroy_and_parent_adopts_children
16
+ self.class.transaction do
17
+ parent_adopts_children
18
+ self.destroy
19
+ end
20
+ end
21
+
22
+ def reorder_children
23
+ self.class.transaction do
24
+ children(true).each do |child|
25
+ new_position = children.index(child) + 1
26
+ child.update_attribute(order_column, new_position) if (child.position_in_list != new_position)
27
+ end
28
+ end
29
+ end
30
+
31
+ protected
32
+
33
+ def destroy_descendants #:nodoc:
34
+ # before_destroy callback (recursive)
35
+ @old_parent = self.class.find(self).parent || 'root'
36
+ self.children(true).each{|child| child.destroy}
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,151 @@
1
+ module OrderedTree
2
+ module InstanceMethods
3
+ module List
4
+ ## List Read Methods
5
+
6
+ # returns an array of the object's siblings, including itself
7
+ #
8
+ # return is cached
9
+ # use self_and_siblings(true) to force a reload
10
+ def self_and_siblings(reload = false)
11
+ parent(reload) ? parent.children(reload) : self.class.roots(reload)
12
+ end
13
+
14
+ # returns an array of the object's siblings, excluding itself
15
+ #
16
+ # return is cached
17
+ # use siblings(true) to force a reload
18
+ def siblings(reload = false)
19
+ self_and_siblings(reload) - [self]
20
+ end
21
+
22
+ # returns object's position in the list
23
+ # the list will either be parent.children,
24
+ # or self.class.roots
25
+ #
26
+ # i.e. self.position
27
+ def position_in_list
28
+ self[order_column]
29
+ end
30
+
31
+ ## List Update Methods
32
+
33
+ # moves the item above sibling in the list
34
+ # defaults to the top of the list
35
+ def move_above(sibling = nil)
36
+ if sibling
37
+ return if (!self_and_siblings(true).include?(sibling) || (sibling == self))
38
+ if sibling.position_in_list > position_in_list
39
+ move_to(sibling.position_in_list - 1)
40
+ else
41
+ move_to(sibling.position_in_list)
42
+ end
43
+ else
44
+ move_to_top
45
+ end
46
+ end
47
+
48
+ # move to the top of the list
49
+ def move_to_top
50
+ return if position_in_list == 1
51
+ move_to(1)
52
+ end
53
+
54
+ # swap with the node above self
55
+ def move_higher
56
+ return if position_in_list == 1
57
+ move_to(position_in_list - 1)
58
+ end
59
+
60
+ # swap with the node below self
61
+ def move_lower
62
+ return if self == self_and_siblings(true).last
63
+ move_to(position_in_list + 1)
64
+ end
65
+
66
+ # move to the bottom of the list
67
+ def move_to_bottom
68
+ return if self == self_and_siblings(true).last
69
+ move_to(self_and_siblings.last.position_in_list)
70
+ end
71
+
72
+ protected
73
+
74
+ def check_list_changes #:nodoc:
75
+ # before_update callback
76
+ #
77
+ # Note: to shift to another parent AND specify a position, use shift_to()
78
+ # i.e. don't assign the object a new position, then new_parent << obj
79
+ # this will end up at the bottom of the list.
80
+ #
81
+ if !self_and_siblings(true).include?(self)
82
+ add_to_list_bottom
83
+ @old_parent = self.class.find(self).parent || 'root'
84
+ end
85
+ end
86
+
87
+ def reorder_old_list #:nodoc:
88
+ # after_update and after_destroy callback
89
+ # re-order the old parent's list
90
+ if @old_parent == 'root'
91
+ reorder_roots
92
+ elsif @old_parent
93
+ @old_parent.reorder_children
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ def add_to_list
100
+ new_position = position_in_list if (1..self_and_siblings(true).size).include?(position_in_list.to_i)
101
+ add_to_list_bottom
102
+ move_to(new_position, true) if new_position
103
+ end
104
+
105
+ def add_to_list_bottom
106
+ self[order_column] = self_and_siblings.size + 1
107
+ end
108
+
109
+ def move_to(new_position, on_create = false)
110
+ if parent(true)
111
+ scope = "#{foreign_key_column} = #{parent.id}"
112
+ else
113
+ scope = "#{foreign_key_column} = 0"
114
+ end
115
+ if new_position < position_in_list
116
+ # moving from lower to higher, increment all in between
117
+ # #{order_column} >= #{new_position} AND #{order_column} < #{position_in_list}
118
+ self.class.transaction do
119
+ self.class.update_all(
120
+ "#{order_column} = (#{order_column} + 1)", "#{scope} AND (#{order_column} BETWEEN #{new_position} AND #{position_in_list - 1})"
121
+ )
122
+ if on_create
123
+ self[order_column] = new_position
124
+ else
125
+ update_attribute(order_column, new_position)
126
+ end
127
+ end
128
+ else
129
+ # moving from higher to lower, decrement all in between
130
+ # #{order_column} > #{position_in_list} AND #{order_column} <= #{new_position}
131
+ self.class.transaction do
132
+ self.class.update_all(
133
+ "#{order_column} = (#{order_column} - 1)", "#{scope} AND (#{order_column} BETWEEN #{position_in_list + 1} AND #{new_position})"
134
+ )
135
+ update_attribute(order_column, new_position)
136
+ end
137
+ end
138
+ end
139
+
140
+ def reorder_roots
141
+ self.class.transaction do
142
+ self.class.roots(true).each do |root|
143
+ new_position = self.class.roots.index(root) + 1
144
+ root.update_attribute(order_column, new_position) if (root.position_in_list != new_position)
145
+ end
146
+ end
147
+ end
148
+
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,15 @@
1
+ module OrderedTree
2
+ module InstanceMethods
3
+ module Misc
4
+ protected
5
+
6
+ def foreign_key_column
7
+ :"#{ordered_tree_config[:foreign_key]}"
8
+ end
9
+
10
+ def order_column
11
+ :"#{ordered_tree_config[:order]}"
12
+ end
13
+ end
14
+ end
15
+ end