chronic_tree 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +87 -0
- data/LICENSE +21 -0
- data/README.md +278 -0
- data/Rakefile +6 -0
- data/chronic_tree.gemspec +30 -0
- data/lib/chronic_tree.rb +73 -0
- data/lib/chronic_tree/active_record/element.rb +26 -0
- data/lib/chronic_tree/active_record/relation.rb +34 -0
- data/lib/chronic_tree/command.rb +165 -0
- data/lib/chronic_tree/operation.rb +212 -0
- data/lib/chronic_tree/railtie.rb +9 -0
- data/lib/chronic_tree/travesal.rb +64 -0
- data/lib/chronic_tree/version.rb +3 -0
- data/lib/generators/chronic_tree/install_generator.rb +15 -0
- data/lib/generators/chronic_tree/templates/create_chronic_tree_elements.rb +22 -0
- data/spec/chronic_tree_spec.rb +223 -0
- data/spec/spec_helper.rb +438 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b52c7d90e61d2f6f2c147ed874baa5c1d67f312c
|
4
|
+
data.tar.gz: b29cf0a5b4c9299a098902e61a3e0dba30f7f38a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 79595c974dc69193490703f08de4f2f9c099ad755d71839955c3cb734f4ea35c463649f74cf97dd07b5fb940a4a691445ac83e012a62b858af948402a74a70d1
|
7
|
+
data.tar.gz: e4b7b047332da17ba9b988ddbad31aac9b9671b55e60a56741269105f49247be197943e96befae32780022d8a40e7a6c5a4ef95bcb5fc5b54c48ecbf62398899
|
data/.gitignore
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
*.rbc
|
2
|
+
.rspec
|
3
|
+
/tmp
|
4
|
+
/coverage/
|
5
|
+
/spec/tmp
|
6
|
+
**.orig
|
7
|
+
/pkg/*
|
8
|
+
|
9
|
+
## Environment normalisation:
|
10
|
+
/.bundle
|
11
|
+
|
12
|
+
# these should all be checked in to normalise the environment:
|
13
|
+
# Gemfile.lock, .ruby-version, .ruby-gemset
|
14
|
+
|
15
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
16
|
+
.rvmrc
|
17
|
+
|
18
|
+
/*.sublime-project
|
19
|
+
/*.sublime-workspace
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.1.1@chronic_tree
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
chronic_tree (1.0.0)
|
5
|
+
activerecord (>= 4.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.1.1)
|
11
|
+
activesupport (= 4.1.1)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.1.1)
|
14
|
+
activemodel (= 4.1.1)
|
15
|
+
activesupport (= 4.1.1)
|
16
|
+
arel (~> 5.0.0)
|
17
|
+
activesupport (4.1.1)
|
18
|
+
i18n (~> 0.6, >= 0.6.9)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.1)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
arel (5.0.1.20140414130214)
|
24
|
+
builder (3.2.2)
|
25
|
+
codeclimate-test-reporter (0.3.0)
|
26
|
+
simplecov (>= 0.7.1, < 1.0.0)
|
27
|
+
coderay (1.1.0)
|
28
|
+
coveralls (0.7.0)
|
29
|
+
multi_json (~> 1.3)
|
30
|
+
rest-client
|
31
|
+
simplecov (>= 0.7)
|
32
|
+
term-ansicolor
|
33
|
+
thor
|
34
|
+
diff-lcs (1.2.5)
|
35
|
+
docile (1.1.5)
|
36
|
+
i18n (0.6.9)
|
37
|
+
json (1.8.1)
|
38
|
+
method_source (0.8.2)
|
39
|
+
mime-types (2.3)
|
40
|
+
minitest (5.3.4)
|
41
|
+
multi_json (1.10.1)
|
42
|
+
pry (0.10.0)
|
43
|
+
coderay (~> 1.1.0)
|
44
|
+
method_source (~> 0.8.1)
|
45
|
+
slop (~> 3.4)
|
46
|
+
rake (10.3.2)
|
47
|
+
rest-client (1.6.7)
|
48
|
+
mime-types (>= 1.16)
|
49
|
+
rspec (3.0.0)
|
50
|
+
rspec-core (~> 3.0.0)
|
51
|
+
rspec-expectations (~> 3.0.0)
|
52
|
+
rspec-mocks (~> 3.0.0)
|
53
|
+
rspec-core (3.0.1)
|
54
|
+
rspec-support (~> 3.0.0)
|
55
|
+
rspec-expectations (3.0.1)
|
56
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
57
|
+
rspec-support (~> 3.0.0)
|
58
|
+
rspec-mocks (3.0.1)
|
59
|
+
rspec-support (~> 3.0.0)
|
60
|
+
rspec-support (3.0.0)
|
61
|
+
simplecov (0.8.2)
|
62
|
+
docile (~> 1.1.0)
|
63
|
+
multi_json
|
64
|
+
simplecov-html (~> 0.8.0)
|
65
|
+
simplecov-html (0.8.0)
|
66
|
+
slop (3.5.0)
|
67
|
+
sqlite3 (1.3.9)
|
68
|
+
term-ansicolor (1.3.0)
|
69
|
+
tins (~> 1.0)
|
70
|
+
thor (0.19.1)
|
71
|
+
thread_safe (0.3.4)
|
72
|
+
tins (1.3.0)
|
73
|
+
tzinfo (1.2.1)
|
74
|
+
thread_safe (~> 0.1)
|
75
|
+
|
76
|
+
PLATFORMS
|
77
|
+
ruby
|
78
|
+
|
79
|
+
DEPENDENCIES
|
80
|
+
bundler (~> 1.3)
|
81
|
+
chronic_tree!
|
82
|
+
codeclimate-test-reporter
|
83
|
+
coveralls
|
84
|
+
pry
|
85
|
+
rake
|
86
|
+
rspec (~> 3.0.0)
|
87
|
+
sqlite3
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Xiang Li
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
# Chronic Tree
|
2
|
+
|
3
|
+
[![GitHub version](https://badge.fury.io/gh/bigxiang%2Fchronic_tree.svg)](http://badge.fury.io/gh/bigxiang%2Fchronic_tree)
|
4
|
+
[![Build Status](https://travis-ci.org/bigxiang/chronic_tree.svg?branch=master)](https://travis-ci.org/bigxiang/chronic_tree)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/bigxiang/chronic_tree.png)](https://codeclimate.com/github/bigxiang/chronic_tree)
|
6
|
+
[![Coverage Status](https://coveralls.io/repos/bigxiang/chronic_tree/badge.png)](https://coveralls.io/r/bigxiang/chronic_tree)
|
7
|
+
|
8
|
+
Build a tree with historical versions and multiple scopes by one model class.
|
9
|
+
|
10
|
+
There are some gems for tree structures, for example: [acts_as_tree](https://github.com/amerine/acts_as_tree). They are simple and easy to use.
|
11
|
+
|
12
|
+
But in some applications, we are facing more complicated cases. We probably need to build multiple trees using one model and track their histories. For example, organization tree in an ERP or CRM application. Multiple organization trees in these apps usually are created and tracked. So we can’t solve this problem only by using a ‘parent_id’. It’s why this gem has been created.
|
13
|
+
|
14
|
+
This gem is compatible with Ruby 2.0+ and Rails 4.0+.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
gem 'chronic_tree'
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Execute the installation script:
|
27
|
+
|
28
|
+
$ rails g chronic_tree:install
|
29
|
+
|
30
|
+
Execute the rake command:
|
31
|
+
|
32
|
+
$ rake db:migrate
|
33
|
+
|
34
|
+
## Quick Start
|
35
|
+
|
36
|
+
Add a tree to your model:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class Org < ActiveRecord::Base
|
40
|
+
chronic_tree
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Before travelling in the tree, using `as_tree` to initialize the tree arguments is highly recommended however the methods below would call `as_tree` implicitly if the arguments aren't set. It's somewhat dup, I am trying to simplify it, but it's the safest way to get the correct version of the tree now.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# init a tree with current timestamp and default scope
|
48
|
+
@org.as_tree
|
49
|
+
|
50
|
+
# init a tree with the timestamp at 10 minutes ago and default scope
|
51
|
+
@org.as_tree(10.minutes.ago)
|
52
|
+
|
53
|
+
# init a tree with the timestamp at 10 minutes ago and special scope
|
54
|
+
@org.as_tree(10.minutes.ago, 'special')
|
55
|
+
```
|
56
|
+
|
57
|
+
####Add root
|
58
|
+
|
59
|
+
This method creates a root node for an empty tree.
|
60
|
+
```ruby
|
61
|
+
@org.add_as_root
|
62
|
+
```
|
63
|
+
|
64
|
+
####Add children
|
65
|
+
|
66
|
+
This method adds a child object under itself.
|
67
|
+
|
68
|
+
Create this structure:
|
69
|
+
```ruby
|
70
|
+
# root
|
71
|
+
# -- child_org
|
72
|
+
# -- another_child_org
|
73
|
+
@org.add_child(@child_org)
|
74
|
+
@child_org.add_child(@another_child_org)
|
75
|
+
|
76
|
+
@org.as_tree.descendants # => [[@child_org], [@another_child_org]]
|
77
|
+
```
|
78
|
+
|
79
|
+
####Change parent
|
80
|
+
|
81
|
+
You can change the parent node with another node existing in the tree.
|
82
|
+
```ruby
|
83
|
+
@org.as_tree.parent # => @parent
|
84
|
+
@org.change_parent(@another_parent)
|
85
|
+
@org.as_tree.parent # => @another_parent
|
86
|
+
```
|
87
|
+
|
88
|
+
####Remove descendants
|
89
|
+
|
90
|
+
It destroys all descendant nodes.
|
91
|
+
```ruby
|
92
|
+
@org.remove_descendants
|
93
|
+
```
|
94
|
+
|
95
|
+
####Remove self
|
96
|
+
|
97
|
+
It destroys self node and its descendant nodes.
|
98
|
+
```ruby
|
99
|
+
@org.remove_self
|
100
|
+
```
|
101
|
+
|
102
|
+
####Replace self by another object
|
103
|
+
|
104
|
+
It’s replaced by another object. This behavior doesn't affect other tree nodes.
|
105
|
+
```ruby
|
106
|
+
# before replacing
|
107
|
+
# org
|
108
|
+
# -- child
|
109
|
+
|
110
|
+
@child.as_tree.parent # => @org
|
111
|
+
@org.replace_by(@another_org)
|
112
|
+
@child.as_tree.parent # => @another_org
|
113
|
+
|
114
|
+
# after replacing
|
115
|
+
# another_org
|
116
|
+
# -- child
|
117
|
+
```
|
118
|
+
|
119
|
+
####Get children
|
120
|
+
|
121
|
+
Get all direct child objects of itself.
|
122
|
+
```ruby
|
123
|
+
@org.as_tree.children.each do |child_org|
|
124
|
+
# actions...
|
125
|
+
# each child org has called as_tree automatically
|
126
|
+
# so you can use tree traversal directly
|
127
|
+
|
128
|
+
...
|
129
|
+
child_org.children
|
130
|
+
child_org.parent
|
131
|
+
child_org.root
|
132
|
+
...
|
133
|
+
end
|
134
|
+
```
|
135
|
+
|
136
|
+
####Get parent
|
137
|
+
|
138
|
+
Get parent object, return nil if the parent doesn't exist.
|
139
|
+
```ruby
|
140
|
+
@org.as_tree.parent # => @parent_org
|
141
|
+
@org.as_tree.parent # => nil if parent doesn't exist
|
142
|
+
@org.as_tree.parent? # => alias of parent method
|
143
|
+
```
|
144
|
+
|
145
|
+
####Get root
|
146
|
+
|
147
|
+
Get root object, return nil if the parent doesn't exist.
|
148
|
+
```ruby
|
149
|
+
@org.as_tree.root # => @root_org
|
150
|
+
@org.as_tree.root # => nil if the tree is empty
|
151
|
+
```
|
152
|
+
|
153
|
+
####Get ancestors
|
154
|
+
|
155
|
+
It returns a list of all parent objects of itself and order by distance to it.
|
156
|
+
```ruby
|
157
|
+
@org.as_tree.ancestors # => [<parent_org>, <root_org>]
|
158
|
+
```
|
159
|
+
|
160
|
+
####Get descendants
|
161
|
+
|
162
|
+
It returns a list of levels. Each level is an array too and contains all objects of this level.
|
163
|
+
```ruby
|
164
|
+
@org.as_tree.descendants # => [[<first_level_children>], [<second_level_children>], ...]
|
165
|
+
```
|
166
|
+
|
167
|
+
It returns all descendants as a flat array at once.
|
168
|
+
```ruby
|
169
|
+
@org.as_tree.flat_descendants # => [<all_descendants>]
|
170
|
+
```
|
171
|
+
|
172
|
+
####Utils
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
@org.tree_empty? # => Return true if the tree is empty.
|
176
|
+
@org.tree_empty?(10.minutes.ago, 'special')
|
177
|
+
@org.existed_in_tree? # => Return true if @org exists in the tree.
|
178
|
+
@org.existed_in_tree?(10.minutes.ago, 'special')
|
179
|
+
```
|
180
|
+
These two methods wouldn't call `as_tree`.
|
181
|
+
|
182
|
+
## Advanced
|
183
|
+
|
184
|
+
The most important part of this gem is playing with historical versions and multiple scopes in one tree.
|
185
|
+
|
186
|
+
####Play with history
|
187
|
+
|
188
|
+
If you had a tree 1 day ago:
|
189
|
+
|
190
|
+
```
|
191
|
+
root
|
192
|
+
-- child1
|
193
|
+
-- child1.1
|
194
|
+
-- child2
|
195
|
+
-- child2.1
|
196
|
+
```
|
197
|
+
|
198
|
+
And change to this version now:
|
199
|
+
|
200
|
+
```
|
201
|
+
root
|
202
|
+
-- child1
|
203
|
+
-- child2
|
204
|
+
-- child2.1
|
205
|
+
-- child2.1.1
|
206
|
+
```
|
207
|
+
|
208
|
+
You can easily get the correct version of the tree at any time.
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
root.as_tree.children # => [child1, child2]
|
212
|
+
child1.as_tree.children # => []
|
213
|
+
child2.as_tree.flat_descendants # =? [child2.1, child2.1.1]
|
214
|
+
|
215
|
+
root.as_tree(1.days.ago).children # => [child1, child2]
|
216
|
+
child1.as_tree(1.days.ago).children # => [child1.1]
|
217
|
+
child2.as_tree(1.days.ago).flat_descendants # =? [child2.1]
|
218
|
+
```
|
219
|
+
|
220
|
+
####Play with multiple scopes
|
221
|
+
|
222
|
+
If you have two scopes in one tree at the same time:
|
223
|
+
|
224
|
+
```
|
225
|
+
default scope:
|
226
|
+
root
|
227
|
+
-- child1
|
228
|
+
-- child2
|
229
|
+
-- child2.1
|
230
|
+
|
231
|
+
special scope:
|
232
|
+
another_root
|
233
|
+
-- child2
|
234
|
+
-- child2.1
|
235
|
+
-- child2.2
|
236
|
+
-- child3
|
237
|
+
```
|
238
|
+
|
239
|
+
You can switch between two scopes:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
child2.as_tree.parent # => root
|
243
|
+
child2.as_tree.children # => [child2.1]
|
244
|
+
|
245
|
+
child2.as_tree('special').parent # => another_root
|
246
|
+
child2.as_tree('special').children # => [child2.1, child2.2]
|
247
|
+
```
|
248
|
+
|
249
|
+
|
250
|
+
####Using low-level relations
|
251
|
+
|
252
|
+
They usually don't need to be used. All tree traversal methods are based on these relations, they just return ActiveRecord::Relation for chronic_tree_elements table, so you can chain the method as you want.
|
253
|
+
|
254
|
+
You must pass timestamp and scope name explicitly in these relations.
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# Return children elements
|
258
|
+
root.children_relation(Time.now, 'default')
|
259
|
+
|
260
|
+
# Return parent elements
|
261
|
+
root.parent_relation(Time.now, 'default')
|
262
|
+
|
263
|
+
# Return descendant elements
|
264
|
+
root.descendants_relation(Time.now, 'default')
|
265
|
+
|
266
|
+
# Return ancestor elements
|
267
|
+
root.ancestors_relation(Time.now, 'default')
|
268
|
+
```
|
269
|
+
|
270
|
+
## Contributing
|
271
|
+
|
272
|
+
Any feedback or improvement is appreciated!
|
273
|
+
|
274
|
+
1. Fork it
|
275
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
276
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
277
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
278
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chronic_tree/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "chronic_tree"
|
8
|
+
spec.version = ChronicTree::VERSION
|
9
|
+
spec.authors = ["bigxiang"]
|
10
|
+
spec.email = ["bigxiang@gmail.com"]
|
11
|
+
spec.description = %q{Build a tree with multiple versions and scopes by one model class.}
|
12
|
+
spec.summary = %q{Build a tree with multiple versions and scopes by one model class.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0.0"
|
24
|
+
spec.add_development_dependency "sqlite3"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "codeclimate-test-reporter"
|
27
|
+
spec.add_development_dependency "coveralls"
|
28
|
+
|
29
|
+
spec.add_dependency "activerecord", ">= 4.0.0"
|
30
|
+
end
|