ordered_tree 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rvmrc +1 -0
- data/CHANGELOG +14 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +57 -0
- data/Guardfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/README.textile +123 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/ordered_tree/class_methods.rb +26 -0
- data/lib/ordered_tree/instance_methods/destroy.rb +40 -0
- data/lib/ordered_tree/instance_methods/list.rb +151 -0
- data/lib/ordered_tree/instance_methods/misc.rb +15 -0
- data/lib/ordered_tree/instance_methods/tree.rb +169 -0
- data/lib/ordered_tree/instance_methods.rb +13 -0
- data/lib/ordered_tree.rb +33 -0
- data/ordered_tree.gemspec +85 -0
- data/spec/fixtures/person.rb +3 -0
- data/spec/ordered_tree_spec.rb +414 -0
- data/spec/spec_helper.rb +114 -0
- metadata +222 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
module OrderedTree
|
2
|
+
module InstanceMethods
|
3
|
+
module Tree
|
4
|
+
## Tree Read Methods
|
5
|
+
|
6
|
+
# returns the top node in the object's tree
|
7
|
+
#
|
8
|
+
# return is cached, unless nil
|
9
|
+
# use root(true) to force a reload
|
10
|
+
def root(reload = false)
|
11
|
+
reload = true if !@root
|
12
|
+
reload ? find_root : @root
|
13
|
+
end
|
14
|
+
|
15
|
+
# returns an array of ancestors, starting from parent until root.
|
16
|
+
# return is cached
|
17
|
+
# use ancestors(true) to force a reload
|
18
|
+
def ancestors(reload = false)
|
19
|
+
reload = true if !@ancestors
|
20
|
+
reload ? find_ancestors : @ancestors
|
21
|
+
end
|
22
|
+
|
23
|
+
# returns object's parent in the tree
|
24
|
+
# auto-loads itself on first access
|
25
|
+
# instead of returning "<parent_node not loaded yet>"
|
26
|
+
#
|
27
|
+
# return is cached, unless nil
|
28
|
+
# use parent(true) to force a reload
|
29
|
+
def parent(reload=false)
|
30
|
+
reload = true if !@parent
|
31
|
+
reload ? parent_node(true) : @parent
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns an array of the object's immediate children
|
35
|
+
# auto-loads itself on first access
|
36
|
+
# instead of returning "<child_nodes not loaded yet>"
|
37
|
+
#
|
38
|
+
# return is cached
|
39
|
+
# use children(true) to force a reload
|
40
|
+
def children(reload=false)
|
41
|
+
reload = true if !@children
|
42
|
+
reload ? child_nodes(true) : @children
|
43
|
+
end
|
44
|
+
|
45
|
+
# returns an array of the object's descendants
|
46
|
+
#
|
47
|
+
# return is cached
|
48
|
+
# use descendants(true) to force a reload
|
49
|
+
def descendants(reload = false)
|
50
|
+
@descendants = nil if reload
|
51
|
+
reload = true if !@descendants
|
52
|
+
reload ? find_descendants(self) : @descendants
|
53
|
+
end
|
54
|
+
|
55
|
+
## Tree Update Methods
|
56
|
+
|
57
|
+
# shifts a node to another parent, optionally specifying it's position
|
58
|
+
# (descendants will follow along)
|
59
|
+
#
|
60
|
+
# shift_to()
|
61
|
+
# defaults to the bottom of the "roots" list
|
62
|
+
#
|
63
|
+
# shift_to(nil, new_sibling)
|
64
|
+
# will move the item to "roots",
|
65
|
+
# and position the item above new_sibling
|
66
|
+
#
|
67
|
+
# shift_to(new_parent)
|
68
|
+
# will move the item to the new parent,
|
69
|
+
# and position at the bottom of the parent's list
|
70
|
+
#
|
71
|
+
# shift_to(new_parent, new_sibling)
|
72
|
+
# will move the item to the new parent,
|
73
|
+
# and position the item above new_sibling
|
74
|
+
#
|
75
|
+
def shift_to(new_parent = nil, new_sibling = nil)
|
76
|
+
if new_parent
|
77
|
+
ok = new_parent.children(true) << self
|
78
|
+
else
|
79
|
+
ok = orphan
|
80
|
+
end
|
81
|
+
if ok && new_sibling
|
82
|
+
ok = move_above(new_sibling) if self_and_siblings(true).include?(new_sibling)
|
83
|
+
end
|
84
|
+
return ok
|
85
|
+
end
|
86
|
+
|
87
|
+
# orphans the node (sends it to the roots list)
|
88
|
+
# (descendants follow)
|
89
|
+
def orphan
|
90
|
+
self[foreign_key_column] = 0
|
91
|
+
self.save
|
92
|
+
end
|
93
|
+
|
94
|
+
# orphans the node's children
|
95
|
+
# sends all immediate children to the 'roots' list
|
96
|
+
def orphan_children
|
97
|
+
self.class.transaction do
|
98
|
+
children(true).each{|child| child.orphan}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# hands children off to parent
|
103
|
+
# if no parent, children will be orphaned
|
104
|
+
def parent_adopts_children
|
105
|
+
if parent(true)
|
106
|
+
self.class.transaction do
|
107
|
+
children(true).each{|child| parent.children << child}
|
108
|
+
end
|
109
|
+
else
|
110
|
+
orphan_children
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# sends self and immediate children to the roots list
|
115
|
+
def orphan_self_and_children
|
116
|
+
self.class.transaction do
|
117
|
+
orphan_children
|
118
|
+
orphan
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# hands children off to parent (if possible), then orphans itself
|
123
|
+
def orphan_self_and_parent_adopts_children
|
124
|
+
self.class.transaction do
|
125
|
+
parent_adopts_children
|
126
|
+
orphan
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
protected
|
132
|
+
|
133
|
+
def check_parentage #:nodoc:
|
134
|
+
if !self_and_siblings(true).include?(self)
|
135
|
+
if self.parent == self
|
136
|
+
errors.add(:base, "cannot be a parent to itself.")
|
137
|
+
elsif (self.parent && self.descendants(true).include?(self.parent))
|
138
|
+
errors.add(:base, "is an ancestor of the new parent.")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def find_root
|
146
|
+
node = self
|
147
|
+
node = node.parent while node.parent(true)
|
148
|
+
node
|
149
|
+
end
|
150
|
+
|
151
|
+
def find_ancestors
|
152
|
+
node, nodes = self, []
|
153
|
+
nodes << node = node.parent while node.parent(true)
|
154
|
+
nodes
|
155
|
+
end
|
156
|
+
|
157
|
+
# recursive method
|
158
|
+
def find_descendants(node)
|
159
|
+
@descendants ||= []
|
160
|
+
node.children(true).each do |child|
|
161
|
+
@descendants << child
|
162
|
+
find_descendants(child)
|
163
|
+
end
|
164
|
+
@descendants
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'ordered_tree/instance_methods/tree'
|
2
|
+
require 'ordered_tree/instance_methods/list'
|
3
|
+
require 'ordered_tree/instance_methods/destroy'
|
4
|
+
require 'ordered_tree/instance_methods/misc'
|
5
|
+
|
6
|
+
module OrderedTree
|
7
|
+
module InstanceMethods
|
8
|
+
include OrderedTree::InstanceMethods::Tree
|
9
|
+
include OrderedTree::InstanceMethods::List
|
10
|
+
include OrderedTree::InstanceMethods::Destroy
|
11
|
+
include OrderedTree::InstanceMethods::Misc
|
12
|
+
end
|
13
|
+
end
|
data/lib/ordered_tree.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'ordered_tree/class_methods'
|
2
|
+
require 'ordered_tree/instance_methods'
|
3
|
+
|
4
|
+
module OrderedTree #:nodoc:
|
5
|
+
# Configuration:
|
6
|
+
#
|
7
|
+
# class Person < ActiveRecord::Base
|
8
|
+
# ordered_tree :foreign_key => :parent_id,
|
9
|
+
# :order => :position
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class CreatePeople < ActiveRecord::Migration
|
13
|
+
# def self.up
|
14
|
+
# create_table :people do |t|
|
15
|
+
# t.column :parent_id ,:integer ,:null => false ,:default => 0
|
16
|
+
# t.column :position ,:integer
|
17
|
+
# end
|
18
|
+
# add_index(:people, :parent_id)
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
|
22
|
+
def ordered_tree(options = {})
|
23
|
+
cattr_accessor :ordered_tree_config
|
24
|
+
self.ordered_tree_config ||= {}
|
25
|
+
self.ordered_tree_config[:foreign_key] ||= :parent_id
|
26
|
+
self.ordered_tree_config[:order] ||= :position
|
27
|
+
self.ordered_tree_config.update(options) if options.is_a?(Hash)
|
28
|
+
include OrderedTree::ClassMethods
|
29
|
+
include OrderedTree::InstanceMethods
|
30
|
+
end #ordered_tree
|
31
|
+
end #module OrderedTree
|
32
|
+
|
33
|
+
ActiveRecord::Base.extend OrderedTree
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{ordered_tree}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Ramon Tayag"]
|
12
|
+
s.date = %q{2011-06-03}
|
13
|
+
s.description = %q{Uses parent_id and position to create an ordered tree.}
|
14
|
+
s.email = %q{ramon@tayag.net}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc",
|
18
|
+
"README.textile"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".rvmrc",
|
22
|
+
"CHANGELOG",
|
23
|
+
"Gemfile",
|
24
|
+
"Gemfile.lock",
|
25
|
+
"Guardfile",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.rdoc",
|
28
|
+
"README.textile",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"lib/ordered_tree.rb",
|
32
|
+
"lib/ordered_tree/class_methods.rb",
|
33
|
+
"lib/ordered_tree/instance_methods.rb",
|
34
|
+
"lib/ordered_tree/instance_methods/destroy.rb",
|
35
|
+
"lib/ordered_tree/instance_methods/list.rb",
|
36
|
+
"lib/ordered_tree/instance_methods/misc.rb",
|
37
|
+
"lib/ordered_tree/instance_methods/tree.rb",
|
38
|
+
"ordered_tree.gemspec",
|
39
|
+
"spec/fixtures/person.rb",
|
40
|
+
"spec/ordered_tree_spec.rb",
|
41
|
+
"spec/spec_helper.rb"
|
42
|
+
]
|
43
|
+
s.homepage = %q{http://github.com/ramontayag/ordered_tree}
|
44
|
+
s.licenses = ["MIT"]
|
45
|
+
s.require_paths = ["lib"]
|
46
|
+
s.rubygems_version = %q{1.6.2}
|
47
|
+
s.summary = %q{Gem version of Wizard's ActsAsTree}
|
48
|
+
|
49
|
+
if s.respond_to? :specification_version then
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 3.0.0"])
|
54
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
|
55
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
56
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
|
57
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
58
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<libnotify>, [">= 0"])
|
61
|
+
s.add_development_dependency(%q<rb-inotify>, [">= 0"])
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.0"])
|
64
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
65
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
66
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
67
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
68
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
69
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
70
|
+
s.add_dependency(%q<libnotify>, [">= 0"])
|
71
|
+
s.add_dependency(%q<rb-inotify>, [">= 0"])
|
72
|
+
end
|
73
|
+
else
|
74
|
+
s.add_dependency(%q<activerecord>, [">= 3.0.0"])
|
75
|
+
s.add_dependency(%q<rspec>, ["~> 2.6.0"])
|
76
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
77
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
|
78
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
79
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
80
|
+
s.add_dependency(%q<guard-rspec>, [">= 0"])
|
81
|
+
s.add_dependency(%q<libnotify>, [">= 0"])
|
82
|
+
s.add_dependency(%q<rb-inotify>, [">= 0"])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|