chronic_tree 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ - 2.1.1
5
+ - 2.0.0
6
+ env:
7
+ - CODECLIMATE_REPO_TOKEN=c2536dff5fa10e3c3cccd9b8dfe4b5caa73584f59c5e3ea56181cd6092dda7bc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chronic_tree.gemspec
4
+ gemspec
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,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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