closure_tree 3.0.2 → 3.0.3

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