path_tree 1.0.12 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96e7dd6bb5121febc2b06677e4bf4e4ae089fc7c
4
- data.tar.gz: da856ee73c84f9e429e816719c07631ac49755c4
3
+ metadata.gz: d8fbfdfca21ebd5334ea1829ed80ed67675dca0a
4
+ data.tar.gz: 8f0d28fd3a6517030a207a65de808ce5f35c676c
5
5
  SHA512:
6
- metadata.gz: 21b99a6057ca538a29978716acc9f8fddfcea9f17bad139d45941a6f0335b94f11d799c68640c130c587986deedbff6e9cbaa35f1e699dd7a07d6a6c63edcde6
7
- data.tar.gz: 2d56fcd98cd005d446eab85e70524864f88c4df81499f016542193d3c99197940e3f475d54391ea67deead52129be9952632d82489ad9c08904f38b171e39d9f
6
+ metadata.gz: 3a63b1fb4e7d51d403fdc185e09454caa0c0d4e595a42bee6b5a9ec13a1a564494ca5cad8203470b1ea0138d712c90f2e46fe2108e2d96d591296ef0ebc43186
7
+ data.tar.gz: 1b64fd875d59d5a283b5066c0787f953bfd539da2205cad4f3f7099a66ed3c80c17d4b34f259a065dc40cae302bac9b3fc6dad1dd71947977dc4d4082edc6fff
data/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
- Gemfile.lock
2
- *.swm
3
- *.swn
4
- *.swo
5
- *.swp
1
+ *.sw[mnop]
6
2
  *.gem
