closure_tree 3.0.2 → 3.0.3

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Closure Tree
2
2
 
3
+ [![Build Status](https://secure.travis-ci.org/mceachen/closure_tree.png?branch=master)](http://travis-ci.org/mceachen/closure_tree)
4
+
3
5
  Closure Tree is a mostly-API-compatible replacement for the
4
6
  acts_as_tree and awesome_nested_set gems, but with much better
5
7
  mutation performance thanks to the Closure Tree storage algorithm,
@@ -9,9 +11,9 @@ See [Bill Karwin](http://karwin.blogspot.com/)'s excellent
9
11
  [Models for hierarchical data presentation](http://www.slideshare.net/billkarwin/models-for-hierarchical-data)
10
12
  for a description of different tree storage algorithms.
11
13
 
12
- ## Setup
14
+ ## Installation
13
15
 
14
- Note that closure_tree supports Rails 3. Rails 2, not so much.
16
+ Note that closure_tree only supports Rails 3.0 and later, and has test coverage for MySQL, PostgreSQL, and SQLite.
15
17
 
16
18
  1. Add this to your Gemfile: ```gem 'closure_tree'```
17
19
 
@@ -21,8 +23,6 @@ Note that closure_tree supports Rails 3. Rails 2, not so much.
21
23
 
22
24
  4. Add a migration to add a ```parent_id``` column to the model you want to act_as_tree.
23
25
 
24
- Note that if the column is null, the tag will be considered a root node.
25
-
26
26
  ```ruby
27
27
  class AddParentIdToTag < ActiveRecord::Migration
28
28
  def change
@@ -31,9 +31,12 @@ Note that closure_tree supports Rails 3. Rails 2, not so much.
31
31
  end
32
32
  ```
33
33
 
34
+ Note that if the column is null, the tag will be considered a root node.
35
+
34
36
  5. Add a database migration to store the hierarchy for your model. By
35
- convention the table name will be the model's table name, followed by
36
- "_hierarchy". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagsHierarchy```) will be added automatically, so you don't need to create it.
37
+ default the table name will be the model's table name, followed by
38
+ "_hierarchies". Note that by calling ```acts_as_tree```, a "virtual model" (in this case, ```TagsHierarchy```)
39
+ will be added automatically, so you don't need to create it.
37
40
 
38
41
  ```ruby
39
42
  class CreateTagHierarchies < ActiveRecord::Migration
@@ -44,10 +47,10 @@ Note that closure_tree supports Rails 3. Rails 2, not so much.
44
47
  t.integer :generations, :null => false # Number of generations between the ancestor and the descendant. Parent/child = 1, for example.
45
48
  end
46
49
 
47
- # For "all progeny of..." selects:
50
+ # For "all progeny of" selects:
48
51
  add_index :tag_hierarchies, [:ancestor_id, :descendant_id], :unique => true
49
52
 
50
- # For "all ancestors of..." selects
53
+ # For "all ancestors of" selects
51
54
  add_index :tag_hierarchies, [:descendant_id]
52
55
  end
53
56
  end
@@ -55,9 +58,9 @@ Note that closure_tree supports Rails 3. Rails 2, not so much.
55
58
 
56
59
  6. Run ```rake db:migrate```
57
60
 
58
- 7. If you're migrating away from another system where your model already has a
61
+ 7. If you're migrating from another system where your model already has a
59
62
  ```parent_id``` column, run ```Tag.rebuild!``` and the
60
- ..._hierarchy table will be truncated and rebuilt.
63
+ _hierarchy table will be truncated and rebuilt.
61
64
 
62
65
  If you're starting from scratch you don't need to call ```rebuild!```.
63
66
 
@@ -154,7 +157,7 @@ When you include ```acts_as_tree``` in your model, you can provide a hash to ove
154
157
  * ```tag.level``` returns the level, or "generation", for this node in the tree. A root node == 0.
155
158
  * ```tag.parent``` returns the node's immediate parent. Root nodes will return nil.
156
159
  * ```tag.children``` returns an array of immediate children (just those nodes whose parent is the current node).
157
- * ```tag.ancestors``` returns an array of [ parent, grandparent, great grandparent, ... ]. Note that the size of this array will always equal ```tag.level```.
160
+ * ```tag.ancestors``` returns an array of [ parent, grandparent, great grandparent, ]. Note that the size of this array will always equal ```tag.level```.
158
161
  * ```tag.self_and_ancestors``` returns an array of self, parent, grandparent, great grandparent, etc.
159
162
  * ```tag.siblings``` returns an array of brothers and sisters (all at that level), excluding self.
160
163
  * ```tag.self_and_siblings``` returns an array of brothers and sisters (all at that level), including self.
@@ -201,6 +204,12 @@ class WhatTag < Tag ; end
201
204
 
202
205
  * Fix for ancestry-loop detection (performed by a validation, not through raising an exception in before_save)
203
206
 
207
+ ### 3.0.3
208
+
209
+ * Added support for ActiveRecord's whitelist_attributes
210
+ (Make sure you read [the Rails Security Guide](http://guides.rubyonrails.org/security.html), and
211
+ enable ```config.active_record.whitelist_attributes``` in your ```config/application.rb``` ASAP!)
212
+
204
213
  ## Thanks to
205
214
 
206
215
  * https://github.com/collectiveidea/awesome_nested_set
@@ -23,6 +23,7 @@ module ClosureTree
23
23
  self.hierarchy_class.class_eval <<-RUBY
24
24
  belongs_to :ancestor, :class_name => "#{ct_class.to_s}"
25
25
  belongs_to :descendant, :class_name => "#{ct_class.to_s}"
26
+ attr_accessible :ancestor, :descendant, :generations
26
27
  RUBY
27
28
 
28
29
  include ClosureTree::Model
@@ -137,7 +138,7 @@ module ClosureTree
137
138
  def find_by_path(path)
138
139
  path = [path] unless path.is_a? Enumerable
139
140
  node = self
140
- while (!path.empty? && node)
141
+ while !path.empty? && node
141
142
  node = node.children.send("find_by_#{name_column}", path.shift)
142
143
  end
143
144
  node
@@ -153,7 +154,7 @@ module ClosureTree
153
154
  attrs[name_sym] = name
154
155
  child = node.children.where(attrs).first
155
156
  unless child
156
- child = self.class.new(attributes.merge attrs)
157
+ child = self.class.new(attributes.merge(attrs))
157
158
  node.children << child
158
159
  end
159
160
  node = child
@@ -173,7 +174,7 @@ module ClosureTree
173
174
 
174
175
  def acts_as_tree_before_save
175
176
  @was_new_record = new_record?
176
- nil # AR will cancel the save if this is falsy
177
+ true # don't cancel the save
177
178
  end
178
179
 
179
180
  def acts_as_tree_after_save
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = "3.0.2" unless defined?(::ClosureTree::VERSION)
2
+ VERSION = "3.0.3" unless defined?(::ClosureTree::VERSION)
3
3
  end
data/spec/db/database.yml CHANGED
@@ -7,12 +7,10 @@ sqlite3mem:
7
7
  postgresql:
8
8
  adapter: postgresql
9
9
  username: postgres
10
- password: postgres
11
- database: closure_tree_plugin_test
10
+ database: closure_tree_test
12
11
  min_messages: ERROR
13
12
  mysql:
14
13
  adapter: mysql2
15
14
  host: localhost
16
15
  username: root
17
- password:
18
- database: closure_tree_plugin_test
16
+ database: closure_tree_test
data/spec/spec_helper.rb CHANGED
@@ -10,7 +10,7 @@ require 'logger'
10
10
  require 'active_support'
11
11
  require 'active_model'
12
12
  require 'active_record'
13
- require 'action_controller'
13
+ require 'action_controller' # rspec-rails needs this :(
14
14
 
15
15
  require 'closure_tree'
16
16
 
@@ -24,8 +24,8 @@ ActiveRecord::Migration.verbose = false
24
24
  load(File.join(plugin_test_dir, "db", "schema.rb"))
25
25
 
26
26
  require 'support/models'
27
+ require 'rspec/rails' # TODO: clean this up-- I don't want to pull the elephant through the mouse hole just for fixture support
27
28
 
28
- require 'rspec/rails'
29
29
  RSpec.configure do |config|
30
30
  config.fixture_path = "#{plugin_test_dir}/fixtures"
31
31
  # true runs the tests 1 second faster, but then you can't
@@ -1,6 +1,7 @@
1
1
  class Tag < ActiveRecord::Base
2
2
  acts_as_tree :dependent => :destroy
3
3
  before_destroy :add_destroyed_tag
4
+ attr_accessible :name
4
5
 
5
6
  def to_s
6
7
  name
@@ -13,12 +14,14 @@ class Tag < ActiveRecord::Base
13
14
  end
14
15
 
15
16
  class DestroyedTag < ActiveRecord::Base
17
+ attr_accessible :name
16
18
  end
17
19
 
18
20
  class User < ActiveRecord::Base
19
21
  acts_as_tree :parent_column_name => "referrer_id",
20
22
  :name_column => 'email',
21
23
  :hierarchy_table_name => 'referral_hierarchies'
24
+ attr_accessible :email, :referrer
22
25
 
23
26
  def to_s
24
27
  email
@@ -27,6 +30,8 @@ end
27
30
 
28
31
  class Label < ActiveRecord::Base
29
32
  acts_as_tree
33
+ attr_accessible :name
34
+
30
35
  def to_s
31
36
  "#{self.class}: #{name}"
32
37
  end
@@ -39,4 +44,4 @@ class DateLabel < Label
39
44
  end
40
45
 
41
46
  class DirectoryLabel < Label
42
- end
47
+ end
data/spec/tag_spec.rb CHANGED
@@ -1,293 +1,307 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe "empty db" do
3
+ shared_examples_for Tag do
4
+ describe "empty db" do
4
5
 
5
- def nuke_db
6
- Tag.delete_all
7
- TagHierarchy.delete_all
8
- DestroyedTag.delete_all
9
- end
10
-
11
- before :each do
12
- nuke_db
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
6
+ def nuke_db
7
+ Tag.delete_all
8
+ TagHierarchy.delete_all
9
+ DestroyedTag.delete_all
19
10
  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
11
 
39
- context "3 tag collection.create db" do
40
12
  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}
13
+ nuke_db
14
+ end
15
+
16
+ context "empty db" do
17
+ it "should return no entities" do
18
+ Tag.roots.should be_empty
19
+ Tag.leaves.should be_empty
20
+ end
21
+ end
22
+
23
+ context "1 tag db" do
24
+ it "should return the only entity as a root and leaf" do
25
+ a = Tag.create!(:name => "a")
26
+ Tag.roots.should == [a]
27
+ Tag.leaves.should == [a]
28
+ end
29
+ end
30
+
31
+ context "2 tag db" do
32
+ it "should return a simple root and leaf" do
33
+ root = Tag.create!(:name => "root")
34
+ leaf = root.add_child(Tag.create!(:name => "leaf"))
35
+ Tag.roots.should == [root]
36
+ Tag.leaves.should == [leaf]
37
+ end
38
+ end
39
+
40
+ context "3 tag collection.create db" do
41
+ before :each do
42
+ @root = Tag.create! :name => "root"
43
+ @mid = @root.children.create! :name => "mid"
44
+ @leaf = @mid.children.create! :name => "leaf"
45
+ end
46
+
47
+ it "should create all tags" do
48
+ Tag.all.should =~ [@root, @mid, @leaf]
49
+ end
50
+
51
+ it "should return a root and leaf without middle tag" do
52
+ Tag.roots.should == [@root]
53
+ Tag.leaves.should == [@leaf]
54
+ end
55
+
56
+ it "should delete leaves" do
57
+ Tag.leaves.destroy_all
58
+ Tag.roots.should == [@root] # untouched
59
+ Tag.leaves.should == [@mid]
60
+ end
61
+
62
+ it "should delete everything if you delete the roots" do
63
+ Tag.roots.destroy_all
64
+ Tag.all.should be_empty
65
+ Tag.roots.should be_empty
66
+ Tag.leaves.should be_empty
67
+ DestroyedTag.all.collect { |t| t.name }.should =~ %w{root mid leaf}
68
+ end
69
+ end
70
+
71
+ context "3 tag explicit_create db" do
72
+ before :each do
73
+ @root = Tag.create!(:name => "root")
74
+ @mid = @root.add_child(Tag.create!(:name => "mid"))
75
+ @leaf = @mid.add_child(Tag.create!(:name => "leaf"))
76
+ end
77
+
78
+ it "should create all tags" do
79
+ Tag.all.should =~ [@root, @mid, @leaf]
80
+ end
81
+
82
+ it "should return a root and leaf without middle tag" do
83
+ Tag.roots.should == [@root]
84
+ Tag.leaves.should == [@leaf]
85
+ end
86
+
87
+ it "should prevent parental loops from torso" do
88
+ @mid.children << @root
89
+ @root.valid?.should be_false
90
+ @mid.reload.children.should == [@leaf]
91
+ end
92
+
93
+ it "should prevent parental loops from toes" do
94
+ @leaf.children << @root
95
+ @root.valid?.should be_false
96
+ @leaf.reload.children.should be_empty
97
+ end
98
+
99
+ it "should support reparenting" do
100
+ @root.children << @leaf
101
+ Tag.leaves.should =~ [@leaf, @mid]
102
+ end
67
103
  end
68
104
  end
69
105
 
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
106
+ describe Tag do
80
107
 
81
- it "should return a root and leaf without middle tag" do
82
- Tag.roots.should == [@root]
83
- Tag.leaves.should == [@leaf]
84
- end
108
+ fixtures :tags
85
109
 
86
- it "should prevent parental loops from torso" do
87
- @mid.children << @root
88
- @root.valid?.should be_false
89
- @mid.reload.children.should == [@leaf]
110
+ before :each do
111
+ Tag.rebuild!
112
+ end
113
+
114
+ context "class injection" do
115
+ it "should build hierarchy classname correctly" do
116
+ Tag.hierarchy_class.to_s.should == "TagHierarchy"
117
+ Tag.hierarchy_class_name.should == "TagHierarchy"
118
+ end
119
+
120
+ it "should have a correct parent column name" do
121
+ Tag.parent_column_name.should == "parent_id"
122
+ end
123
+ end
124
+
125
+ context "roots" do
126
+ it "should find global roots" do
127
+ roots = Tag.roots.to_a
128
+ roots.should be_member(tags(:people))
129
+ roots.should be_member(tags(:events))
130
+ roots.should_not be_member(tags(:child))
131
+ tags(:people).root?.should be_true
132
+ tags(:child).root?.should be_false
133
+ end
134
+
135
+ it "should find an instance root" do
136
+ tags(:grandparent).root.should == tags(:grandparent)
137
+ tags(:parent).root.should == tags(:grandparent)
138
+ tags(:child).root.should == tags(:grandparent)
139
+ end
140
+ end
141
+
142
+ context "leaves" do
143
+ it "should assemble global leaves" do
144
+ Tag.leaves.size.should > 0
145
+ Tag.leaves.each { |t| t.children.should be_empty, "#{t.name} was returned by leaves but has children: #{t.children}" }
146
+ Tag.leaves.each { |t| t.should be_leaf, "{t.name} was returned by leaves but was not a leaf" }
147
+ end
148
+
149
+ it "should assemble instance leaves" do
150
+ tags(:grandparent).leaves.should == [tags(:child)]
151
+ tags(:parent).leaves.should == [tags(:child)]
152
+ tags(:child).leaves.should == [tags(:child)]
153
+ end
154
+ end
155
+
156
+ context "adding children" do
157
+ it "should work explicitly" do
158
+ sb = Tag.create!(:name => "Santa Barbara")
159
+ sb.leaf?.should_not be_nil
160
+ tags(:california).add_child sb
161
+ sb.leaf?.should_not be_nil
162
+ validate_city_tag sb
163
+ end
164
+
165
+ it "should work implicitly through the collection" do
166
+ eg = Tag.create!(:name => "El Granada")
167
+ eg.leaf?.should_not be_nil
168
+ tags(:california).children << eg
169
+ eg.leaf?.should_not be_nil
170
+ validate_city_tag eg
171
+ end
172
+
173
+ it "should fail to create ancestor loops" do
174
+ child = tags(:child)
175
+ parent = child.parent
176
+ child.add_child(parent) # this should fail
177
+ parent.valid?.should be_false
178
+ child.reload.children.should be_empty
179
+ parent.reload.children.should =~ [child]
180
+ end
181
+
182
+ it "should move non-leaves" do
183
+ # This is what the fixture should encode:
184
+ tags(:d2).ancestry_path.should == %w{a1 b2 c2 d2}
185
+ tags(:b1).add_child(tags(:c2))
186
+ tags(:b2).leaf?.should_not be_nil
187
+ tags(:b1).children.include?(tags(:c2)).should_not be_nil
188
+ tags(:d2).reload.ancestry_path.should == %w{a1 b1 c2 d2}
189
+ end
190
+
191
+ it "should move leaves" do
192
+ l = Tag.find_or_create_by_path(%w{leaftest branch1 leaf})
193
+ b2 = Tag.find_or_create_by_path(%w{leaftest branch2})
194
+ b2.children << l
195
+ l.ancestry_path.should == %w{leaftest branch2 leaf}
196
+ end
197
+
198
+ it "should move roots" do
199
+ l1 = Tag.find_or_create_by_path(%w{roottest1 branch1 leaf1})
200
+ l2 = Tag.find_or_create_by_path(%w{roottest2 branch2 leaf2})
201
+ l1.children << l2.root
202
+ l1.ancestry_path.should == %w{roottest1 branch1 leaf1}
203
+ l2.ancestry_path.should == %w{roottest1 branch1 leaf1 roottest2 branch2 leaf2}
204
+ end
205
+
206
+ it "should cascade delete all children" do
207
+ b2 = tags(:b2)
208
+ entities = b2.self_and_descendants.to_a
209
+ names = b2.self_and_descendants.collect { |t| t.name }
210
+ b2.destroy
211
+ entities.each { |e| Tag.find_by_id(e.id).should be_nil }
212
+ DestroyedTag.all.collect { |t| t.name }.should =~ names
213
+ end
214
+ end
215
+
216
+ context "injected attributes" do
217
+ it "should compute level correctly" do
218
+ tags(:grandparent).level.should == 0
219
+ tags(:parent).level.should == 1
220
+ tags(:child).level.should == 2
221
+ end
222
+
223
+ it "should determine parent correctly" do
224
+ tags(:grandparent).parent.should == nil
225
+ tags(:parent).parent.should == tags(:grandparent)
226
+ tags(:child).parent.should == tags(:parent)
227
+ end
228
+
229
+ it "should have a sane children collection" do
230
+ tags(:grandparent).children.include? tags(:parent).should_not be_nil
231
+ tags(:parent).children.include? tags(:child).should_not be_nil
232
+ tags(:child).children.empty?.should_not be_nil
233
+ end
234
+
235
+ it "should assemble ancestors correctly" do
236
+ tags(:child).ancestors.should == [tags(:parent), tags(:grandparent)]
237
+ tags(:child).self_and_ancestors.should == [tags(:child), tags(:parent), tags(:grandparent)]
238
+ end
239
+
240
+ it "should assemble descendants correctly" do
241
+ tags(:parent).descendants.should == [tags(:child)]
242
+ tags(:parent).self_and_descendants.should == [tags(:parent), tags(:child)]
243
+ tags(:grandparent).descendants.should == [tags(:parent), tags(:child)]
244
+ tags(:grandparent).self_and_descendants.should == [tags(:grandparent), tags(:parent), tags(:child)]
245
+ tags(:grandparent).self_and_descendants.collect { |t| t.name }.join(" > ").should == "grandparent > parent > child"
246
+ end
247
+ end
248
+
249
+ context "paths" do
250
+
251
+ it "should build ancestry path" do
252
+ tags(:child).ancestry_path.should == %w{grandparent parent child}
253
+ tags(:child).ancestry_path(:name).should == %w{grandparent parent child}
254
+ tags(:child).ancestry_path(:title).should == %w{Nonnie Mom Kid}
255
+ end
256
+
257
+ it "should find by path" do
258
+ # class method:
259
+ Tag.find_by_path(%w{grandparent parent child}).should == tags(:child)
260
+ # instance method:
261
+ tags(:parent).find_by_path(%w{child}).should == tags(:child)
262
+ tags(:grandparent).find_by_path(%w{parent child}).should == tags(:child)
263
+ tags(:parent).find_by_path(%w{child larvae}).should be_nil
264
+ end
265
+
266
+ it "should return nil for missing nodes" do
267
+ Tag.find_by_path(%w{missing}).should be_nil
268
+ Tag.find_by_path(%w{grandparent missing}).should be_nil
269
+ Tag.find_by_path(%w{grandparent parent missing}).should be_nil
270
+ Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
271
+ end
272
+
273
+ it "should find or create by path" do
274
+ # class method:
275
+ grandparent = Tag.find_or_create_by_path(%w{grandparent})
276
+ grandparent.should == tags(:grandparent)
277
+ child = Tag.find_or_create_by_path(%w{grandparent parent child})
278
+ child.should == tags(:child)
279
+ Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
280
+ a = Tag.find_or_create_by_path(%w{a})
281
+ a.ancestry_path.should == %w{a}
282
+ # instance method:
283
+ a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
284
+ end
285
+ end
286
+
287
+ def validate_city_tag city
288
+ tags(:california).children.include?(city).should_not be_nil
289
+ city.ancestors.should == [tags(:california), tags(:united_states), tags(:places)]
290
+ city.self_and_ancestors.should == [city, tags(:california), tags(:united_states), tags(:places)]
90
291
  end
91
292
 
92
- it "should prevent parental loops from toes" do
93
- @leaf.children << @root
94
- @root.valid?.should be_false
95
- @leaf.reload.children.should be_empty
96
- end
97
-
98
- it "should support reparenting" do
99
- @root.children << @leaf
100
- Tag.leaves.should =~ [@leaf, @mid]
101
- end
102
293
  end
103
294
  end
104
295
 
105
296
  describe Tag do
297
+ it_behaves_like Tag
298
+ end
106
299
 
107
- fixtures :tags
108
-
109
- before :each do
110
- Tag.rebuild!
111
- end
112
-
113
- context "class injection" do
114
- it "should build hierarchy classname correctly" do
115
- Tag.hierarchy_class.to_s.should == "TagHierarchy"
116
- Tag.hierarchy_class_name.should == "TagHierarchy"
117
- end
118
-
119
- it "should have a correct parent column name" do
120
- Tag.parent_column_name.should == "parent_id"
121
- end
122
- end
123
-
124
- context "roots" do
125
- it "should find global roots" do
126
- roots = Tag.roots.to_a
127
- roots.should be_member(tags(:people))
128
- roots.should be_member(tags(:events))
129
- roots.should_not be_member(tags(:child))
130
- tags(:people).root?.should be_true
131
- tags(:child).root?.should be_false
132
- end
133
-
134
- it "should find an instance root" do
135
- tags(:grandparent).root.should == tags(:grandparent)
136
- tags(:parent).root.should == tags(:grandparent)
137
- tags(:child).root.should == tags(:grandparent)
138
- end
139
- end
140
-
141
- context "leaves" do
142
- it "should assemble global leaves" do
143
- Tag.leaves.size.should > 0
144
- Tag.leaves.each { |t| t.children.should be_empty, "#{t.name} was returned by leaves but has children: #{t.children}" }
145
- Tag.leaves.each { |t| t.should be_leaf, "{t.name} was returned by leaves but was not a leaf" }
146
- end
147
-
148
- it "should assemble instance leaves" do
149
- tags(:grandparent).leaves.should == [tags(:child)]
150
- tags(:parent).leaves.should == [tags(:child)]
151
- tags(:child).leaves.should == [tags(:child)]
152
- end
153
- end
154
-
155
- context "adding children" do
156
- it "should work explicitly" do
157
- sb = Tag.create!(:name => "Santa Barbara")
158
- sb.leaf?.should_not be_nil
159
- tags(:california).add_child sb
160
- sb.leaf?.should_not be_nil
161
- validate_city_tag sb
162
- end
163
-
164
- it "should work implicitly through the collection" do
165
- eg = Tag.create!(:name => "El Granada")
166
- eg.leaf?.should_not be_nil
167
- tags(:california).children << eg
168
- eg.leaf?.should_not be_nil
169
- validate_city_tag eg
170
- end
171
-
172
- it "should fail to create ancestor loops" do
173
- child = tags(:child)
174
- parent = child.parent
175
- child.add_child(parent) # this should fail
176
- parent.valid?.should be_false
177
- child.reload.children.should be_empty
178
- parent.reload.children.should =~ [child]
179
- end
180
-
181
- it "should move non-leaves" do
182
- # This is what the fixture should encode:
183
- tags(:d2).ancestry_path.should == %w{a1 b2 c2 d2}
184
- tags(:b1).add_child(tags(:c2))
185
- tags(:b2).leaf?.should_not be_nil
186
- tags(:b1).children.include?(tags(:c2)).should_not be_nil
187
- tags(:d2).reload.ancestry_path.should == %w{a1 b1 c2 d2}
188
- end
189
-
190
- it "should move leaves" do
191
- l = Tag.find_or_create_by_path(%w{leaftest branch1 leaf})
192
- b2 = Tag.find_or_create_by_path(%w{leaftest branch2})
193
- b2.children << l
194
- l.ancestry_path.should == %w{leaftest branch2 leaf}
195
- end
196
-
197
- it "should move roots" do
198
- l1 = Tag.find_or_create_by_path(%w{roottest1 branch1 leaf1})
199
- l2 = Tag.find_or_create_by_path(%w{roottest2 branch2 leaf2})
200
- l1.children << l2.root
201
- l1.ancestry_path.should == %w{roottest1 branch1 leaf1}
202
- l2.ancestry_path.should == %w{roottest1 branch1 leaf1 roottest2 branch2 leaf2}
203
- end
204
-
205
- it "should cascade delete all children" do
206
- b2 = tags(:b2)
207
- entities = b2.self_and_descendants.to_a
208
- names = b2.self_and_descendants.collect { |t| t.name }
209
- b2.destroy
210
- entities.each { |e| Tag.find_by_id(e.id).should be_nil }
211
- DestroyedTag.all.collect { |t| t.name }.should =~ names
212
- end
213
- end
214
-
215
- context "injected attributes" do
216
- it "should compute level correctly" do
217
- tags(:grandparent).level.should == 0
218
- tags(:parent).level.should == 1
219
- tags(:child).level.should == 2
220
- end
221
-
222
- it "should determine parent correctly" do
223
- tags(:grandparent).parent.should == nil
224
- tags(:parent).parent.should == tags(:grandparent)
225
- tags(:child).parent.should == tags(:parent)
226
- end
227
-
228
- it "should have a sane children collection" do
229
- tags(:grandparent).children.include? tags(:parent).should_not be_nil
230
- tags(:parent).children.include? tags(:child).should_not be_nil
231
- tags(:child).children.empty?.should_not be_nil
232
- end
233
-
234
- it "should assemble ancestors correctly" do
235
- tags(:child).ancestors.should == [tags(:parent), tags(:grandparent)]
236
- tags(:child).self_and_ancestors.should == [tags(:child), tags(:parent), tags(:grandparent)]
237
- end
238
-
239
- it "should assemble descendants correctly" do
240
- tags(:parent).descendants.should == [tags(:child)]
241
- tags(:parent).self_and_descendants.should == [tags(:parent), tags(:child)]
242
- tags(:grandparent).descendants.should == [tags(:parent), tags(:child)]
243
- tags(:grandparent).self_and_descendants.should == [tags(:grandparent), tags(:parent), tags(:child)]
244
- tags(:grandparent).self_and_descendants.collect { |t| t.name }.join(" > ").should == "grandparent > parent > child"
245
- end
246
- end
247
-
248
- context "paths" do
249
-
250
- it "should build ancestry path" do
251
- tags(:child).ancestry_path.should == %w{grandparent parent child}
252
- tags(:child).ancestry_path(:name).should == %w{grandparent parent child}
253
- tags(:child).ancestry_path(:title).should == %w{Nonnie Mom Kid}
254
- end
255
-
256
- it "should find by path" do
257
- # class method:
258
- Tag.find_by_path(%w{grandparent parent child}).should == tags(:child)
259
- # instance method:
260
- tags(:parent).find_by_path(%w{child}).should == tags(:child)
261
- tags(:grandparent).find_by_path(%w{parent child}).should == tags(:child)
262
- tags(:parent).find_by_path(%w{child larvae}).should be_nil
263
- end
264
-
265
- it "should return nil for missing nodes" do
266
- Tag.find_by_path(%w{missing}).should be_nil
267
- Tag.find_by_path(%w{grandparent missing}).should be_nil
268
- Tag.find_by_path(%w{grandparent parent missing}).should be_nil
269
- Tag.find_by_path(%w{grandparent parent missing child}).should be_nil
270
- end
271
-
272
- it "should find or create by path" do
273
- # class method:
274
- grandparent = Tag.find_or_create_by_path(%w{grandparent})
275
- grandparent.should == tags(:grandparent)
276
- child = Tag.find_or_create_by_path(%w{grandparent parent child})
277
- child.should == tags(:child)
278
- Tag.find_or_create_by_path(%w{events anniversary}).ancestry_path.should == %w{events anniversary}
279
- a = Tag.find_or_create_by_path(%w{a})
280
- a.ancestry_path.should == %w{a}
281
- # instance method:
282
- a.find_or_create_by_path(%w{b c}).ancestry_path.should == %w{a b c}
283
- end
284
- end
285
-
286
- def validate_city_tag city
287
- tags(:california).children.include?(city).should_not be_nil
288
- city.ancestors.should == [tags(:california), tags(:united_states), tags(:places)]
289
- city.self_and_ancestors.should == [city, tags(:california), tags(:united_states), tags(:places)]
300
+ describe "Tag with AR whitelisted attributes enabled" do
301
+ before(:all) do
302
+ ActiveRecord::Base.attr_accessible(nil) # turn on whitelisted attributes
303
+ ActiveRecord::Base.subclasses.each{|ea|ea.reset_column_information}
290
304
  end
291
-
305
+ it_behaves_like Tag
292
306
  end
293
307
 
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: 3
4
+ hash: 1
5
5
  prerelease:
6
6
  segments:
7
7
  - 3
8
8
  - 0
9
- - 2
10
- version: 3.0.2
9
+ - 3
10
+ version: 3.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthew McEachen
@@ -15,11 +15,10 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-02-07 00:00:00 Z
18
+ date: 2012-03-11 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  prerelease: false
22
- type: :runtime
23
22
  requirement: &id001 !ruby/object:Gem::Requirement
24
23
  none: false
25
24
  requirements:
@@ -31,8 +30,121 @@ dependencies:
31
30
  - 0
32
31
  - 0
33
32
  version: 3.0.0
34
- name: activerecord
35
33
  version_requirements: *id001
34
+ name: activerecord
35
+ type: :runtime
36
+ - !ruby/object:Gem::Dependency
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ version_requirements: *id002
48
+ name: rake
49
+ type: :development
50
+ - !ruby/object:Gem::Dependency
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
60
+ version: "0"
61
+ version_requirements: *id003
62
+ name: yard
63
+ type: :development
64
+ - !ruby/object:Gem::Dependency
65
+ prerelease: false
66
+ requirement: &id004 !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ hash: 3
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ version_requirements: *id004
76
+ name: rspec
77
+ type: :development
78
+ - !ruby/object:Gem::Dependency
79
+ prerelease: false
80
+ requirement: &id005 !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ version_requirements: *id005
90
+ name: rails
91
+ type: :development
92
+ - !ruby/object:Gem::Dependency
93
+ prerelease: false
94
+ requirement: &id006 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ version_requirements: *id006
104
+ name: rspec-rails
105
+ type: :development
106
+ - !ruby/object:Gem::Dependency
107
+ prerelease: false
108
+ requirement: &id007 !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ version_requirements: *id007
118
+ name: mysql2
119
+ type: :development
120
+ - !ruby/object:Gem::Dependency
121
+ prerelease: false
122
+ requirement: &id008 !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ version_requirements: *id008
132
+ name: pg
133
+ type: :development
134
+ - !ruby/object:Gem::Dependency
135
+ prerelease: false
136
+ requirement: &id009 !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ hash: 3
142
+ segments:
143
+ - 0
144
+ version: "0"
145
+ version_requirements: *id009
146
+ name: sqlite3
147
+ type: :development
36
148
  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
149
  email:
38
150
  - matthew-github@mceachen.org
@@ -46,7 +158,6 @@ files:
46
158
  - lib/closure_tree/acts_as_tree.rb
47
159
  - lib/closure_tree/version.rb
48
160
  - lib/closure_tree.rb
49
- - lib/tasks/closure_tree_tasks.rake
50
161
  - MIT-LICENSE
51
162
  - Rakefile
52
163
  - README.md
@@ -87,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
198
  requirements: []
88
199
 
89
200
  rubyforge_project:
90
- rubygems_version: 1.8.15
201
+ rubygems_version: 1.8.17
91
202
  signing_key:
92
203
  specification_version: 3
93
204
  summary: Hierarchies for ActiveRecord models using a Closure Tree storage algorithm
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :closure_tree do
3
- # # Task goes here
4
- # end