text_based_nested_set 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +42 -0
- data/Rakefile +2 -0
- data/lib/text_based_nested_set/model.rb +224 -0
- data/lib/text_based_nested_set/text_based_nested_set.rb +32 -0
- data/lib/text_based_nested_set/version.rb +3 -0
- data/lib/text_based_nested_set.rb +7 -0
- data/spec/db/database.yml +7 -0
- data/spec/db/schema.rb +10 -0
- data/spec/factories/categories.rb +5 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/models.rb +3 -0
- data/spec/text_based_nested_set_spec.rb +203 -0
- data/text_based_nested_set.gemspec +29 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 00dbca035b8e477e826841cbbda16c6648fdace6
|
4
|
+
data.tar.gz: aaed56d81da79ef4c0358820efb709c492c1c75f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ba9f837ddb26838b7e825a7615127d5e27bf94464be68277425f2ff8183a8495bd5cbd50d499db349437bdf8522422b26264a021a7679a23af7647a370751b7
|
7
|
+
data.tar.gz: b6ef1f7ce94c5457940b727e9721146214f48986485ae1ddada2362fba01eb72b569deddf67104fdf46b6a63fbfd182f29be5ded576d41c5798f91db9fe46ca4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 beyondalbert
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# TextBasedNestedSet
|
2
|
+
|
3
|
+
this acts provides Text Based Nested Set functionality.
|
4
|
+
Text Based Nested Set is another way to implementation of an SQL Nested Set created by Trever Shick(http://threebit.net/tutorials/nestedset/varcharBasedNestedSet.html).
|
5
|
+
Compare to lft and rgt Nested Set, this implementation can updates and deletions without require all of the nodes to be updated.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'text_based_nested_set'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install text_based_nested_set
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
set up:
|
26
|
+
1. Add parent_id, path, position column to exsiting model
|
27
|
+
class AddTextBasedNestedSetToDemo < ActiveRecord::Migration
|
28
|
+
def change
|
29
|
+
add_column :demos, :parent_id, :integer, default: 0
|
30
|
+
add_column :demos, :path, :string, default: '/0/'
|
31
|
+
add_column :demos, position, :integer, default: 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
2. add acts_as_text_based_nested_set method to target model
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
1. Fork it ( https://github.com/[my-github-username]/text_based_nested_set/fork )
|
39
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
40
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
41
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
42
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
module BeyondAlbert
|
2
|
+
module Acts
|
3
|
+
module TextBasedNestedSet
|
4
|
+
module Model
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def convert_from_awesome_nested_set
|
12
|
+
root_nodes = where(parent_id: nil)
|
13
|
+
root_nodes.each do |r|
|
14
|
+
r.update(parent_id: 0, path: '/0/')
|
15
|
+
r.rebuild
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def move_to_root
|
21
|
+
in_tenacious_transaction do
|
22
|
+
# reset siblings' position
|
23
|
+
self.siblings.each do |s|
|
24
|
+
if s.position > self.position
|
25
|
+
s.position -= 1
|
26
|
+
s.save!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
descendants = self.descendants
|
31
|
+
|
32
|
+
self.update(parent_id: 0, path: '/0/', position: 0)
|
33
|
+
|
34
|
+
# reset descendants' path
|
35
|
+
descendants.each do |d|
|
36
|
+
d.path = d.parent.path + d.parent_id.to_s + '/'
|
37
|
+
d.save!
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def move_to_left_of(target)
|
44
|
+
in_tenacious_transaction do
|
45
|
+
descendants = self.descendants
|
46
|
+
|
47
|
+
# set new siblings's position
|
48
|
+
target.self_and_siblings.each do |s|
|
49
|
+
if s.position >= target.position
|
50
|
+
s.position += 1
|
51
|
+
s.save!
|
52
|
+
end
|
53
|
+
end
|
54
|
+
target.reload
|
55
|
+
|
56
|
+
# set old siblings's position
|
57
|
+
self.siblings.each do |s|
|
58
|
+
if s.position > self.position
|
59
|
+
s.position -= 1
|
60
|
+
s.save!
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# set self attributes
|
65
|
+
self.update(path: target.path, parent_id: target.parent_id, position: target.position - 1)
|
66
|
+
|
67
|
+
# set descendants' path
|
68
|
+
descendants.each do |d|
|
69
|
+
d.path = d.parent.path + d.parent_id.to_s + '/'
|
70
|
+
d.save!
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def move_to_right_of(target)
|
76
|
+
in_tenacious_transaction do
|
77
|
+
descendants = self.descendants
|
78
|
+
|
79
|
+
# set new siblings' position
|
80
|
+
target.self_and_siblings.each do |s|
|
81
|
+
if s.position > target.position
|
82
|
+
s.position += 1
|
83
|
+
s.save!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# set old siblings' position
|
88
|
+
self.siblings.each do |s|
|
89
|
+
if s.position > self.position
|
90
|
+
s.position -= 1
|
91
|
+
s.save!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# set self attributes
|
96
|
+
self.update(path: target.path, parent_id: target.parent_id, position: target.position + 1)
|
97
|
+
|
98
|
+
# set descendants' path
|
99
|
+
descendants.each do |d|
|
100
|
+
d.path = d.parent.path + d.parent_id.to_s + '/'
|
101
|
+
d.save!
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def move_to_child_of(target)
|
107
|
+
in_tenacious_transaction do
|
108
|
+
descendants = self.descendants
|
109
|
+
|
110
|
+
# set old siblings' position
|
111
|
+
self.siblings.each do |s|
|
112
|
+
if s.position > self.position
|
113
|
+
s.position -= 1
|
114
|
+
s.save!
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# set self attributes
|
119
|
+
self.update(path: target.path + target.id.to_s + '/', parent_id: target.id, position: target.children.size)
|
120
|
+
|
121
|
+
# set descendants' path
|
122
|
+
descendants.each do |d|
|
123
|
+
d.path = d.parent.path + d.parent_id.to_s + '/'
|
124
|
+
d.save!
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def parent
|
130
|
+
if self.root?
|
131
|
+
nil
|
132
|
+
else
|
133
|
+
current_class.find(self.parent_id)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def ancestors
|
138
|
+
parent_ids = self.path.split('/').select {|v| v != "" && v != "0"}
|
139
|
+
current_class.where(id: parent_ids)
|
140
|
+
end
|
141
|
+
|
142
|
+
def children
|
143
|
+
children_path = self.path + self.id.to_s + '/'
|
144
|
+
current_class.where(path: children_path).order('position')
|
145
|
+
end
|
146
|
+
|
147
|
+
def descendants
|
148
|
+
descendants_path = self.path + self.id.to_s + '/%'
|
149
|
+
current_class.where("path LIKE ?", descendants_path)
|
150
|
+
end
|
151
|
+
|
152
|
+
def right_sibling
|
153
|
+
right_siblings = current_class.where(path: self.path, position: self.position + 1)
|
154
|
+
if right_siblings.empty?
|
155
|
+
nil
|
156
|
+
else
|
157
|
+
right_siblings.first
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def left_sibling
|
162
|
+
left_siblings = current_class.where(path: self.path, position: self.position - 1 )
|
163
|
+
if left_siblings.empty?
|
164
|
+
nil
|
165
|
+
else
|
166
|
+
left_siblings.first
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def siblings
|
171
|
+
current_class.where(path: self.path).select {|o| o.id != self.id}
|
172
|
+
end
|
173
|
+
|
174
|
+
def self_and_siblings
|
175
|
+
current_class.where(path: self.path)
|
176
|
+
end
|
177
|
+
|
178
|
+
def root?
|
179
|
+
self.parent_id == 0
|
180
|
+
end
|
181
|
+
|
182
|
+
def rebuild
|
183
|
+
in_tenacious_transaction do
|
184
|
+
children = current_class.where(parent_id: self.id)
|
185
|
+
if self.parent_id == 0
|
186
|
+
self.update!(path: '/0/')
|
187
|
+
end
|
188
|
+
unless children.empty?
|
189
|
+
children.each.with_index do |c, index|
|
190
|
+
c.update(path: self.path + self.id.to_s + '/', position: index)
|
191
|
+
c.rebuild
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
def in_tenacious_transaction(&block)
|
200
|
+
retry_count = 0
|
201
|
+
begin
|
202
|
+
transaction(&block)
|
203
|
+
rescue ActiveRecord::StatementInvalid => error
|
204
|
+
raise unless connection.open_transactions.zero?
|
205
|
+
raise unless error.message =~ /Deadlock found when trying to get lock|Lock wait timeout exceeded/
|
206
|
+
raise unless retry_count < 10
|
207
|
+
retry_count += 1
|
208
|
+
logger.info "Deadlock detected on retry #{retry_count}, restarting transaction"
|
209
|
+
sleep(rand(retry_count)*0.1) # Aloha protocol
|
210
|
+
retry
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def current_class
|
215
|
+
self.class.base_class
|
216
|
+
end
|
217
|
+
|
218
|
+
def destroy_descendants
|
219
|
+
self.descendants.destroy_all
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'text_based_nested_set/model'
|
2
|
+
|
3
|
+
module BeyondAlbert #:nodoc:
|
4
|
+
module Acts #:nodoc:
|
5
|
+
module TextBasedNestedSet #:nodoc:
|
6
|
+
|
7
|
+
# this acts provides Text Based Nested Set functionality.
|
8
|
+
# Text Based Nested Set is another way to implementation of an SQL Nested Set created by Trever Shick.
|
9
|
+
# Compare to lft and rgt Nested Set, this implementation can updates and deletions without require all of the nodes to be updated.
|
10
|
+
#
|
11
|
+
# set up:
|
12
|
+
# 1. Add parent_id, path, position column to exsiting model
|
13
|
+
# class AddTextBasedNestedSetToDemo < ActiveRecord::Migration
|
14
|
+
# def change
|
15
|
+
# add_column :demos, :parent_id, :integer, default: 0
|
16
|
+
# add_column :demos, :path, :string, default: '/0/'
|
17
|
+
# add_column :demos, position, :integer, default: 0
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# 2. add acts_as_text_based_nested_set method to target model
|
21
|
+
#
|
22
|
+
# See BeyondAlbert::Acts::TextBasedNestedSet::Model
|
23
|
+
# for a list ofclass methods and instance methods
|
24
|
+
# added to acts_as_text_based_nested_set models.
|
25
|
+
def acts_as_text_based_nested_set(options = {})
|
26
|
+
include Model
|
27
|
+
|
28
|
+
before_destroy :destroy_descendants
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/spec/db/schema.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'factory_girl'
|
5
|
+
require 'active_record'
|
6
|
+
require 'yaml'
|
7
|
+
require 'combustion/database'
|
8
|
+
|
9
|
+
ActiveRecord::Base.establish_connection(YAML.load_file(File.dirname(__FILE__) + '/db/database.yml')['mysql'])
|
10
|
+
Combustion::Database.create_database(YAML.load_file(File.dirname(__FILE__) + '/db/database.yml')['mysql'])
|
11
|
+
load File.dirname(__FILE__) + '/db/schema.rb'
|
12
|
+
|
13
|
+
require 'text_based_nested_set'
|
14
|
+
require 'support/models'
|
15
|
+
require 'factories/categories'
|
16
|
+
require 'database_cleaner'
|
17
|
+
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.include FactoryGirl::Syntax::Methods
|
20
|
+
|
21
|
+
config.before(:suite) do
|
22
|
+
DatabaseCleaner.strategy = :transaction
|
23
|
+
DatabaseCleaner.clean_with(:truncation)
|
24
|
+
end
|
25
|
+
|
26
|
+
config.before(:each) do
|
27
|
+
DatabaseCleaner.start
|
28
|
+
end
|
29
|
+
|
30
|
+
config.after(:each) do
|
31
|
+
DatabaseCleaner.clean
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'TextBasedNestedSet' do
|
4
|
+
before :each do
|
5
|
+
# test tree:
|
6
|
+
# root(1)
|
7
|
+
# |__child_1(2)
|
8
|
+
# | |__child_1_1(4)
|
9
|
+
# | | |__child_1_1_1(7)
|
10
|
+
# | |__child_1_2(5)
|
11
|
+
# | | |__child_1_2_1(8)
|
12
|
+
# | |__child_1_3(6)
|
13
|
+
# |__child_2(3)
|
14
|
+
@root = create(:category, id: 1, parent_id: 0, path: "/0/", position: 0)
|
15
|
+
@child_1 = create(:category, id: 2, name: "child_1", parent_id: 1, path: "/0/1/", position: 0)
|
16
|
+
@child_2 = create(:category, id: 3, name: "child_2", parent_id: 1, path: "/0/1/", position: 1)
|
17
|
+
@child_1_1 = create(:category, id: 4, name: "child_1_1", parent_id: 2, path: "/0/1/2/", position: 0)
|
18
|
+
@child_1_2 = create(:category, id: 5, name: "child_1_2", parent_id: 2, path: "/0/1/2/", position: 1)
|
19
|
+
@child_1_3 = create(:category, id: 6, name: "child_1_3", parent_id: 2, path: "/0/1/2/", position: 2)
|
20
|
+
@child_1_1_1 = create(:category, id: 7, name: "child_1_1_1", parent_id: 4, path: "/0/1/2/4/", position: 0)
|
21
|
+
@child_1_2_1 = create(:category, id: 8, name: "child_1_2_1", parent_id: 5, path: "/0/1/2/5/", position: 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'move_to_root' do
|
25
|
+
it 'should move to the root of the tree' do
|
26
|
+
@child_1_2.move_to_root
|
27
|
+
@child_1_2.reload
|
28
|
+
@child_1_1.reload
|
29
|
+
@child_1_3.reload
|
30
|
+
@child_1_2_1.reload
|
31
|
+
expect(@child_1_2.path).to eq("/0/")
|
32
|
+
expect(@child_1_2.parent_id).to eq(0)
|
33
|
+
expect(@child_1_2.position).to eq(0)
|
34
|
+
expect(@child_1_1.position).to eq(0)
|
35
|
+
expect(@child_1_3.position).to eq(1)
|
36
|
+
expect(@child_1_2_1.path).to eq("/0/5/")
|
37
|
+
expect(@child_1.descendants).to eq([@child_1_1, @child_1_3, @child_1_1_1])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'move_to_left_of' do
|
42
|
+
it 'should move to the left of target node' do
|
43
|
+
@child_1_2.move_to_left_of(@child_2)
|
44
|
+
|
45
|
+
@child_1_2.reload
|
46
|
+
@child_2.reload
|
47
|
+
@child_1_3.reload
|
48
|
+
@child_1_2_1.reload
|
49
|
+
expect(@child_1_2.path).to eq('/0/1/')
|
50
|
+
expect(@child_1_2.position).to eq(1)
|
51
|
+
expect(@child_2.position).to eq(2)
|
52
|
+
expect(@child_1_3.position).to eq(1)
|
53
|
+
expect(@child_1_2_1.path).to eq('/0/1/5/')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'move_to_right_of' do
|
58
|
+
it 'should move to the right of target node' do
|
59
|
+
@child_1_1.move_to_right_of(@child_1)
|
60
|
+
|
61
|
+
@child_1_1.reload
|
62
|
+
@child_2.reload
|
63
|
+
@child_1_2.reload
|
64
|
+
@child_1_3.reload
|
65
|
+
expect(@child_1_1.path).to eq('/0/1/')
|
66
|
+
expect(@child_1_1.position).to eq(1)
|
67
|
+
expect(@child_2.position).to eq(2)
|
68
|
+
expect(@child_1_2.position).to eq(0)
|
69
|
+
expect(@child_1_3.position).to eq(1)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'move_to_child_of' do
|
74
|
+
it 'should move to the child of target node' do
|
75
|
+
@child_1_2.move_to_child_of(@child_1_1)
|
76
|
+
|
77
|
+
@child_1_2.reload
|
78
|
+
@child_1_3.reload
|
79
|
+
@child_1_2_1.reload
|
80
|
+
expect(@child_1_2.path).to eq('/0/1/2/4/')
|
81
|
+
expect(@child_1_2.position).to eq(1)
|
82
|
+
expect(@child_1_3.position).to eq(1)
|
83
|
+
expect(@child_1_2_1.path).to eq('/0/1/2/4/5/')
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'parent' do
|
88
|
+
it "should return current node's parent node" do
|
89
|
+
expect(@child_1_1.parent.name).to eq("child_1")
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should return nil when current node is root node" do
|
93
|
+
expect(@root.parent).to eq(nil)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe 'ancestors' do
|
98
|
+
it "should return current node's ancestors node" do
|
99
|
+
expect(@child_1_1.ancestors).to eq([@root, @child_1])
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe 'children' do
|
104
|
+
it "should return current node's children node" do
|
105
|
+
expect(@child_1.children).to eq([@child_1_1, @child_1_2, @child_1_3])
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe 'descendants' do
|
110
|
+
it "should return current node's all descendants" do
|
111
|
+
expect(@child_1.descendants).to eq([@child_1_1, @child_1_2, @child_1_3, @child_1_1_1, @child_1_2_1])
|
112
|
+
expect(@root.descendants).to eq([@child_1, @child_2, @child_1_1, @child_1_2, @child_1_3, @child_1_1_1, @child_1_2_1])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'right_sibling' do
|
117
|
+
it "should return current node's right sibling" do
|
118
|
+
expect(@child_1_2.right_sibling).to eq(@child_1_3)
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should return nil when current node's right sibling is not existed" do
|
122
|
+
expect(@child_1_3.right_sibling).to eq(nil)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'left_sibling' do
|
127
|
+
it "should return current node's left sibling" do
|
128
|
+
expect(@child_1_2.left_sibling).to eq(@child_1_1)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should return nil when current node's left sibling is not existed" do
|
132
|
+
expect(@child_1_1.left_sibling).to eq(nil)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'root?' do
|
137
|
+
it "should return true if current node is root node" do
|
138
|
+
expect(@root.root?).to eq(true)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should return false if current node is not root node" do
|
142
|
+
expect(@child_1.root?).to eq(false)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe 'convert_from_awesome_nested_set' do
|
147
|
+
it "should convert awesome nested set to text based nested set" do
|
148
|
+
@root.update(parent_id: nil)
|
149
|
+
@root.reload
|
150
|
+
Category.convert_from_awesome_nested_set
|
151
|
+
expect(@root.path).to eq('/0/')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe 'rebuild' do
|
156
|
+
it "should rebuild current node's all descendants, set path and position again " do
|
157
|
+
@root.descendants.each do |d|
|
158
|
+
d.update(path: nil, position: nil)
|
159
|
+
end
|
160
|
+
@root.update(path: nil, position: nil)
|
161
|
+
@root.reload
|
162
|
+
@child_1.reload
|
163
|
+
@child_1_1.reload
|
164
|
+
@child_1_1_1.reload
|
165
|
+
@child_1_2.reload
|
166
|
+
@child_1_2_1.reload
|
167
|
+
@child_1_3.reload
|
168
|
+
@child_2.reload
|
169
|
+
|
170
|
+
@root.rebuild
|
171
|
+
|
172
|
+
@root.reload
|
173
|
+
@child_1.reload
|
174
|
+
@child_1_1.reload
|
175
|
+
@child_1_1_1.reload
|
176
|
+
@child_1_2.reload
|
177
|
+
@child_1_2_1.reload
|
178
|
+
@child_1_3.reload
|
179
|
+
@child_2.reload
|
180
|
+
|
181
|
+
expect(@child_1.path).to eq('/0/1/')
|
182
|
+
expect(@child_1.position).to eq(0)
|
183
|
+
expect(@child_1_1_1.path).to eq('/0/1/2/4/')
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe 'private destroy_descendants' do
|
188
|
+
it 'should destroy descendants when itself be destroyed' do
|
189
|
+
@child_1.destroy
|
190
|
+
expect(Category.find_by_id(4)).to eq(nil)
|
191
|
+
expect(Category.find_by_id(3).name).to eq('child_2')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe 'default value test' do
|
196
|
+
it 'should create a root node' do
|
197
|
+
root1 = create(:category, name: 'root1')
|
198
|
+
expect(root1.parent_id).to eq(0)
|
199
|
+
expect(root1.path).to eq('/0/')
|
200
|
+
expect(root1.position).to eq(0)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'text_based_nested_set/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "text_based_nested_set"
|
8
|
+
spec.version = TextBasedNestedSet::VERSION
|
9
|
+
spec.authors = ["beyondalbert"]
|
10
|
+
spec.email = ["beyondalbert@gmail.com"]
|
11
|
+
spec.summary = %q{Text based nested set.}
|
12
|
+
spec.description = %q{Text based nested set.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "factory_girl", "~> 4.0"
|
25
|
+
spec.add_development_dependency "mysql2", "~> 0.3.17"
|
26
|
+
spec.add_development_dependency 'combustion', '>= 0.5.2'
|
27
|
+
spec.add_development_dependency 'database_cleaner', '~> 1.4.0'
|
28
|
+
spec.add_dependency "activerecord", "~> 4.0"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: text_based_nested_set
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- beyondalbert
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: factory_girl
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mysql2
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.3.17
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.3.17
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: combustion
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.5.2
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.5.2
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: database_cleaner
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.4.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.4.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: activerecord
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.0'
|
125
|
+
description: Text based nested set.
|
126
|
+
email:
|
127
|
+
- beyondalbert@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rspec"
|
134
|
+
- Gemfile
|
135
|
+
- LICENSE.txt
|
136
|
+
- README.md
|
137
|
+
- Rakefile
|
138
|
+
- lib/text_based_nested_set.rb
|
139
|
+
- lib/text_based_nested_set/model.rb
|
140
|
+
- lib/text_based_nested_set/text_based_nested_set.rb
|
141
|
+
- lib/text_based_nested_set/version.rb
|
142
|
+
- spec/db/database.yml
|
143
|
+
- spec/db/schema.rb
|
144
|
+
- spec/factories/categories.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/support/models.rb
|
147
|
+
- spec/text_based_nested_set_spec.rb
|
148
|
+
- text_based_nested_set.gemspec
|
149
|
+
homepage: ''
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.2.2
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Text based nested set.
|
173
|
+
test_files:
|
174
|
+
- spec/db/database.yml
|
175
|
+
- spec/db/schema.rb
|
176
|
+
- spec/factories/categories.rb
|
177
|
+
- spec/spec_helper.rb
|
178
|
+
- spec/support/models.rb
|
179
|
+
- spec/text_based_nested_set_spec.rb
|