7
- pkg
3
+ /Gemfile.lock
4
+ /gemfiles/*.lock
5
+ /pkg
6
+ /.bundle
@@ -0,0 +1,18 @@
1
+ # Install gems for all appraisal definitions:
2
+ #
3
+ # $ appraisal install
4
+ #
5
+ # To run tests on different versions:
6
+ #
7
+ # $ appraisal activerecord_x.x rspec spec
8
+
9
+ [
10
+ [ '3.2', '~> 3.2.0' ],
11
+ [ '4.0', '~> 4.0.0' ],
12
+ [ '4.1', '~> 4.1.0' ],
13
+ [ '4.2', '~> 4.2.0' ],
14
+ ].each do |ver_name, ver_req|
15
+ appraise "rails_#{ver_name}" do
16
+ gem 'activerecord', ver_req
17
+ end
18
+ end
data/Rakefile CHANGED
@@ -1 +1,21 @@
1
1
  require 'bundler/gem_tasks'
2
+ require 'bundler/setup' # constrain rake version
3
+
4
+ # Note: if you get this error: "Bundler could not find compatible versions for gem ..."
5
+ # try deleting Gemfile.lock (usually happens when switching branches).
6
+
7
+ task default: :appraise_all
8
+
9
+ # This is slightly different from 'appraisal COMMAND' because it continues even if a definition fails.
10
+ desc "Run rspecs for all appraisals"
11
+ task :appraise_all do
12
+ success_map = {}
13
+ `bundle exec appraisal list`.lines.map(&:chomp).each do |appraise_def|
14
+ success = system('appraisal', appraise_def, 'rspec', 'spec')
15
+ success_map[appraise_def] = success
16
+ end
17
+ puts "\n===== Test Summary ====="
18
+ success_map.each do |appraise_def, success|
19
+ puts "#{appraise_def}: #{success ? 'no failures (but check pending)' : 'failed'}"
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 3.2.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.0.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 4.2.0"
6
+
7
+ gemspec :path => "../"
@@ -1,3 +1,5 @@
1
+ require File.expand_path("../path_tree/patterns.rb", __FILE__)
2
+
1
3
  # This module implements a tree structure by using a convention of converting a name into a path.
2
4
  # Paths created by normalizing a name attribute and then separating levels with periods with
3
5
  # the lowest level coming last.
@@ -6,11 +8,6 @@
6
8
  # have support for after_destroy and after_save callbacks, validates_* macros and include attributes
7
9
  # for name, node_path, path, and parent_path.
8
10
  module PathTree
9
- if RUBY_VERSION.match(/^1\.8/)
10
- require File.expand_path("../ruby_18_patterns.rb", __FILE__)
11
- else
12
- require File.expand_path("../ruby_19_patterns.rb", __FILE__)
13
- end
14
11
  include Patterns
15
12
 
16
13
  def self.included (base)
@@ -45,7 +42,7 @@ module PathTree
45
42
 
46
43
  # Get all the root nodes (i.e. those without any parents)
47
44
  def roots
48
- all(:conditions => {:parent_path => nil})
45
+ where(parent_path: nil).to_a
49
46
  end
50
47
 
51
48
  # Set the path delimiter (default is '.').
@@ -61,7 +58,7 @@ module PathTree
61
58
  # tree, this is the fastest way to load it. Returns the root node of the branch.
62
59
  def branch (path)
63
60
  raise ArgumentError.new("branch path must not be blank") if path.blank?
64
- root = first(:conditions => {:path => path})
61
+ root = where(path: path).first
65
62
  return [] unless root
66
63
  nodes = path_like(path).sort{|a,b| b.path <=> a.path}
67
64
  nodes << root
@@ -76,7 +73,8 @@ module PathTree
76
73
  end
77
74
  end
78
75
 
79
- # Replace accented characters with the closest ascii equivalent
76
+ # Replace accented characters with the closest ascii equivalent.
77
+ # NOTE: characters than can't be decomposed to a latin equivalent may not be replaced.
80
78
  def asciify (value)
81
79
  if value
82
80
  value.gsub(UPPER_A_PATTERN, 'A').gsub(LOWER_A_PATTERN, 'a').
@@ -99,7 +97,7 @@ module PathTree
99
97
 
100
98
  # Abstract way of finding paths that start with a value so it can be overridden by non-SQL implementations.
101
99
  def path_like (value)
102
- all(:conditions => ["path LIKE ?", "#{value}#{path_delimiter}%"])
100
+ where("path LIKE ?", "#{value}#{path_delimiter}%").to_a
103
101
  end
104
102
 
105
103
  # Expand a path into an array of the path and all its ancestor paths.
@@ -156,7 +154,7 @@ module PathTree
156
154
  def parent
157
155
  unless instance_variable_defined?(:@parent)
158
156
  if path.index(path_delimiter)
159
- @parent = self.class.base_class.first(:conditions => {:path => parent_path})
157
+ @parent = self.class.base_class.where(path: parent_path).first
160
158
  else
161
159
  @parent = nil
162
160
  end
@@ -199,7 +197,7 @@ module PathTree
199
197
  def children
200
198
  unless @children
201
199
  childrens_path = new_record? ? path : path_was
202
- @children = self.class.base_class.all(:conditions => {:parent_path => childrens_path})
200
+ @children = self.class.base_class.where(parent_path: childrens_path).to_a
203
201
  @children.each{|c| c.parent = self}
204
202
  end
205
203
  @children
@@ -207,7 +205,8 @@ module PathTree
207
205
 
208
206
  # Get all nodes that share the same parent as this node.
209
207
  def siblings
210
- self.class.base_class.all(:conditions => {:parent_path => parent_path}).reject{|node| node == self}
208
+ # OPTIMIZE if this record has an ID, it may be more efficient to exclude it via query rather than using reject
209
+ self.class.base_class.where(parent_path: parent_path).to_a.reject{|node| node == self }
211
210
  end
212
211
 
213
212
  # Get all descendant of this node.
@@ -222,7 +221,7 @@ module PathTree
222
221
  if ancestor_paths.empty?
223
222
  []
224
223
  else
225
- self.class.base_class.all(:conditions => {:path => ancestor_paths}).sort{|a,b| a.path.length <=> b.path.length}
224
+ self.class.base_class.where(path: ancestor_paths).to_a.sort{|a,b| a.path.length <=> b.path.length}
226
225
  end
227
226
  end
228
227
 
@@ -1,3 +1,3 @@
1
1
  module PathTree
2
- VERSION = '1.0.12'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'path_tree/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'path_tree'
7
- spec.version = PathTree::VERSION
7
+ spec.version = PathTree::VERSION.dup # dup for ruby 1.9
8
8
  spec.authors = ['Brian Durand', 'Milan Dobrota']
9
9
  spec.email = ['mdobrota@tribpub.com']
10
10
  spec.summary = 'Helper module for constructing tree data structures'
@@ -16,11 +16,12 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
17
  spec.require_paths = ['lib']
18
18
 
19
- spec.add_runtime_dependency 'activerecord', '~> 3.2.0'
19
+ spec.add_runtime_dependency 'activerecord', '>= 3.2.0', '< 4.3'
20
20
 
21
- spec.add_development_dependency 'rspec', '~> 2.8.0'
21
+ spec.add_development_dependency 'rspec', '~> 2.99'
22
22
  spec.add_development_dependency 'sqlite3', '>= 0'
23
23
 
24
- spec.add_development_dependency 'bundler', '~> 1.7'
25
- spec.add_development_dependency 'rake', '~> 10.0'
24
+ spec.add_development_dependency 'bundler' , '~> 1.7'
25
+ spec.add_development_dependency 'rake' , '~> 10.0'
26
+ spec.add_development_dependency 'appraisal', '~> 2.0'
26
27
  end
@@ -1,30 +1,9 @@
1
1
  # encoding: UTF-8
2
2
 
3
3
  require 'spec_helper'
4
+ require 'shared/path_tree_model_examples'
4
5
 
5
6
  describe PathTree do
6
-
7
- module PathTree
8
- class Test < ActiveRecord::Base
9
- self.table_name = :test_path_trees
10
-
11
- def self.create_tables
12
- connection.create_table(table_name) do |t|
13
- t.string :name
14
- t.string :node_path
15
- t.string :path
16
- t.string :parent_path
17
- end unless table_exists?
18
- end
19
-
20
- def self.drop_tables
21
- connection.drop_table(table_name)
22
- end
23
-
24
- include PathTree
25
- end
26
- end
27
-
28
7
  before :all do
29
8
  PathTree::Test.create_tables
30
9
  end
@@ -32,245 +11,83 @@ describe PathTree do
32
11
  after :all do
33
12
  PathTree::Test.drop_tables
34
13
  end
35
-
36
- context "path construction" do
37
- it "should turn accented characters into ascii equivalents" do
38
- PathTree::Test.asciify("ÀÂÄÃÅàâáããä-ÈÊÉËèêéë-ÌÎÍÏìîíï-ÒÔÖØÓÕòôöøóõ-ÚÜÙÛúüùû-ÝýÿÑñÇçÆæßÐ").should == "AAAAAaaaaaa-EEEEeeee-IIIIiiii-OOOOOOoooooo-UUUUuuuu-YyyNnCcAEaessD"
39
- end
40
-
41
- it "should unquote strings" do
42
- PathTree::Test.unquote(%Q("This is a 'test'")).should == "This is a test"
43
- end
44
-
45
- it "should translate a value to a path part" do
46
- PathTree::Test.pathify("This is the 1st / test À...").should == "this-is-the-1st-test-a"
47
- end
48
-
49
- it "should expand a path to its component paths" do
50
- PathTree::Test.expanded_paths("this.is.a.test").should == ["this", "this.is", "this.is.a", "this.is.a.test"]
51
- end
52
-
53
- it "should set the parent path when setting the parent" do
54
- parent = PathTree::Test.new(:name => "parent")
55
- node = PathTree::Test.new(:name => "child")
56
- node.parent = parent
57
- node.parent_path.should == "parent"
58
- node.parent = nil
59
- node.parent_path.should == nil
60
- end
61
- end
62
-
63
- context "with default delimiter" do
64
- before :all do
65
- @root_1 = PathTree::Test.create!(:name => "Root 1")
66
- @parent_a = PathTree::Test.create!(:name => "Parent A", :parent_path => "root-1")
67
- @parent_b = PathTree::Test.create!(:name => "Parent B", :parent_path => "root-1")
68
- @parent_c = PathTree::Test.create!(:name => "Parent C", :parent_path => "root-1")
69
- @child_a1 = PathTree::Test.create!(:name => "Child A1", :parent_path => "root-1.parent-a")
70
- @child_a2 = PathTree::Test.create!(:name => "Child A2", :parent_path => "root-1.parent-a")
71
- @grandchild = PathTree::Test.create!(:name => "Grandchild A1.1", :parent_path => "root-1.parent-a.child-a1")
72
- @root_2 = PathTree::Test.create!(:name => "Root 2")
73
- @parent_z = PathTree::Test.create!(:name => "Parent Z", :parent_path => "root-2")
74
- end
75
-
76
- after :all do
77
- PathTree::Test.delete_all
78
- end
79
-
80
- it "should get the root nodes" do
81
- PathTree::Test.roots.sort{|a,b| a.path <=> b.path}.should == [@root_1, @root_2]
82
- end
83
-
84
- it "should load an entire branch structure" do
85
- branch = PathTree::Test.branch("root-1.parent-a")
86
- branch.should == @parent_a
87
- branch.instance_variable_get(:@children).should == [@child_a1, @child_a2]
88
- branch.children.first.instance_variable_get(:@children).should == [@grandchild]
89
- end
90
-
91
- it "should construct a fully qualified name with a delimiter" do
92
- @grandchild.full_name.should == "Root 1 > Parent A > Child A1 > Grandchild A1.1"
93
- @grandchild.full_name(:separator => "/").should == "Root 1/Parent A/Child A1/Grandchild A1.1"
94
- @grandchild.full_name(:context => "root-1.parent-a").should == "Child A1 > Grandchild A1.1"
95
- end
96
-
97
- it "should be able to get and set a parent node" do
98
- node = PathTree::Test.find_by_path("root-1.parent-a")
99
- node.parent.should == @root_1
100
- node.parent = @root_2
101
- node.parent_path.should == "root-2"
102
- node.path.should == "root-2.parent-a"
103
- end
104
-
105
- it "should be able to set the parent by path" do
106
- node = PathTree::Test.find_by_path("root-1.parent-a")
107
- node.parent_path = "root-2"
108
- node.parent.should == @root_2
109
- node.path.should == "root-2.parent-a"
110
- end
111
-
112
- it "should have child nodes" do
113
- node = PathTree::Test.find_by_path("root-1.parent-a")
114
- node.children.should == [@child_a1, @child_a2]
115
- end
116
14
 
117
- it "should have descendant nodes" do
118
- node = PathTree::Test.find_by_path("root-1.parent-a")
119
- node.descendants.should == [@child_a1, @child_a2, @grandchild]
120
- end
15
+ context "validations" do
16
+ let(:new_rec) { PathTree::Test.new }
121
17
 
122
- it "should have sibling nodes" do
123
- node = PathTree::Test.find_by_path("root-1.parent-a")
124
- node.siblings.should == [@parent_b, @parent_c]
18
+ it "is invalid without a name" do
19
+ expect(new_rec).not_to be_valid
20
+ expect(new_rec.errors[:name]).to include "can't be blank"
125
21
  end
126
22
 
127
- it "should have ancestor nodes" do
128
- node = PathTree::Test.find_by_path("root-1.parent-a.child-a1")
129
- node.ancestors.should == [@root_1, @parent_a]
23
+ it "is invalid without a node_path" do
24
+ new_rec.name = "I'm a leaf"
25
+ new_rec.node_path = ''
26
+ expect(new_rec).not_to be_valid
27
+ expect(new_rec.errors[:node_path]).to include "can't be blank"
130
28
  end
131
29
 
132
- it "should maintain the path with the name path" do
133
- node = PathTree::Test.find_by_path("root-1.parent-a")
134
- node.node_path = "New Name"
135
- node.path.should == "root-1.new-name"
30
+ it "is invalid without a path" do
31
+ new_rec.name = "I'm a leaf"
32
+ new_rec.path = ''
33
+ expect(new_rec).not_to be_valid
34
+ expect(new_rec.errors[:path]).to include "can't be blank"
136
35
  end
137
36
 
138
- it "should get the expanded paths for a node" do
139
- @grandchild.expanded_paths.should == ["root-1", "root-1.parent-a", "root-1.parent-a.child-a1", "root-1.parent-a.child-a1.grandchild-a1-1"]
140
- end
37
+ context "uniqueness" do
38
+ around :each do |ex|
39
+ PathTree::Test.transaction do
40
+ @root = PathTree::Test.create!(name: 'top-dog')
41
+ @child_1 = PathTree::Test.create!(name: 'underdog', parent: @root)
42
+ ex.run
43
+ raise ActiveRecord::Rollback
44
+ end
45
+ end
141
46
 
142
- it "should update child paths when the path is changed" do
143
- PathTree::Test.transaction do
144
- node = PathTree::Test.find_by_path("root-1.parent-a")
145
- node.node_path = "New Name"
146
- node.save!
147
- node.reload
148
- node.children.collect{|c| c.path}.should == ["root-1.new-name.child-a1", "root-1.new-name.child-a2"]
149
- node.children.first.children.collect{|c| c.path}.should == ["root-1.new-name.child-a1.grandchild-a1-1"]
150
- raise ActiveRecord::Rollback
47
+ it "enforces unique path" do
48
+ new_rec = PathTree::Test.new(name: @child_1.node_path, parent: @root)
49
+ expect(new_rec).not_to be_valid
50
+ expect(new_rec.errors[:path]).to include 'has already been taken'
51
+ expect(new_rec.errors[:node_path]).to include 'has already been taken'
151
52
  end
152
- end
153
53
 
154
- it "should update child paths when a node is destroyed" do
155
- PathTree::Test.transaction do
156
- node = PathTree::Test.find_by_path("root-1.parent-a")
157
- node.name = "New Name"
158
- node.destroy
159
- root = PathTree::Test.find_by_path("root-1")
160
- root.children.collect{|c| c.path}.should == ["root-1.parent-b", "root-1.parent-c", "root-1.child-a1", "root-1.child-a2"]
161
- root.children[2].children.collect{|c| c.path}.should == ["root-1.child-a1.grandchild-a1-1"]
162
- raise ActiveRecord::Rollback
54
+ it "allows duplicate node_path under different parents" do
55
+ new_rec = PathTree::Test.new(name: @child_1.node_path)
56
+ expect(new_rec).to be_valid
163
57
  end
164
58
  end
165
59
  end
166
-
167
60
 
168
- context "with default delimiter" do
169
- before :all do
170
- PathTree::Test.path_delimiter = '/'
171
- @root_1 = PathTree::Test.create!(:name => "Root 1")
172
- @parent_a = PathTree::Test.create!(:name => "Parent A", :parent_path => "root-1")
173
- @parent_b = PathTree::Test.create!(:name => "Parent B", :parent_path => "root-1")
174
- @parent_c = PathTree::Test.create!(:name => "Parent C", :parent_path => "root-1")
175
- @child_a1 = PathTree::Test.create!(:name => "Child A1", :parent_path => "root-1/parent-a")
176
- @child_a2 = PathTree::Test.create!(:name => "Child A2", :parent_path => "root-1/parent-a")
177
- @grandchild = PathTree::Test.create!(:name => "Grandchild A1.1", :parent_path => "root-1/parent-a/child-a1")
178
- @root_2 = PathTree::Test.create!(:name => "Root 2")
179
- @parent_z = PathTree::Test.create!(:name => "Parent Z", :parent_path => "root-2")
180
- end
181
-
182
- after :all do
183
- PathTree::Test.path_delimiter = nil
184
- end
185
-
186
- it "should get the root nodes" do
187
- PathTree::Test.roots.sort{|a,b| a.path <=> b.path}.should == [@root_1, @root_2]
188
- end
189
-
190
- it "should load an entire branch structure" do
191
- branch = PathTree::Test.branch("root-1/parent-a")
192
- branch.should == @parent_a
193
- branch.instance_variable_get(:@children).should == [@child_a1, @child_a2]
194
- branch.children.first.instance_variable_get(:@children).should == [@grandchild]
195
- end
196
-
197
- it "should construct a fully qualified name with a delimiter" do
198
- @grandchild.full_name.should == "Root 1 > Parent A > Child A1 > Grandchild A1.1"
199
- @grandchild.full_name(:separator => ":").should == "Root 1:Parent A:Child A1:Grandchild A1.1"
200
- @grandchild.full_name(:context => "root-1/parent-a").should == "Child A1 > Grandchild A1.1"
201
- end
202
-
203
- it "should be able to get and set a parent node" do
204
- node = PathTree::Test.find_by_path("root-1/parent-a")
205
- node.parent.should == @root_1
206
- node.parent = @root_2
207
- node.parent_path.should == "root-2"
208
- node.path.should == "root-2/parent-a"
209
- end
210
-
211
- it "should be able to set the parent by path" do
212
- node = PathTree::Test.find_by_path("root-1/parent-a")
213
- node.parent_path = "root-2"
214
- node.parent.should == @root_2
215
- node.path.should == "root-2/parent-a"
216
- end
217
-
218
- it "should have child nodes" do
219
- node = PathTree::Test.find_by_path("root-1/parent-a")
220
- node.children.should == [@child_a1, @child_a2]
221
- end
222
-
223
- it "should have descendant nodes" do
224
- node = PathTree::Test.find_by_path("root-1/parent-a")
225
- node.descendants.should == [@child_a1, @child_a2, @grandchild]
226
- end
227
-
228
- it "should have sibling nodes" do
229
- node = PathTree::Test.find_by_path("root-1/parent-a")
230
- node.siblings.should == [@parent_b, @parent_c]
61
+ context "path construction" do
62
+ it "turns accented characters into ascii equivalents" do
63
+ accent_string = "ÀÂÄÃÅàâáããä-ÈÊÉËèêéë-ÌÎÍÏìîíï-ÒÔÖØÓÕòôöøóõ-ÚÜÙÛúüùû-ÝýÿÑñÇçÆæßÐ"
64
+ expected_string = "AAAAAaaaaaa-EEEEeeee-IIIIiiii-OOOOOOoooooo-UUUUuuuu-YyyNnCcAEaessD"
65
+ expect(PathTree::Test.asciify(accent_string)).to eq expected_string
231
66
  end
232
67
 
233
- it "should have ancestor nodes" do
234
- node = PathTree::Test.find_by_path("root-1/parent-a/child-a1")
235
- node.ancestors.should == [@root_1, @parent_a]
68
+ it "unquotes strings" do
69
+ expect(PathTree::Test.unquote(%Q("This is a 'test'"))).to eq "This is a test"
236
70
  end
237
71
 
238
- it "should maintain the path with the name path" do
239
- node = PathTree::Test.find_by_path("root-1/parent-a")
240
- node.node_path = "New Name"
241
- node.path.should == "root-1/new-name"
72
+ it "translates a value to a path part" do
73
+ expect(PathTree::Test.pathify("This is the 1st / test À...")).to eq "this-is-the-1st-test-a"
242
74
  end
243
75
 
244
- it "should get the expanded paths for a node" do
245
- @grandchild.expanded_paths.should == ["root-1", "root-1/parent-a", "root-1/parent-a/child-a1", "root-1/parent-a/child-a1/grandchild-a1-1"]
76
+ it "sets the parent path when setting the parent" do
77
+ parent = PathTree::Test.new(:name => "parent")
78
+ node = PathTree::Test.new(:name => "child")
79
+ node.parent = parent
80
+ expect(node.parent_path).to eq "parent"
81
+ node.parent = nil
82
+ expect(node.parent_path).to be nil
246
83
  end
84
+ end
247
85
 
248
- it "should update child paths when the path is changed" do
249
- PathTree::Test.transaction do
250
- node = PathTree::Test.find_by_path("root-1/parent-a")
251
- node.node_path = "New Name"
252
- node.save!
253
- node.reload
254
- node.children.collect{|c| c.path}.should == ["root-1/new-name/child-a1", "root-1/new-name/child-a2"]
255
- node.children.first.children.collect{|c| c.path}.should == ["root-1/new-name/child-a1/grandchild-a1-1"]
256
- raise ActiveRecord::Rollback
257
- end
258
- end
86
+ context "with default delimiter" do
87
+ include_examples "a PathTree model", nil, '/'
88
+ end
259
89
 
260
- it "should update child paths when a node is destroyed" do
261
- begin
262
- PathTree::Test.transaction do
263
- node = PathTree::Test.find_by_path("root-1/parent-a")
264
- node.name = "New Name"
265
- node.destroy
266
- root = PathTree::Test.find_by_path("root-1")
267
- root.children.collect{|c| c.path}.should == ["root-1/parent-b", "root-1/parent-c", "root-1/child-a1", "root-1/child-a2"]
268
- root.children[2].children.collect{|c| c.path}.should == ["root-1/child-a1/grandchild-a1-1"]
269
- raise ActiveRecord::Rollback
270
- end
271
- rescue
272
- puts $@.join("\n")
273
- end
274
- end
90
+ context "with custom delimiter" do
91
+ include_examples "a PathTree model", '/', ':'
275
92
  end
276
93
  end
@@ -0,0 +1,170 @@
1
+ # path_delimiter is used for configuring the model; use nil for default
2
+ # fq_separator: separator for fully qualified name
3
+ shared_examples "a PathTree model" do |path_delimiter, fq_separator|
4
+ # actual_delim: the generated paths will be delimited by this
5
+ attr_reader :path_delim, :fq_sep, :actual_delim
6
+
7
+ before :all do
8
+ @path_delim = path_delimiter
9
+ @fq_sep = fq_separator or raise 'fq_separator must be set'
10
+
11
+ @actual_delim = path_delim || '.'.freeze
12
+ if $DEBUG
13
+ puts "OPTIONS: path_delim=#{path_delim.inspect}, fq_sep=#{fq_sep.inspect}"
14
+ end
15
+ end
16
+
17
+ def delim_join(*strings)
18
+ strings.join(actual_delim)
19
+ end
20
+ def fq_join(*strings)
21
+ strings.join(fq_sep)
22
+ end
23
+
24
+ before :all do
25
+ PathTree::Test.path_delimiter = path_delim if path_delim
26
+ @root_1 = PathTree::Test.create!(:name => "Root 1")
27
+ @parent_a = PathTree::Test.create!(:name => "Parent A", :parent_path => "root-1")
28
+ @parent_b = PathTree::Test.create!(:name => "Parent B", :parent_path => "root-1")
29
+ @parent_c = PathTree::Test.create!(:name => "Parent C", :parent_path => "root-1")
30
+ @child_a1 = PathTree::Test.create!(:name => "Child A1", :parent_path => delim_join("root-1", "parent-a"))
31
+ @child_a2 = PathTree::Test.create!(:name => "Child A2", :parent_path => delim_join("root-1", "parent-a"))
32
+ @grandchild = PathTree::Test.create!(:name => "Grandchild A1.1", :parent_path => delim_join("root-1", "parent-a", "child-a1"))
33
+ @root_2 = PathTree::Test.create!(:name => "Root 2")
34
+ @parent_z = PathTree::Test.create!(:name => "Parent Z", :parent_path => "root-2")
35
+ end
36
+
37
+ after :all do
38
+ PathTree::Test.delete_all
39
+ PathTree::Test.path_delimiter = nil # reset to default
40
+ end
41
+
42
+ let(:unsaved_rec) { PathTree::Test.new(name: 'level-2', parent: @root_1) }
43
+
44
+ it "gets the root nodes" do
45
+ expect(PathTree::Test.roots).to match_array [@root_1, @root_2]
46
+ end
47
+
48
+ it "loads an entire branch structure" do
49
+ branch = PathTree::Test.branch(delim_join("root-1", "parent-a"))
50
+ expect(branch).to eq @parent_a
51
+ expect(branch.instance_variable_get(:@children)).to eq [@child_a1, @child_a2]
52
+ expect(branch.children.first.instance_variable_get(:@children)).to eq [@grandchild]
53
+ end
54
+
55
+ it "constructs a fully qualified name with a delimiter" do
56
+ expect(@grandchild.full_name).to eq "Root 1 > Parent A > Child A1 > Grandchild A1.1"
57
+ expect(@grandchild.full_name(:separator => fq_sep)).to eq(
58
+ fq_join("Root 1", "Parent A", "Child A1", "Grandchild A1.1") )
59
+ expect(@grandchild.full_name(:context => delim_join("root-1", "parent-a"))).to eq "Child A1 > Grandchild A1.1"
60
+ end
61
+
62
+ it "is able to get and set a parent node" do
63
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
64
+ expect(node.parent).to eq @root_1
65
+ node.parent = @root_2
66
+ expect(node.parent_path).to eq "root-2"
67
+ expect(node.path).to eq delim_join("root-2", "parent-a")
68
+ end
69
+
70
+ it "is able to set the parent by path" do
71
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
72
+ node.parent_path = "root-2"
73
+ expect(node.parent).to eq @root_2
74
+ expect(node.path).to eq delim_join("root-2", "parent-a")
75
+ end
76
+
77
+ it "has child nodes" do
78
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
79
+ expect(node.children).to eq [@child_a1, @child_a2]
80
+ end
81
+
82
+ it "has descendant nodes" do
83
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
84
+ expect(node.descendants).to eq [@child_a1, @child_a2, @grandchild]
85
+ end
86
+
87
+ it "has sibling nodes" do
88
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
89
+ expect(node.siblings).to eq [@parent_b, @parent_c]
90
+ end
91
+
92
+ it "has ancestor nodes" do
93
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a", "child-a1"))
94
+ expect(node.ancestors).to eq [@root_1, @parent_a]
95
+ end
96
+
97
+ context "when name is changed" do
98
+ it "fills in a blank node_path when name is changed" do
99
+ unsaved_rec.node_path = ''
100
+
101
+ unsaved_rec.name = 'Changed Parent'
102
+ expect(unsaved_rec.node_path).to eq 'changed-parent'
103
+ expect(unsaved_rec.path).to eq delim_join('root-1', 'changed-parent')
104
+ end
105
+
106
+ it "does not overwrite a present node_path when name is changed" do
107
+ orig_node_path = unsaved_rec.node_path
108
+ unsaved_rec.name = 'Changed Parent'
109
+ expect(unsaved_rec.node_path).to eq orig_node_path
110
+ expect(unsaved_rec.path).to eq delim_join('root-1', orig_node_path)
111
+ end
112
+ end
113
+
114
+ it "maintains the path with the node path" do
115
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
116
+ node.node_path = "New Name"
117
+ expect(node.path).to eq delim_join("root-1", "new-name")
118
+ end
119
+
120
+ it "expands a path to its component paths" do
121
+ expect(PathTree::Test.expanded_paths(delim_join(*%w[this is a test]))).to eq [
122
+ "this",
123
+ delim_join(*%w[this is]),
124
+ delim_join(*%w[this is a]),
125
+ delim_join(*%w[this is a test]) ]
126
+ end
127
+
128
+ it "gets the expanded paths for a node" do
129
+ expect(@grandchild.expanded_paths).to eq [
130
+ "root-1",
131
+ delim_join("root-1", "parent-a"),
132
+ delim_join("root-1", "parent-a", "child-a1"),
133
+ delim_join("root-1", "parent-a", "child-a1", "grandchild-a1-1") ]
134
+ end
135
+
136
+ context "child paths" do
137
+ around :each do |ex|
138
+ PathTree::Test.transaction do
139
+ ex.run
140
+ raise ActiveRecord::Rollback
141
+ end
142
+ end
143
+
144
+ it "updates child paths when the path is changed" do
145
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
146
+ node.node_path = "New Name"
147
+ node.save!
148
+ node.reload
149
+ expect(node.children.map(&:path)).to eq [
150
+ delim_join("root-1", "new-name", "child-a1"),
151
+ delim_join("root-1", "new-name", "child-a2") ]
152
+ expect(node.children.first.children.map(&:path)).to eq [
153
+ delim_join("root-1", "new-name", "child-a1", "grandchild-a1-1") ]
154
+ end
155
+
156
+ it "updates child paths when a node is destroyed" do
157
+ node = PathTree::Test.find_by_path(delim_join("root-1", "parent-a"))
158
+ node.name = "New Name"
159
+ node.destroy
160
+ root = PathTree::Test.find_by_path("root-1")
161
+ expect(root.children.map(&:path)).to eq [
162
+ delim_join("root-1", "parent-b"),
163
+ delim_join("root-1", "parent-c"),
164
+ delim_join("root-1", "child-a1"),
165
+ delim_join("root-1", "child-a2") ]
166
+ expect(root.children[2].children.map(&:path)).to eq [
167
+ delim_join("root-1", "child-a1", "grandchild-a1-1") ]
168
+ end
169
+ end
170
+ end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'active_record'
3
2
  require 'sqlite3'
4
3
 
@@ -14,3 +13,25 @@ end
14
13
  ActiveRecord::Base.establish_connection("adapter" => "sqlite3", "database" => ":memory:")
15
14
 
16
15
  require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'path_tree'))
16
+
17
+ module PathTree
18
+ class Test < ActiveRecord::Base
19
+ self.table_name = :test_path_trees
20
+
21
+ def self.create_tables
22
+ connection.create_table(table_name) do |t|
23
+ t.string :name
24
+ t.string :node_path
25
+ t.string :path
26
+ t.string :parent_path
27
+ end unless table_exists?
28
+ end
29
+
30
+ def self.drop_tables
31
+ connection.drop_table(table_name)
32
+ end
33
+
34
+ # lib/path_tree must be loaded before this
35
+ include PathTree
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: path_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.12
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -9,36 +9,42 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-31 00:00:00.000000000 Z
12
+ date: 2015-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "~>"
18
+ - - ">="
19
19
  - !ruby/object:Gem::Version
20
20
  version: 3.2.0
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: '4.3'
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - "~>"
28
+ - - ">="
26
29
  - !ruby/object:Gem::Version
27
30
  version: 3.2.0
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.3'
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: rspec
30
36
  requirement: !ruby/object:Gem::Requirement
31
37
  requirements:
32
38
  - - "~>"
33
39
  - !ruby/object:Gem::Version
34
- version: 2.8.0
40
+ version: '2.99'
35
41
  type: :development
36
42
  prerelease: false
37
43
  version_requirements: !ruby/object:Gem::Requirement
38
44
  requirements:
39
45
  - - "~>"
40
46
  - !ruby/object:Gem::Version
41
- version: 2.8.0
47
+ version: '2.99'
42
48
  - !ruby/object:Gem::Dependency
43
49
  name: sqlite3
44
50
  requirement: !ruby/object:Gem::Requirement
@@ -81,6 +87,20 @@ dependencies:
81
87
  - - "~>"
82
88
  - !ruby/object:Gem::Version
83
89
  version: '10.0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: appraisal
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2.0'
84
104
  description: Module that defines a tree data structure based on a path.
85
105
  email:
86
106
  - mdobrota@tribpub.com
@@ -89,16 +109,21 @@ extensions: []
89
109
  extra_rdoc_files: []
90
110
  files:
91
111
  - ".gitignore"
112
+ - Appraisals
92
113
  - Gemfile
93
114
  - License.txt
94
115
  - README.rdoc
95
116
  - Rakefile
117
+ - gemfiles/rails_3.2.gemfile
118
+ - gemfiles/rails_4.0.gemfile
119
+ - gemfiles/rails_4.1.gemfile
120
+ - gemfiles/rails_4.2.gemfile
96
121
  - lib/path_tree.rb
122
+ - lib/path_tree/patterns.rb
97
123
  - lib/path_tree/version.rb
98
- - lib/ruby_18_patterns.rb
99
- - lib/ruby_19_patterns.rb
100
124
  - path_tree.gemspec
101
125
  - spec/path_tree_spec.rb
126
+ - spec/shared/path_tree_model_examples.rb
102
127
  - spec/spec_helper.rb
103
128
  homepage: ''
104
129
  licenses: []
@@ -125,4 +150,5 @@ specification_version: 4
125
150
  summary: Helper module for constructing tree data structures
126
151
  test_files:
127
152
  - spec/path_tree_spec.rb
153
+ - spec/shared/path_tree_model_examples.rb
128
154
  - spec/spec_helper.rb
@@ -1,24 +0,0 @@
1
- module PathTree
2
- module Patterns
3
- UPPER_A_PATTERN = /\xC3[\x80-\x85]/.freeze
4
- LOWER_A_PATTERN = /\xC3[\xA0-\xA5]/.freeze
5
- UPPER_E_PATTERN = /\xC3[\x88-\x8B]/.freeze
6
- LOWER_E_PATTERN = /\xC3[\xA8-\xAB]/.freeze
7
- UPPER_I_PATTERN = /\xC3[\x8C-\x8F]/.freeze
8
- LOWER_I_PATTERN = /\xC3[\xAC-\xAF]/.freeze
9
- UPPER_O_PATTERN = /\xC3[\x92-\x96\x98]/.freeze
10
- LOWER_O_PATTERN = /\xC3[\xB2-\xB6\xB8]/.freeze
11
- UPPER_U_PATTERN = /\xC3[\x99-\x9C]/.freeze
12
- LOWER_U_PATTERN = /\xC3[\xB9-\xBC]/.freeze
13
- UPPER_Y_PATTERN = /\xC3\x9D/.freeze
14
- LOWER_Y_PATTERN = /\xC3[\xBD\xBF]/.freeze
15
- UPPER_C_PATTERN = /\xC3\x87/.freeze
16
- LOWER_C_PATTERN = /\xC3\xA7/.freeze
17
- UPPER_N_PATTERN = /\xC3\x91/.freeze
18
- LOWER_N_PATTERN = /\xC3\xB1/.freeze
19
- UPPER_D_PATTERN = /\xC3\x90/.freeze
20
- UPPER_AE_PATTERN = /\xC3\x86/.freeze
21
- LOWER_AE_PATTERN = /\xC3\xA6/.freeze
22
- SS_PATTERN = /\xC3\x9F/.freeze
23
- end
24
- end