closure_tree 2.0.0 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +58 -25
- data/lib/closure_tree/acts_as_tree.rb +56 -30
- data/lib/closure_tree/version.rb +1 -1
- data/spec/db/schema.rb +16 -0
- data/spec/label_spec.rb +67 -0
- data/spec/support/models.rb +18 -2
- data/spec/tag_spec.rb +17 -7
- metadata +12 -9
data/README.md
CHANGED
@@ -105,12 +105,22 @@ Then:
|
|
105
105
|
We can do all the node creation and add_child calls from the prior section with one method call:
|
106
106
|
|
107
107
|
```ruby
|
108
|
-
child = Tag.find_or_create_by_path("grandparent", "parent", "child")
|
108
|
+
child = Tag.find_or_create_by_path(["grandparent", "parent", "child"])
|
109
109
|
```
|
110
110
|
|
111
|
-
You can ```find``` as well as ```find_or_create``` by "ancestry paths".
|
111
|
+
You can ```find``` as well as ```find_or_create``` by "ancestry paths".
|
112
|
+
Ancestry paths may be built using any column in your model. The default
|
113
|
+
column is ```name```, which can be changed with the :name_column option
|
114
|
+
provided to ```acts_as_tree```.
|
112
115
|
|
113
|
-
Note that
|
116
|
+
Note that any other AR fields can be set with the second, optional ```attributes``` argument.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
child = Tag.find_or_create_by_path(%w{home chuck Photos"}, {:tag_type => "File"})
|
120
|
+
```
|
121
|
+
This will pass the attribute hash of ```{:name => "home", :tag_type => "File"}``` to
|
122
|
+
```Tag.find_or_create_by_name``` if the root directory doesn't exist (and
|
123
|
+
```{:name => "chuck", :tag_type => "File"}``` if the second-level tag doesn't exist, and so on).
|
114
124
|
|
115
125
|
### Available options
|
116
126
|
<a id="options" />
|
@@ -129,35 +139,58 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
|
|
129
139
|
|
130
140
|
### Class methods
|
131
141
|
|
132
|
-
* ```
|
133
|
-
* ```
|
134
|
-
* ```
|
142
|
+
* ```Tag.root``` returns an arbitrary root node
|
143
|
+
* ```Tag.roots``` returns all root nodes
|
144
|
+
* ```Tag.leaves``` returns all leaf nodes
|
135
145
|
|
136
146
|
### Instance methods
|
137
147
|
|
138
|
-
* ```
|
139
|
-
* ```
|
140
|
-
* ```
|
141
|
-
* ```
|
142
|
-
* ```
|
143
|
-
* ```
|
144
|
-
* ```
|
145
|
-
* ```
|
146
|
-
* ```
|
147
|
-
* ```
|
148
|
-
* ```
|
149
|
-
* ```
|
150
|
-
* ```
|
151
|
-
* ```
|
152
|
-
* ```
|
153
|
-
|
154
|
-
##
|
155
|
-
|
156
|
-
|
148
|
+
* ```tag.root``` returns the root for this node
|
149
|
+
* ```tag.root?``` returns true if this is a root node
|
150
|
+
* ```tag.child?``` returns true if this is a child node. It has a parent.
|
151
|
+
* ```tag.leaf?``` returns true if this is a leaf node. It has no children.
|
152
|
+
* ```tag.leaves``` returns an array of all the nodes in self_and_descendants that are leaves.
|
153
|
+
* ```tag.level``` returns the level, or "generation", for this node in the tree. A root node == 0.
|
154
|
+
* ```tag.parent``` returns the node's immediate parent. Root nodes will return nil.
|
155
|
+
* ```tag.children``` returns an array of immediate children (just those nodes whose parent is the current node).
|
156
|
+
* ```tag.ancestors``` returns an array of [ parent, grandparent, great grandparent, ... ]. Note that the size of this array will always equal ```tag.level```.
|
157
|
+
* ```tag.self_and_ancestors``` returns an array of self, parent, grandparent, great grandparent, etc.
|
158
|
+
* ```tag.siblings``` returns an array of brothers and sisters (all at that level), excluding self.
|
159
|
+
* ```tag.self_and_siblings``` returns an array of brothers and sisters (all at that level), including self.
|
160
|
+
* ```tag.descendants``` returns an array of all children, childrens' children, etc., excluding self.
|
161
|
+
* ```tag.self_and_descendants``` returns an array of all children, childrens' children, etc., including self.
|
162
|
+
* ```tag.destroy``` will destroy a node and do <em>something</em> to its children, which is determined by the ```:dependent``` option passed to ```acts_as_tree```.
|
163
|
+
|
164
|
+
## Polymorphic hierarchies
|
165
|
+
|
166
|
+
Polymorphic models are supported:
|
167
|
+
|
168
|
+
1. Create a db migration that adds a String ```type``` column to your model
|
169
|
+
2. Subclass the model class. You only need to add acts_as_tree to your base class.
|
170
|
+
|
171
|
+
```ruby
|
172
|
+
class Tag < ActiveRecord::Base
|
173
|
+
acts_as_tree
|
174
|
+
end
|
175
|
+
class WhenTag < Tag ; end
|
176
|
+
class WhereTag < Tag ; end
|
177
|
+
class WhatTag < Tag ; end
|
178
|
+
```
|
179
|
+
|
180
|
+
## Change log
|
181
|
+
|
182
|
+
### 2.0.0
|
157
183
|
|
158
184
|
* Had to increment the major version, as rebuild! will need to be called by prior consumers to support the new ```leaves``` class and instance methods.
|
159
185
|
* Tag deletion is supported now along with ```:dependent => :destroy``` and ```:dependent => :delete_all```
|
160
186
|
* Switched from default rails plugin directory structure to rspec
|
187
|
+
* Support for running specs under different database engines: ```export DB ; for DB in sqlite3 mysql postgresql ; do rake ; done```
|
188
|
+
|
189
|
+
### 3.0.0
|
190
|
+
|
191
|
+
* Support for polymorphic trees
|
192
|
+
* ```find_by_path``` and ```find_or_create_by_path``` signatures changed to support constructor attributes
|
193
|
+
* tested against Rails 3.1.3
|
161
194
|
|
162
195
|
## Thanks to
|
163
196
|
|
@@ -10,6 +10,8 @@ module ClosureTree
|
|
10
10
|
:name_column => 'name'
|
11
11
|
}.merge(options)
|
12
12
|
|
13
|
+
raise IllegalArgumentException, "name_column can't be 'path'" if closure_tree_options[:name_column] == 'path'
|
14
|
+
|
13
15
|
include ClosureTree::Columns
|
14
16
|
extend ClosureTree::Columns
|
15
17
|
|
@@ -19,8 +21,8 @@ module ClosureTree
|
|
19
21
|
self.hierarchy_class = Object.const_set hierarchy_class_name, Class.new(ActiveRecord::Base)
|
20
22
|
|
21
23
|
self.hierarchy_class.class_eval <<-RUBY
|
22
|
-
belongs_to :ancestor, :class_name => "#{
|
23
|
-
belongs_to :descendant, :class_name => "#{
|
24
|
+
belongs_to :ancestor, :class_name => "#{ct_class.to_s}"
|
25
|
+
belongs_to :descendant, :class_name => "#{ct_class.to_s}"
|
24
26
|
RUBY
|
25
27
|
|
26
28
|
include ClosureTree::Model
|
@@ -30,23 +32,23 @@ module ClosureTree
|
|
30
32
|
after_save :acts_as_tree_after_save
|
31
33
|
|
32
34
|
belongs_to :parent,
|
33
|
-
:class_name =>
|
35
|
+
:class_name => ct_class.to_s,
|
34
36
|
:foreign_key => parent_column_name
|
35
37
|
|
36
38
|
has_many :children,
|
37
|
-
:class_name =>
|
39
|
+
:class_name => ct_class.to_s,
|
38
40
|
:foreign_key => parent_column_name,
|
39
41
|
:dependent => closure_tree_options[:dependent]
|
40
42
|
|
41
43
|
has_and_belongs_to_many :self_and_ancestors,
|
42
|
-
:class_name =>
|
44
|
+
:class_name => ct_class.to_s,
|
43
45
|
:join_table => hierarchy_table_name,
|
44
46
|
:foreign_key => "descendant_id",
|
45
47
|
:association_foreign_key => "ancestor_id",
|
46
48
|
:order => "generations asc"
|
47
49
|
|
48
50
|
has_and_belongs_to_many :self_and_descendants,
|
49
|
-
:class_name =>
|
51
|
+
:class_name => ct_class.to_s,
|
50
52
|
:join_table => hierarchy_table_name,
|
51
53
|
:foreign_key => "ancestor_id",
|
52
54
|
:association_foreign_key => "descendant_id",
|
@@ -124,21 +126,39 @@ module ClosureTree
|
|
124
126
|
without_self(self_and_siblings)
|
125
127
|
end
|
126
128
|
|
127
|
-
#
|
129
|
+
# Alias for appending to the children collection.
|
130
|
+
# You can also add directly to the children collection, if you'd prefer.
|
128
131
|
def add_child(child_node)
|
129
132
|
children << child_node
|
130
133
|
child_node
|
131
134
|
end
|
132
135
|
|
133
136
|
# Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+.
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
+
def find_by_path(path)
|
138
|
+
path = [path] unless path.is_a? Enumerable
|
139
|
+
node = self
|
140
|
+
while (!path.empty? && node)
|
141
|
+
node = node.children.send("find_by_#{name_column}", path.shift)
|
142
|
+
end
|
143
|
+
node
|
137
144
|
end
|
138
145
|
|
139
146
|
# Find a child node whose +ancestry_path+ minus self.ancestry_path is +path+
|
140
|
-
def find_or_create_by_path(
|
141
|
-
|
147
|
+
def find_or_create_by_path(path, attributes = {})
|
148
|
+
path = [path] unless path.is_a? Enumerable
|
149
|
+
node = self
|
150
|
+
attrs = {}
|
151
|
+
attrs[:type] = self.type if ct_subclass? && ct_has_type?
|
152
|
+
path.each do |name|
|
153
|
+
attrs[name_sym] = name
|
154
|
+
child = node.children.where(attrs).first
|
155
|
+
unless child
|
156
|
+
child = self.class.new(attributes.merge attrs)
|
157
|
+
node.children << child
|
158
|
+
end
|
159
|
+
node = child
|
160
|
+
end
|
161
|
+
node
|
142
162
|
end
|
143
163
|
|
144
164
|
protected
|
@@ -195,16 +215,6 @@ module ClosureTree
|
|
195
215
|
SQL
|
196
216
|
end
|
197
217
|
|
198
|
-
def foc_by_path(method_prefix, *path)
|
199
|
-
path = path.flatten
|
200
|
-
return self if path.empty?
|
201
|
-
node = self
|
202
|
-
while (!path.empty? && node)
|
203
|
-
node = node.children.send("#{method_prefix}_by_#{name_column}", path.shift)
|
204
|
-
end
|
205
|
-
node
|
206
|
-
end
|
207
|
-
|
208
218
|
def without_self(scope)
|
209
219
|
scope.where(["#{quoted_table_name}.#{self.class.primary_key} != ?", self])
|
210
220
|
end
|
@@ -230,17 +240,21 @@ module ClosureTree
|
|
230
240
|
end
|
231
241
|
|
232
242
|
# Find the node whose +ancestry_path+ is +path+
|
233
|
-
def find_by_path(
|
234
|
-
|
235
|
-
|
236
|
-
r.nil? ? nil : r.find_by_path(*path)
|
243
|
+
def find_by_path(path)
|
244
|
+
root = roots.send("find_by_#{name_column}", path.shift)
|
245
|
+
root.try(:find_by_path, path)
|
237
246
|
end
|
238
247
|
|
239
248
|
# Find or create nodes such that the +ancestry_path+ is +path+
|
240
|
-
def find_or_create_by_path(
|
241
|
-
|
242
|
-
|
243
|
-
|
249
|
+
def find_or_create_by_path(path, attributes = {})
|
250
|
+
name = path.shift
|
251
|
+
# shenanigans because find_or_create can't infer we want the same class as this:
|
252
|
+
# Note that roots will already be constrained to this subclass (in the case of polymorphism):
|
253
|
+
root = roots.send("find_by_#{name_column}", name)
|
254
|
+
if root.nil?
|
255
|
+
root = create!(attributes.merge(name_sym => name))
|
256
|
+
end
|
257
|
+
root.find_or_create_by_path(path, attributes)
|
244
258
|
end
|
245
259
|
end
|
246
260
|
end
|
@@ -289,6 +303,18 @@ module ClosureTree
|
|
289
303
|
(self.is_a?(Class) ? self : self.class)
|
290
304
|
end
|
291
305
|
|
306
|
+
def ct_subclass?
|
307
|
+
ct_class != ct_class.base_class
|
308
|
+
end
|
309
|
+
|
310
|
+
def ct_attribute_names
|
311
|
+
@ct_attr_names ||= ct_class.new.attributes.keys - ct_class.protected_attributes.to_a
|
312
|
+
end
|
313
|
+
|
314
|
+
def ct_has_type?
|
315
|
+
ct_attribute_names.include? 'type'
|
316
|
+
end
|
317
|
+
|
292
318
|
def ct_table_name
|
293
319
|
ct_class.table_name
|
294
320
|
end
|
data/lib/closure_tree/version.rb
CHANGED
data/spec/db/schema.rb
CHANGED
@@ -37,4 +37,20 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
37
37
|
|
38
38
|
add_index :referral_hierarchies, [:ancestor_id, :descendant_id], :unique => true
|
39
39
|
add_index :referral_hierarchies, [:descendant_id]
|
40
|
+
|
41
|
+
create_table "labels", :force => true do |t|
|
42
|
+
t.string "name"
|
43
|
+
t.string "type"
|
44
|
+
t.integer "parent_id"
|
45
|
+
end
|
46
|
+
|
47
|
+
create_table "label_hierarchies", :id => false, :force => true do |t|
|
48
|
+
t.integer "ancestor_id", :null => false
|
49
|
+
t.integer "descendant_id", :null => false
|
50
|
+
t.integer "generations", :null => false
|
51
|
+
end
|
52
|
+
|
53
|
+
add_index :label_hierarchies, [:ancestor_id, :descendant_id], :unique => true
|
54
|
+
add_index :label_hierarchies, [:descendant_id]
|
55
|
+
|
40
56
|
end
|
data/spec/label_spec.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def nuke_db
|
4
|
+
Label.delete_all
|
5
|
+
LabelHierarchy.delete_all
|
6
|
+
end
|
7
|
+
|
8
|
+
describe Label do
|
9
|
+
context "Base Label class" do
|
10
|
+
it "should find or create by path" do
|
11
|
+
# class method:
|
12
|
+
c = Label.find_or_create_by_path(%w{grandparent parent child})
|
13
|
+
c.ancestry_path.should == %w{grandparent parent child}
|
14
|
+
c.name.should == "child"
|
15
|
+
c.parent.name.should == "parent"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context "DateLabel" do
|
19
|
+
|
20
|
+
it "should find or create by path" do
|
21
|
+
date = DateLabel.find_or_create_by_path(%w{2011 November 23})
|
22
|
+
date.ancestry_path.should == %w{2011 November 23}
|
23
|
+
date.parent
|
24
|
+
date.self_and_ancestors.each { |ea| ea.class.should == DateLabel }
|
25
|
+
date.name.should == "23"
|
26
|
+
date.parent.name.should == "November"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "DirectoryLabel" do
|
31
|
+
it "should find or create by path" do
|
32
|
+
dir = DirectoryLabel.find_or_create_by_path(%w{grandparent parent child})
|
33
|
+
dir.ancestry_path.should == %w{grandparent parent child}
|
34
|
+
dir.name.should == "child"
|
35
|
+
dir.parent.name.should == "parent"
|
36
|
+
dir.parent.parent.name.should == "grandparent"
|
37
|
+
dir.root.name.should == "grandparent"
|
38
|
+
dir.id.should_not == Label.find_or_create_by_path(%w{grandparent parent child})
|
39
|
+
dir.self_and_ancestors.each { |ea| ea.class.should == DirectoryLabel }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Mixed class tree" do
|
44
|
+
it "should support mixed type ancestors" do
|
45
|
+
[Label, DateLabel, DirectoryLabel, EventLabel].permutation do |classes|
|
46
|
+
nuke_db
|
47
|
+
classes.each{|c|c.all.should(be_empty, "class #{c} wasn't cleaned out") }
|
48
|
+
names = ('A'..'Z').to_a.first(classes.size)
|
49
|
+
instances = classes.collect { |clazz| clazz.new(:name => names.shift) }
|
50
|
+
a = instances.first
|
51
|
+
a.save!
|
52
|
+
a.name.should == "A"
|
53
|
+
instances[1..-1].each_with_index do |ea, idx|
|
54
|
+
instances[idx].children << ea
|
55
|
+
end
|
56
|
+
roots = classes.first.roots
|
57
|
+
i = instances.shift
|
58
|
+
roots.should =~ [i]
|
59
|
+
while (!instances.empty?) do
|
60
|
+
child = instances.shift
|
61
|
+
i.children.should =~ [child]
|
62
|
+
i = child
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/spec/support/models.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
class Tag < ActiveRecord::Base
|
2
2
|
acts_as_tree :dependent => :destroy
|
3
|
-
before_destroy :
|
3
|
+
before_destroy :add_destroyed_tag
|
4
4
|
|
5
5
|
def to_s
|
6
6
|
name
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
9
|
+
def add_destroyed_tag
|
10
10
|
# Proof for the tests that the destroy rather than the delete method was called:
|
11
11
|
DestroyedTag.create(:name => name)
|
12
12
|
end
|
@@ -24,3 +24,19 @@ class User < ActiveRecord::Base
|
|
24
24
|
email
|
25
25
|
end
|
26
26
|
end
|
27
|
+
|
28
|
+
class Label < ActiveRecord::Base
|
29
|
+
acts_as_tree
|
30
|
+
def to_s
|
31
|
+
"#{self.class}: #{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class EventLabel < Label
|
36
|
+
end
|
37
|
+
|
38
|
+
class DateLabel < Label
|
39
|
+
end
|
40
|
+
|
41
|
+
class DirectoryLabel < Label
|
42
|
+
end
|
data/spec/tag_spec.rb
CHANGED
@@ -2,14 +2,14 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "empty db" do
|
4
4
|
|
5
|
-
def
|
5
|
+
def nuke_db
|
6
6
|
Tag.delete_all
|
7
7
|
TagHierarchy.delete_all
|
8
8
|
DestroyedTag.delete_all
|
9
9
|
end
|
10
10
|
|
11
11
|
before :each do
|
12
|
-
|
12
|
+
nuke_db
|
13
13
|
end
|
14
14
|
|
15
15
|
context "empty db" do
|
@@ -63,7 +63,7 @@ describe "empty db" do
|
|
63
63
|
Tag.all.should be_empty
|
64
64
|
Tag.roots.should be_empty
|
65
65
|
Tag.leaves.should be_empty
|
66
|
-
DestroyedTag.all.collect{|t|t.name}.should =~ %w{root mid leaf}
|
66
|
+
DestroyedTag.all.collect { |t| t.name }.should =~ %w{root mid leaf}
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -200,10 +200,10 @@ describe Tag do
|
|
200
200
|
it "should cascade delete all children" do
|
201
201
|
b2 = tags(:b2)
|
202
202
|
entities = b2.self_and_descendants.to_a
|
203
|
-
names = b2.self_and_descendants.collect{|t|t.name}
|
203
|
+
names = b2.self_and_descendants.collect { |t| t.name }
|
204
204
|
b2.destroy
|
205
|
-
entities.each{|e| Tag.find_by_id(e.id).should be_nil }
|
206
|
-
DestroyedTag.all.collect{|t|t.name}.should =~ names
|
205
|
+
entities.each { |e| Tag.find_by_id(e.id).should be_nil }
|
206
|
+
DestroyedTag.all.collect { |t| t.name }.should =~ names
|
207
207
|
end
|
208
208
|
end
|
209
209
|
|
@@ -257,9 +257,19 @@ describe Tag do
|
|
257
257
|
tags(:parent).find_by_path(%w{child larvae}).should be_nil
|
258
258
|
end
|
259
259
|
|
260
|
+
it "should return nil for missing nodes" do
|
261
|
+
Tag.find_by_path(%w{missing}).should be_nil
|
262
|
+
Tag.find_by_path(%w{grandparent missing}).should be_nil
|
263
|
+
Tag.find_by_path(%w{grandparent parent missing}).should be_nil
|
264
|
+
Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
|
265
|
+
end
|
266
|
+
|
260
267
|
it "should find or create by path" do
|
261
268
|
# class method:
|
262
|
-
Tag.find_or_create_by_path(%w{grandparent
|
269
|
+
grandparent = Tag.find_or_create_by_path(%w{grandparent})
|
270
|
+
grandparent.should == tags(:grandparent)
|
271
|
+
child = Tag.find_or_create_by_path(%w{grandparent parent child})
|
272
|
+
child.should == tags(:child)
|
263
273
|
Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
|
264
274
|
a = Tag.find_or_create_by_path(%w{a})
|
265
275
|
a.ancestry_path.should == %w{a}
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: closure_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 7
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
9
|
- 0
|
10
|
-
version:
|
10
|
+
version: 3.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Matthew McEachen
|
@@ -15,10 +15,12 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-11-
|
18
|
+
date: 2011-11-27 00:00:00 -08:00
|
19
|
+
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
22
24
|
none: false
|
23
25
|
requirements:
|
24
26
|
- - ">="
|
@@ -29,10 +31,9 @@ dependencies:
|
|
29
31
|
- 0
|
30
32
|
- 0
|
31
33
|
version: 3.0.0
|
32
|
-
requirement: *id001
|
33
34
|
type: :runtime
|
34
|
-
prerelease: false
|
35
35
|
name: activerecord
|
36
|
+
version_requirements: *id001
|
36
37
|
description: " A mostly-API-compatible replacement for the acts_as_tree and awesome_nested_set gems,\n but with much better mutation performance thanks to the Closure Tree storage algorithm\n"
|
37
38
|
email:
|
38
39
|
- matthew-github@mceachen.org
|
@@ -53,10 +54,12 @@ files:
|
|
53
54
|
- spec/db/database.yml
|
54
55
|
- spec/db/schema.rb
|
55
56
|
- spec/fixtures/tags.yml
|
57
|
+
- spec/label_spec.rb
|
56
58
|
- spec/spec_helper.rb
|
57
59
|
- spec/support/models.rb
|
58
60
|
- spec/tag_spec.rb
|
59
61
|
- spec/user_spec.rb
|
62
|
+
has_rdoc: true
|
60
63
|
homepage: http://matthew.mceachen.us/closure_tree
|
61
64
|
licenses: []
|
62
65
|
|
@@ -86,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
89
|
requirements: []
|
87
90
|
|
88
91
|
rubyforge_project:
|
89
|
-
rubygems_version: 1.
|
92
|
+
rubygems_version: 1.6.2
|
90
93
|
signing_key:
|
91
94
|
specification_version: 3
|
92
95
|
summary: Hierarchies for ActiveRecord models using a Closure Tree storage algorithm
|
@@ -94,8 +97,8 @@ test_files:
|
|
94
97
|
- spec/db/database.yml
|
95
98
|
- spec/db/schema.rb
|
96
99
|
- spec/fixtures/tags.yml
|
100
|
+
- spec/label_spec.rb
|
97
101
|
- spec/spec_helper.rb
|
98
102
|
- spec/support/models.rb
|
99
103
|
- spec/tag_spec.rb
|
100
104
|
- spec/user_spec.rb
|
101
|
-
has_rdoc:
|