closure_tree 1.0.0 → 2.0.0.beta1

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.
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "1.0.0" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "2.0.0.beta1" unless defined?(::ClosureTree::VERSION)
3
3
  end
@@ -0,0 +1,18 @@
1
+ sqlite3:
2
+ adapter: <%= "jdbc" if defined? JRUBY_VERSION %>sqlite3
3
+ database: closure_tree.sqlite3.db
4
+ sqlite3mem:
5
+ adapter: <%= "jdbc" if defined? JRUBY_VERSION %>sqlite3
6
+ database: ":memory:"
7
+ postgresql:
8
+ adapter: postgresql
9
+ username: postgres
10
+ password: postgres
11
+ database: closure_tree_plugin_test
12
+ min_messages: ERROR
13
+ mysql:
14
+ adapter: mysql2
15
+ host: localhost
16
+ username: root
17
+ password:
18
+ database: closure_tree_plugin_test
data/spec/db/schema.rb ADDED
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+ ActiveRecord::Schema.define(:version => 0) do
3
+
4
+ create_table "tags", :force => true do |t|
5
+ t.string "name"
6
+ t.string "title"
7
+ t.integer "parent_id"
8
+ t.datetime "created_at"
9
+ t.datetime "updated_at"
10
+ end
11
+
12
+ create_table "tag_hierarchies", :id => false, :force => true do |t|
13
+ t.integer "ancestor_id", :null => false
14
+ t.integer "descendant_id", :null => false
15
+ t.integer "generations", :null => false
16
+ end
17
+
18
+ create_table "destroyed_tags", :force => true do |t|
19
+ t.string "name"
20
+ end
21
+
22
+ add_index :tag_hierarchies, [:ancestor_id, :descendant_id], :unique => true
23
+ add_index :tag_hierarchies, [:descendant_id]
24
+
25
+ create_table "users", :force => true do |t|
26
+ t.string "email"
27
+ t.integer "referrer_id"
28
+ t.datetime "created_at"
29
+ t.datetime "updated_at"
30
+ end
31
+
32
+ create_table "referral_hierarchies", :id => false, :force => true do |t|
33
+ t.integer "ancestor_id", :null => false
34
+ t.integer "descendant_id", :null => false
35
+ t.integer "generations", :null => false
36
+ end
37
+
38
+ add_index :referral_hierarchies, [:ancestor_id, :descendant_id], :unique => true
39
+ add_index :referral_hierarchies, [:descendant_id]
40
+ end
@@ -12,7 +12,7 @@ parent:
12
12
  child:
13
13
  name: child
14
14
  parent: parent
15
- title: Kid
15
+ title: Kid
16
16
 
17
17
  people:
18
18
  name: people
@@ -61,3 +61,36 @@ san_francisco:
61
61
  name: san_francisco
62
62
  parent: california
63
63
 
