closure_tree 1.0.0.beta1 → 1.0.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +13 -1
- data/lib/closure_tree/acts_as_tree.rb +61 -1
- data/lib/closure_tree/version.rb +1 -1
- data/test/dummy/config/database.yml +6 -0
- data/test/dummy/test/unit/tag_test.rb +23 -0
- metadata +3 -3
data/README.rdoc
CHANGED
@@ -34,7 +34,7 @@ Note that closure_tree is being developed for Rails 3.1.0.rc1
|
|
34
34
|
|
35
35
|
class CreateTagHierarchy < ActiveRecord::Migration
|
36
36
|
def change
|
37
|
-
create_table :tags_hierarchy do |t|
|
37
|
+
create_table :tags_hierarchy, :id => false do |t|
|
38
38
|
t.integer :ancestor_id, :null => false # ID of the parent/grandparent/great-grandparent/... tag
|
39
39
|
t.integer :descendant_id, :null => false # ID of the target tag
|
40
40
|
t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
|
@@ -79,6 +79,18 @@ Then:
|
|
79
79
|
puts grandparent.self_and_descendants.collect{ |t| t.name }.join(" > ")
|
80
80
|
"grandparent > parent > child"
|
81
81
|
|
82
|
+
child.ancestry_names
|
83
|
+
["grandparent", "parent", "child"]
|
84
|
+
|
85
|
+
=== <code>find_or_create_by_path</code>
|
86
|
+
|
87
|
+
If your model has a single string column that can build a given node (like a "name" or a "title"),
|
88
|
+
you can use that to find_or_create:
|
89
|
+
|
90
|
+
Tag.find_or_create_by_path %w{one two three}
|
91
|
+
|
92
|
+
You can customize the column name with the :name_column option in <code>acts_as_tree</code>.
|
93
|
+
|
82
94
|
== Accessing Data
|
83
95
|
|
84
96
|
=== Class methods
|
@@ -6,7 +6,8 @@ module ClosureTree #:nodoc:
|
|
6
6
|
self.closure_tree_options = {
|
7
7
|
:parent_column_name => 'parent_id',
|
8
8
|
:dependent => :delete_all, # or :destroy
|
9
|
-
:hierarchy_table_suffix => '_hierarchies'
|
9
|
+
:hierarchy_table_suffix => '_hierarchies',
|
10
|
+
:name_column => 'name'
|
10
11
|
}.merge(options)
|
11
12
|
|
12
13
|
include ClosureTree::Columns
|
@@ -90,6 +91,12 @@ module ClosureTree #:nodoc:
|
|
90
91
|
[self].concat ancestors.to_a
|
91
92
|
end
|
92
93
|
|
94
|
+
# Returns an array, root first, of self_and_ancestors' +name_column+ values
|
95
|
+
# (so child.ancestry_names == +%w{grandparent parent child}+
|
96
|
+
def ancestry_names
|
97
|
+
self_and_ancestors.reverse.collect { |n| n.send name_column.to_sym }
|
98
|
+
end
|
99
|
+
|
93
100
|
def self_and_descendants
|
94
101
|
[self].concat descendants.to_a
|
95
102
|
end
|
@@ -121,8 +128,26 @@ module ClosureTree #:nodoc:
|
|
121
128
|
new_parent.add_child self
|
122
129
|
end
|
123
130
|
|
131
|
+
# Find a child node whose +ancestry_names+ minus self.ancestry_names is +path+
|
132
|
+
def find_by_path path
|
133
|
+
_find_or_create_by_path path, "find_by_#{name_column}".to_sym
|
134
|
+
end
|
135
|
+
|
136
|
+
# Find a child node whose +ancestry_names+ minus self.ancestry_names is +path+
|
137
|
+
def find_or_create_by_path path
|
138
|
+
_find_or_create_by_path path, "find_or_create_by_#{name_column}".to_sym
|
139
|
+
end
|
140
|
+
|
124
141
|
protected
|
125
142
|
|
143
|
+
def _find_or_create_by_path path, methodname
|
144
|
+
node = self
|
145
|
+
while (name = path.shift and node)
|
146
|
+
node = node.children.send(methodname, name)
|
147
|
+
end
|
148
|
+
node
|
149
|
+
end
|
150
|
+
|
126
151
|
def without_self(scope)
|
127
152
|
scope.where(["#{quoted_table_name}.#{self.class.primary_key} != ?", self])
|
128
153
|
end
|
@@ -145,6 +170,29 @@ module ClosureTree #:nodoc:
|
|
145
170
|
nil
|
146
171
|
end
|
147
172
|
|
173
|
+
# Find the node whose +ancestry_names+ is +path+
|
174
|
+
def find_by_path path
|
175
|
+
self.where(name_sym => path.last).each do |n|
|
176
|
+
return n if path == n.ancestry_names
|
177
|
+
end
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
|
181
|
+
# Find or create nodes such that the +ancestry_names+ is +path+
|
182
|
+
def find_or_create_by_path path
|
183
|
+
# short-circuit if we can:
|
184
|
+
n = find_by_path path
|
185
|
+
return n if n
|
186
|
+
|
187
|
+
name = path.shift
|
188
|
+
node = roots.where(name_sym => name).first
|
189
|
+
node = create!(name_sym => name) unless node
|
190
|
+
while (name = path.shift)
|
191
|
+
node = node.children.send("find_or_create_by_#{name_column}".to_sym, name)
|
192
|
+
end
|
193
|
+
node
|
194
|
+
end
|
195
|
+
|
148
196
|
private
|
149
197
|
def rebuild_node_and_children node
|
150
198
|
node.parent.add_child node if node.parent
|
@@ -162,6 +210,18 @@ module ClosureTree #:nodoc:
|
|
162
210
|
closure_tree_options[:parent_column_name]
|
163
211
|
end
|
164
212
|
|
213
|
+
def has_name?
|
214
|
+
ct_class.new.attributes.include? closure_tree_options[:name_column]
|
215
|
+
end
|
216
|
+
|
217
|
+
def name_column
|
218
|
+
closure_tree_options[:name_column]
|
219
|
+
end
|
220
|
+
|
221
|
+
def name_sym
|
222
|
+
name_column.to_sym
|
223
|
+
end
|
224
|
+
|
165
225
|
def hierarchy_table_name
|
166
226
|
ct_table_name + closure_tree_options[:hierarchy_table_suffix]
|
167
227
|
end
|
data/lib/closure_tree/version.rb
CHANGED
@@ -56,6 +56,29 @@ class TagTest < ActiveSupport::TestCase
|
|
56
56
|
assert_equal [tags(:child), tags(:parent), tags(:grandparent)], tags(:child).self_and_ancestors
|
57
57
|
end
|
58
58
|
|
59
|
+
def test_ancestry_path
|
60
|
+
assert_equal %w{grandparent parent child}, tags(:child).ancestry_names
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_find_by_path
|
64
|
+
# class method:
|
65
|
+
assert_equal tags(:child), Tag.find_by_path(%w{grandparent parent child})
|
66
|
+
assert_equal tags(:child), Tag.find_or_create_by_path(%w{grandparent parent child})
|
67
|
+
# instance method:
|
68
|
+
assert_equal tags(:child), tags(:parent).find_by_path(%w{child})
|
69
|
+
assert_equal tags(:child), tags(:grandparent).find_by_path(%w{parent child})
|
70
|
+
assert_nil tags(:parent).find_by_path(%w{child larvae})
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_find_or_create_by_path
|
74
|
+
# class method:
|
75
|
+
assert_equal %w{events anniversary}, Tag.find_or_create_by_path(%w{events anniversary}).ancestry_names
|
76
|
+
a = Tag.find_or_create_by_path(%w{a})
|
77
|
+
assert_equal %w{a}, a.ancestry_names
|
78
|
+
# instance method:
|
79
|
+
assert_equal %w{a b c}, a.find_or_create_by_path(%w{b c}).ancestry_names
|
80
|
+
end
|
81
|
+
|
59
82
|
def test_descendants
|
60
83
|
assert_equal [tags(:child)], tags(:parent).descendants
|
61
84
|
assert_equal [tags(:parent), tags(:child)], tags(:parent).self_and_descendants
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: closure_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 1.0.0.
|
5
|
+
version: 1.0.0.beta2
|
6
6
|
platform: ruby
|
7
7
|
authors: []
|
8
8
|
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
13
|
+
date: 2011-05-25 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -73,7 +73,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
hash:
|
76
|
+
hash: -1571970483886197148
|
77
77
|
segments:
|
78
78
|
- 0
|
79
79
|
version: "0"
|