closure_tree 1.0.0 → 2.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+