64
+
65
+ # Move and deletion test tree
66
+
67
+ a1:
68
+ name: a1
69
+
70
+ b1:
71
+ name: b1
72
+ parent: a1
73
+
74
+ b2:
75
+ name: b2
76
+ parent: a1
77
+
78
+ c1a:
79
+ name: c1a
80
+ parent: b1
81
+
82
+ c1b:
83
+ name: c1b
84
+ parent: b1
85
+
86
+ c2:
87
+ name: c2
88
+ parent: b2
89
+
90
+ d2:
91
+ name: d2
92
+ parent: c2
93
+
94
+ e2:
95
+ name: e2
96
+ parent: d2
@@ -0,0 +1,34 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
2
+ plugin_test_dir = File.dirname(__FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+
7
+ require 'rspec'
8
+ require 'logger'
9
+
10
+ require 'active_support'
11
+ require 'active_model'
12
+ require 'active_record'
13
+ require 'action_controller'
14
+
15
+ require 'closure_tree'
16
+
17
+ ActiveRecord::Base.logger = Logger.new(plugin_test_dir + "/debug.log")
18
+
19
+ require 'yaml'
20
+ require 'erb'
21
+ ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(plugin_test_dir + "/db/database.yml")).result)
22
+ ActiveRecord::Base.establish_connection(ENV["DB"] || "mysql")
23
+ ActiveRecord::Migration.verbose = false
24
+ load(File.join(plugin_test_dir, "db", "schema.rb"))
25
+
26
+ require 'support/models'
27
+
28
+ require 'rspec/rails'
29
+ RSpec.configure do |config|
30
+ config.fixture_path = "#{plugin_test_dir}/fixtures"
31
+ # true runs the tests 1 second faster, but then you can't
32
+ # see what's going on while debuggering with different db connections.
33
+ config.use_transactional_fixtures = false
34
+ end
@@ -0,0 +1,26 @@
1
+ class Tag < ActiveRecord::Base
2
+ acts_as_tree :dependent => :destroy
3
+ before_destroy :create_tag
4
+
5
+ def to_s
6
+ name
7
+ end
8
+
9
+ def create_tag
10
+ # Proof for the tests that the destroy rather than the delete method was called:
11
+ DestroyedTag.create(:name => name)
12
+ end
13
+ end
14
+
15
+ class DestroyedTag < ActiveRecord::Base
16
+ end
17
+
18
+ class User < ActiveRecord::Base
19
+ acts_as_tree :parent_column_name => "referrer_id",
20
+ :name_column => 'email',
21
+ :hierarchy_table_name => 'referral_hierarchies'
22
+
23
+ def to_s
24
+ email
25
+ end
26
+ end
data/spec/tag_spec.rb ADDED
@@ -0,0 +1,278 @@
1
+ require 'spec_helper'
2
+
3
+ describe "empty db" do
4
+
5
+ def nuke
6
+ Tag.delete_all
7
+ TagHierarchy.delete_all
8
+ DestroyedTag.delete_all
9
+ end
10
+
11
+ before :each do
12
+ nuke
13
+ end
14
+
15
+ context "empty db" do
16
+ it "should return no entities" do
17
+ Tag.roots.should be_empty
18
+ Tag.leaves.should be_empty
19
+ end
20
+ end
21
+
22
+ context "1 tag db" do
23
+ it "should return the only entity as a root and leaf" do
24
+ a = Tag.create!(:name => "a")
25
+ Tag.roots.should == [a]
26
+ Tag.leaves.should == [a]
27
+ end
28
+ end
29
+
30
+ context "2 tag db" do
31
+ it "should return a simple root and leaf" do
32
+ root = Tag.create!(:name => "root")
33
+ leaf = root.add_child(Tag.create!(:name => "leaf"))
34
+ Tag.roots.should == [root]
35
+ Tag.leaves.should == [leaf]
36
+ end
37
+ end
38
+
39
+ context "3 tag collection.create db" do
40
+ before :each do
41
+ @root = Tag.create! :name => "root"
42
+ @mid = @root.children.create! :name => "mid"
43
+ @leaf = @mid.children.create! :name => "leaf"
44
+ end
45
+
46
+ it "should create all tags" do
47
+ Tag.all.should =~ [@root, @mid, @leaf]
48
+ end
49
+
50
+ it "should return a root and leaf without middle tag" do
51
+ Tag.roots.should == [@root]
52
+ Tag.leaves.should == [@leaf]
53
+ end
54
+
55
+ it "should delete leaves" do
56
+ Tag.leaves.destroy_all
57
+ Tag.roots.should == [@root] # untouched
58
+ Tag.leaves.should == [@mid]
59
+ end
60
+
61
+ it "should delete everything if you delete the roots" do
62
+ Tag.roots.destroy_all
63
+ Tag.all.should be_empty
64
+ Tag.roots.should be_empty
65
+ Tag.leaves.should be_empty
66
+ DestroyedTag.all.collect{|t|t.name}.should =~ %w{root mid leaf}
67
+ end
68
+ end
69
+
70
+ context "3 tag explicit_create db" do
71
+ before :each do
72
+ @root = Tag.create!(:name => "root")
73
+ @mid = @root.add_child(Tag.create!(:name => "mid"))
74
+ @leaf = @mid.add_child(Tag.create!(:name => "leaf"))
75
+ end
76
+
77
+ it "should create all tags" do
78
+ Tag.all.should =~ [@root, @mid, @leaf]
79
+ end
80
+
81
+ it "should return a root and leaf without middle tag" do
82
+ Tag.roots.should == [@root]
83
+ Tag.leaves.should == [@leaf]
84
+ end
85
+
86
+ it "should prevent parental loops" do
87
+ lambda do
88
+ @mid.children << @root
89
+ end.should raise_error
90
+
91
+ lambda do
92
+ @leaf.children << @root
93
+ end.should raise_error
94
+ end
95
+
96
+ it "should support reparenting" do
97
+ @root.children << @leaf
98
+ Tag.leaves.should =~ [@leaf, @mid]
99
+ end
100
+ end
101
+ end
102
+
103
+ describe Tag do
104
+
105
+ fixtures :tags
106
+
107
+ before :each do
108
+ Tag.rebuild!
109
+ end
110
+
111
+ context "class injection" do
112
+ it "should build hierarchy classname correctly" do
113
+ Tag.hierarchy_class.to_s.should == "TagHierarchy"
114
+ Tag.hierarchy_class_name.should == "TagHierarchy"
115
+ end
116
+
117
+ it "should have a correct parent column name" do
118
+ Tag.parent_column_name.should == "parent_id"
119
+ end
120
+ end
121
+
122
+ context "roots" do
123
+ it "should find global roots" do
124
+ roots = Tag.roots.to_a
125
+ roots.should be_member(tags(:people))
126
+ roots.should be_member(tags(:events))
127
+ roots.should_not be_member(tags(:child))
128
+ tags(:people).root?.should be_true
129
+ tags(:child).root?.should be_false
130
+ end
131
+
132
+ it "should find an instance root" do
133
+ tags(:grandparent).root.should == tags(:grandparent)
134
+ tags(:parent).root.should == tags(:grandparent)
135
+ tags(:child).root.should == tags(:grandparent)
136
+ end
137
+ end
138
+
139
+ context "leaves" do
140
+ it "should assemble global leaves" do
141
+ Tag.leaves.size.should > 0
142
+ Tag.leaves.each { |t| t.children.should be_empty, "#{t.name} was returned by leaves but has children: #{t.children}" }
143
+ Tag.leaves.each { |t| t.should be_leaf, "{t.name} was returned by leaves but was not a leaf" }
144
+ end
145
+
146
+ it "should assemble instance leaves" do
147
+ tags(:grandparent).leaves.should == [tags(:child)]
148
+ tags(:parent).leaves.should == [tags(:child)]
149
+ tags(:child).leaves.should == [tags(:child)]
150
+ end
151
+ end
152
+
153
+ context "adding children" do
154
+ it "should work explicitly" do
155
+ sb = Tag.create!(:name => "Santa Barbara")
156
+ sb.leaf?.should_not be_nil
157
+ tags(:california).add_child sb
158
+ sb.leaf?.should_not be_nil
159
+ validate_city_tag sb
160
+ end
161
+
162
+ it "should work implicitly through the collection" do
163
+ eg = Tag.create!(:name => "El Granada")
164
+ eg.leaf?.should_not be_nil
165
+ tags(:california).children << eg
166
+ eg.leaf?.should_not be_nil
167
+ validate_city_tag eg
168
+ end
169
+
170
+ it "should fail to create ancestor loops" do
171
+ lambda do
172
+ tags(:child).add_child(tags(:grandparent))
173
+ end.should raise_error
174
+ end
175
+
176
+ it "should move non-leaves" do
177
+ # This is what the fixture should encode:
178
+ tags(:d2).ancestry_path.should == %w{a1 b2 c2 d2}
179
+ tags(:b1).add_child(tags(:c2))
180
+ tags(:b2).leaf?.should_not be_nil
181
+ tags(:b1).children.include?(tags(:c2)).should_not be_nil
182
+ tags(:d2).reload.ancestry_path.should == %w{a1 b1 c2 d2}
183
+ end
184
+
185
+ it "should move leaves" do
186
+ l = Tag.find_or_create_by_path(%w{leaftest branch1 leaf})
187
+ b2 = Tag.find_or_create_by_path(%w{leaftest branch2})
188
+ b2.children << l
189
+ l.ancestry_path.should == %w{leaftest branch2 leaf}
190
+ end
191
+
192
+ it "should move roots" do
193
+ l1 = Tag.find_or_create_by_path(%w{roottest1 branch1 leaf1})
194
+ l2 = Tag.find_or_create_by_path(%w{roottest2 branch2 leaf2})
195
+ l1.children << l2.root
196
+ l1.ancestry_path.should == %w{roottest1 branch1 leaf1}
197
+ l2.ancestry_path.should == %w{roottest1 branch1 leaf1 roottest2 branch2 leaf2}
198
+ end
199
+
200
+ it "should cascade delete all children" do
201
+ b2 = tags(:b2)
202
+ entities = b2.self_and_descendants.to_a
203
+ names = b2.self_and_descendants.collect{|t|t.name}
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
207
+ end
208
+ end
209
+
210
+ context "injected attributes" do
211
+ it "should compute level correctly" do
212
+ tags(:grandparent).level.should == 0
213
+ tags(:parent).level.should == 1
214
+ tags(:child).level.should == 2
215
+ end
216
+
217
+ it "should determine parent correctly" do
218
+ tags(:grandparent).parent.should == nil
219
+ tags(:parent).parent.should == tags(:grandparent)
220
+ tags(:child).parent.should == tags(:parent)
221
+ end
222
+
223
+ it "should have a sane children collection" do
224
+ tags(:grandparent).children.include? tags(:parent).should_not be_nil
225
+ tags(:parent).children.include? tags(:child).should_not be_nil
226
+ tags(:child).children.empty?.should_not be_nil
227
+ end
228
+
229
+ it "should assemble ancestors correctly" do
230
+ tags(:child).ancestors.should == [tags(:parent), tags(:grandparent)]
231
+ tags(:child).self_and_ancestors.should == [tags(:child), tags(:parent), tags(:grandparent)]
232
+ end
233
+
234
+ it "should assemble descendants correctly" do
235
+ tags(:parent).descendants.should == [tags(:child)]
236
+ tags(:parent).self_and_descendants.should == [tags(:parent), tags(:child)]
237
+ tags(:grandparent).descendants.should == [tags(:parent), tags(:child)]
238
+ tags(:grandparent).self_and_descendants.should == [tags(:grandparent), tags(:parent), tags(:child)]
239
+ tags(:grandparent).self_and_descendants.collect { |t| t.name }.join(" > ").should == "grandparent > parent > child"
240
+ end
241
+ end
242
+
243
+ context "paths" do
244
+
245
+ it "should build ancestry path" do
246
+ tags(:child).ancestry_path.should == %w{grandparent parent child}
247
+ tags(:child).ancestry_path(:name).should == %w{grandparent parent child}
248
+ tags(:child).ancestry_path(:title).should == %w{Nonnie Mom Kid}
249
+ end
250
+
251
+ it "should find by path" do
252
+ # class method:
253
+ Tag.find_by_path(%w{grandparent parent child}).should == tags(:child)
254
+ # instance method:
255
+ tags(:parent).find_by_path(%w{child}).should == tags(:child)
256
+ tags(:grandparent).find_by_path(%w{parent child}).should == tags(:child)
257
+ tags(:parent).find_by_path(%w{child larvae}).should be_nil
258
+ end
259
+
260
+ it "should find or create by path" do
261
+ # class method:
262
+ Tag.find_or_create_by_path(%w{grandparent parent child}).should == tags(:child)
263
+ Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
264
+ a = Tag.find_or_create_by_path(%w{a})
265
+ a.ancestry_path.should == %w{a}
266
+ # instance method:
267
+ a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
268
+ end
269
+ end
270
+
271
+ def validate_city_tag city
272
+ tags(:california).children.include?(city).should_not be_nil
273
+ city.ancestors.should == [tags(:california), tags(:united_states), tags(:places)]
274
+ city.self_and_ancestors.should == [city, tags(:california), tags(:united_states), tags(:places)]
275
+ end
276
+
277
+ end
278
+