ancestry 2.0.0 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +116 -131
- data/ancestry.gemspec +32 -13
- data/lib/ancestry/class_methods.rb +53 -34
- data/lib/ancestry/has_ancestry.rb +38 -12
- data/lib/ancestry/instance_methods.rb +103 -18
- data/lib/ancestry.rb +4 -4
- metadata +95 -27
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 005c1204eb8018577673e708da755c1e8b0ae388
|
4
|
+
data.tar.gz: 7f55187582a5d94339dd86e972ec9070c9cf1fed
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 568208af2c3eb95eca4b71ceed135c72743370ff60e7d1209f5b1a5ef7557ef379680ccdf5a83dc5b80323a403c422d3b14501b41fc0d01557a53d4d04170a51
|
7
|
+
data.tar.gz: 1b8c1b91c87573b7d9e611df92bb185bbb9712964a3dc0f64ccda3bde6c17fe11dd358212bdbb2f76645915723a66d179570ee4eb1ef630f96558f1f1ea043d9
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
{<img src="https://travis-ci.org/stefankroes/ancestry.svg?branch=master" alt="Build Status" />}[https://travis-ci.org/stefankroes/ancestry]
|
2
|
+
{<img src="https://coveralls.io/repos/stefankroes/ancestry/badge.svg" alt="Coverage Status" />}[https://coveralls.io/r/stefankroes/ancestry]
|
3
|
+
{<img src="https://badges.gitter.im/Join Chat.svg" alt="Gitter" />}[https://gitter.im/stefankroes/ancestry?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge]
|
4
|
+
{<img src="https://hakiri.io/github/stefankroes/ancestry/master.svg" alt="Security" />}[https://hakiri.io/github/stefankroes/ancestry/master]
|
5
|
+
|
1
6
|
= Ancestry
|
2
7
|
|
3
8
|
Ancestry is a gem/plugin that allows the records of a Ruby on Rails ActiveRecord model to be organised as a tree structure (or hierarchy). It uses a single, intuitively formatted database column, using a variation on the materialised path pattern. It exposes all the standard tree structure relations (ancestors, parent, root, children, siblings, descendants) and all of them can be fetched in a single SQL query. Additional features are STI support, scopes, depth caching, depth constraints, easy migration from older plugins/gems, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.
|
@@ -6,20 +11,51 @@ Ancestry is a gem/plugin that allows the records of a Ruby on Rails ActiveRecord
|
|
6
11
|
|
7
12
|
To apply Ancestry to any ActiveRecord model, follow these simple steps:
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
== Install
|
15
|
+
=== Rails 2
|
16
|
+
- See 1-3-stable branch
|
17
|
+
=== Rails 3 and 4
|
18
|
+
- Add to Gemfile:
|
19
|
+
# Gemfile
|
20
|
+
|
21
|
+
gem 'ancestry'
|
22
|
+
- Install required gems:
|
23
|
+
$ bundle install
|
24
|
+
|
25
|
+
== Add ancestry column to your table
|
26
|
+
- Create migration:
|
27
|
+
$ rails g migration add_ancestry_to_[table] ancestry:string
|
28
|
+
- Add index to migration:
|
29
|
+
# db/migrate/[date]_add_ancestry_to_[table].rb
|
30
|
+
|
31
|
+
class AddAncestryTo[Table] < ActiveRecord::Migration
|
32
|
+
# Rails 4 Syntax
|
33
|
+
def change
|
34
|
+
add_column [table], :ancestry, :string
|
35
|
+
add_index [table], :ancestry
|
36
|
+
end
|
37
|
+
|
38
|
+
# Rails 3 Syntax
|
39
|
+
def up
|
40
|
+
add_column [table], :ancestry, :string
|
41
|
+
add_index [table], :ancestry
|
42
|
+
end
|
43
|
+
|
44
|
+
def down
|
45
|
+
remove_column [table], :ancestry
|
46
|
+
remove_index [table], :ancestry
|
47
|
+
end
|
48
|
+
|
49
|
+
- Migrate your database:
|
50
|
+
$ rake db:migrate
|
51
|
+
|
52
|
+
== Add ancestry to your model
|
53
|
+
- Add to app/models/[model].rb:
|
54
|
+
# app/models/[model.rb]
|
55
|
+
|
56
|
+
class [Model] < ActiveRecord::Base
|
57
|
+
has_ancestry
|
58
|
+
end
|
23
59
|
|
24
60
|
Your model is now a tree!
|
25
61
|
|
@@ -53,8 +89,8 @@ To navigate an Ancestry model, use the following methods on any instance / recor
|
|
53
89
|
children Scopes the model on children of the record
|
54
90
|
child_ids Returns a list of child ids
|
55
91
|
has_children? Returns true if the record has any children, false otherwise
|
56
|
-
is_childless? Returns true is the record has no
|
57
|
-
siblings Scopes the model on siblings of the record, the record itself is included
|
92
|
+
is_childless? Returns true is the record has no children, false otherwise
|
93
|
+
siblings Scopes the model on siblings of the record, the record itself is included*
|
58
94
|
sibling_ids Returns a list of sibling ids
|
59
95
|
has_siblings? Returns true if the record's parent has more than one child
|
60
96
|
is_only_child? Returns true if the record is the only child of its parent
|
@@ -64,7 +100,9 @@ To navigate an Ancestry model, use the following methods on any instance / recor
|
|
64
100
|
subtree_ids Returns a list of all ids in the record's subtree
|
65
101
|
depth Return the depth of the node, root nodes are at depth 0
|
66
102
|
|
67
|
-
|
103
|
+
* If the record is a root, other root records are considered siblings
|
104
|
+
|
105
|
+
= Options for has_ancestry
|
68
106
|
|
69
107
|
The has_ancestry methods supports the following options:
|
70
108
|
|
@@ -73,7 +111,8 @@ The has_ancestry methods supports the following options:
|
|
73
111
|
:destroy All children are destroyed as well (default)
|
74
112
|
:rootify The children of the destroyed node become root nodes
|
75
113
|
:restrict An AncestryException is raised if any children exist
|
76
|
-
:adopt The orphan subtree is added to the parent of the deleted node.
|
114
|
+
:adopt The orphan subtree is added to the parent of the deleted node.
|
115
|
+
If the deleted node is Root, then rootify the orphan subtree.
|
77
116
|
:cache_depth Cache the depth of each node in the 'ancestry_depth' column (default: false)
|
78
117
|
If you turn depth_caching on for an existing model:
|
79
118
|
- Migrate: add_column [table], :ancestry_depth, :integer, :default => 0
|
@@ -81,6 +120,8 @@ The has_ancestry methods supports the following options:
|
|
81
120
|
:depth_cache_column Pass in a symbol to store depth cache in a different column
|
82
121
|
:primary_key_format Supply a regular expression that matches the format of your primary key.
|
83
122
|
By default, primary keys only match integers ([0-9]+).
|
123
|
+
:touch Instruct Ancestry to touch the ancestors of a node when it changes, to
|
124
|
+
invalidate nested key-based caches. (default: false)
|
84
125
|
|
85
126
|
= (Named) Scopes
|
86
127
|
|
@@ -115,7 +156,7 @@ When depth caching is enabled (see has_ancestry options), five more named scopes
|
|
115
156
|
at_depth(depth) Return nodes that are at depth (node.depth == depth)
|
116
157
|
from_depth(depth) Return nodes starting from a certain depth (node.depth >= depth)
|
117
158
|
after_depth(depth) Return nodes that are deeper than depth (node.depth > depth)
|
118
|
-
|
159
|
+
|
119
160
|
The depth scopes are also available through calls to descendants, descendant_ids, subtree, subtree_ids, path and ancestors. In this case, depth values are interpreted relatively. Some examples:
|
120
161
|
|
121
162
|
node.subtree(:to_depth => 2) Subtree of node, to a depth of node.depth + 2 (self, children and grandchildren)
|
@@ -156,10 +197,47 @@ The arrange method takes ActiveRecord find options. If you want your hashes to b
|
|
156
197
|
|
157
198
|
TreeNode.find_by_name('Crunchy').subtree.arrange(:order => :name)
|
158
199
|
|
200
|
+
To get the arranged nodes as a nested array of hashes for serialization:
|
201
|
+
|
202
|
+
TreeNode.arrange_serializable
|
203
|
+
|
204
|
+
[
|
205
|
+
{
|
206
|
+
"ancestry" => nil, "id" => 1, "children" => [
|
207
|
+
{ "ancestry" => "1", "id" => 2, "children" => [] }
|
208
|
+
]
|
209
|
+
}
|
210
|
+
]
|
211
|
+
|
212
|
+
You can also supply your own serialization logic using blocks:
|
213
|
+
|
214
|
+
For example, using Active Model Serializers:
|
215
|
+
|
216
|
+
TreeNode.arrange_serializable do |parent, children|
|
217
|
+
MySerializer.new(parent, children: children)
|
218
|
+
end
|
219
|
+
|
220
|
+
Or plain hashes:
|
221
|
+
|
222
|
+
TreeNode.arrange_serializable do |parent, children|
|
223
|
+
{
|
224
|
+
my_id: parent.id
|
225
|
+
my_children: children
|
226
|
+
}
|
227
|
+
end
|
228
|
+
|
229
|
+
The result of arrange_serializable can easily be serialized to json with 'to_json', or some other format:
|
230
|
+
|
231
|
+
TreeNode.arrange_serializable.to_json
|
232
|
+
|
233
|
+
You can also pass the order to the arrange_serializable method just as you can pass it to the arrange method:
|
234
|
+
|
235
|
+
TreeNode.arrange_serializable(:order => :name)
|
236
|
+
|
159
237
|
= Sorting
|
160
238
|
|
161
239
|
If you just want to sort an array of nodes as if you were traversing them in preorder, you can use the sort_by_ancestry class method:
|
162
|
-
|
240
|
+
|
163
241
|
TreeNode.sort_by_ancestry(array_of_nodes)
|
164
242
|
|
165
243
|
Note that since materialised path trees don't support ordering within a rank, the order of siblings depends on their order in the original array.
|
@@ -178,7 +256,7 @@ Most current tree plugins use a parent_id column (has_ancestry, awesome_nested_s
|
|
178
256
|
- Remove gem config line from environment.rb: config.gem [old gem]
|
179
257
|
- Add Ancestry to environment.rb: config.gem :ancestry
|
180
258
|
- See 'Installation' for more info on installing and configuring gems
|
181
|
-
|
259
|
+
|
182
260
|
3. Change your model
|
183
261
|
- Remove any macros required by old plugin/gem from app/models/[model].rb
|
184
262
|
- Add to app/models/[model].rb: <b>has_ancestry</b>
|
@@ -199,7 +277,7 @@ Most current tree plugins use a parent_id column (has_ancestry, awesome_nested_s
|
|
199
277
|
|
200
278
|
= Integrity checking and restoration
|
201
279
|
|
202
|
-
I don't see any way Ancestry tree integrity could get compromised without explicitly setting cyclic parents or invalid ancestry and circumventing validation with update_attribute, if you do, please let me know.
|
280
|
+
I don't see any way Ancestry tree integrity could get compromised without explicitly setting cyclic parents or invalid ancestry and circumventing validation with update_attribute, if you do, please let me know.
|
203
281
|
|
204
282
|
Ancestry includes some methods for detecting integrity problems and restoring integrity just to be sure. To check integrity use: [Model].check_ancestry_integrity!. An AncestryIntegrityException will be raised if there are any problems. You can also specify :report => :list to return an array of exceptions or :report => :echo to echo any error messages. To restore integrity use: [Model].restore_ancestry_integrity!.
|
205
283
|
|
@@ -224,18 +302,16 @@ Additionally, if you think something is wrong with your depth cache:
|
|
224
302
|
|
225
303
|
= Tests
|
226
304
|
|
227
|
-
The Ancestry gem comes with a unit test suite consisting of about
|
228
|
-
-
|
229
|
-
-
|
230
|
-
-
|
231
|
-
-
|
232
|
-
|
233
|
-
You can pass an environment variable for the database to test against (e.g. db=mysql). By default, the test suite runs against the latest activerecord version. You can run agains activerecord 3-0 or 3-1 as follows:
|
234
|
-
- run <tt>bundle --gemfile Gemfile.rails-<version></tt>
|
235
|
-
- run <tt>rake [test] BUNDLE_GEMFILE=Gemfile.rails-<version></tt>
|
305
|
+
The Ancestry gem comes with a unit test suite consisting of about 1900 assertions in about 50 tests. It takes about 10 seconds to run on sqlite. It is run against three databases (sqlite3, mysql and postgresql) and four versions of Activerecord (3.0, 3.1, 3.2 and 4.0) using Appraisals. To run it yourself:
|
306
|
+
- Check out the repository from GitHub
|
307
|
+
- Copy test/database.example.yml to test/database.yml
|
308
|
+
- Run <tt>bundle</tt>
|
309
|
+
- Run <tt>appraisal install</tt>
|
310
|
+
- Run <tt>appraisal rake test</tt>
|
236
311
|
|
237
|
-
|
238
|
-
|
312
|
+
You can also run against a specific database and specific version of Activerecord:
|
313
|
+
- Run the above commands, except for the last one
|
314
|
+
- Run <tt>appraisal sqlite3-ar-32 rake test</tt> (to test against sqlite3 and Activerecord 3.2)
|
239
315
|
|
240
316
|
= Internals
|
241
317
|
|
@@ -245,101 +321,10 @@ In the example above, the ancestry column is created as a string. This puts a li
|
|
245
321
|
|
246
322
|
The materialised path pattern requires Ancestry to use a 'like' condition in order to fetch descendants. This should not be particularly slow however since the the condition never starts with a wildcard which allows the DBMS to use the column index. If you have any data on performance with a large number of records, please drop me line.
|
247
323
|
|
248
|
-
=
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
- Fix depth_cache not being updated when moving up to ancestors (thx scottatron)
|
256
|
-
- add alias :root? to existing is_root? (thx divineforest)
|
257
|
-
- Add block to sort_by_ancestry (thx Iliya)
|
258
|
-
- Add attribute query method for parent_id (thx sj26)
|
259
|
-
- Fixed and tested for rails 4 (thx adammck, Nihad, Systho, Philippe, e.a.)
|
260
|
-
- Fixed overwriting ActiveRecord::Base.base_class (thx Rozhnov)
|
261
|
-
- New adopt strategy (thx unknown)
|
262
|
-
- Many more improvements
|
263
|
-
- Version 1.3.0 (2012-05-04)
|
264
|
-
- Ancestry now ignores default scopes when moving or destroying nodes, ensuring tree consistency
|
265
|
-
- Changed ActiveRecord dependency to 2.3.14
|
266
|
-
- Version 1.2.5 (2012-03-15)
|
267
|
-
- Fixed warnings: "parenthesize argument(s) for future version"
|
268
|
-
- Fixed a bug in the restore_ancestry_integrity! method (thx Arthur Holstvoogd)
|
269
|
-
- Version 1.2.4 (2011-04-22)
|
270
|
-
- Prepended table names to column names in queries (thx raelik)
|
271
|
-
- Better check to see if acts_as_tree can be overloaded (thx jims)
|
272
|
-
- Performance inprovements (thx kueda)
|
273
|
-
- Version 1.2.3 (2010-10-28)
|
274
|
-
- Fixed error with determining ActiveRecord version
|
275
|
-
- Added option to specify :primary_key_format (thanks goes to rolftimmermans)
|
276
|
-
- Version 1.2.2 (2010-10-24)
|
277
|
-
- Fixed all deprecation warnings for rails 3.0.X
|
278
|
-
- Added :report option to check_ancestry_integrity!
|
279
|
-
- Changed ActiveRecord dependency to 2.2.2
|
280
|
-
- Tested and fixed for ruby 1.8.7 and 1.9.2
|
281
|
-
- Changed usage of update_attributes to update_attribute to allow ancestry column protection
|
282
|
-
- Version 1.2.0 (2009-11-07)
|
283
|
-
- Removed some duplication in has_ancestry
|
284
|
-
- Cleaned up plugin pattern according to http://yehudakatz.com/2009/11/12/better-ruby-idioms/
|
285
|
-
- Moved parts of ancestry into seperate files
|
286
|
-
- Made it possible to pass options into the arrange method
|
287
|
-
- Renamed acts_as_tree to has_ancestry
|
288
|
-
- Aliased has_ancestry as acts_as_tree if acts_as_tree is available
|
289
|
-
- Added subtree_of scope
|
290
|
-
- Updated ordered_by_ancestry scope to support Microsoft SQL Server
|
291
|
-
- Added empty hash as parameter to exists? calls for older ActiveRecord versions
|
292
|
-
- Version 1.1.4 (2009-11-07)
|
293
|
-
- Thanks to a patch from tom taylor, Ancestry now works with different primary keys
|
294
|
-
- Version 1.1.3 (2009-11-01)
|
295
|
-
- Fixed a pretty bad bug where several operations took far too many queries
|
296
|
-
- Version 1.1.2 (2009-10-29)
|
297
|
-
- Added validation for depth cache column
|
298
|
-
- Added STI support (reported broken)
|
299
|
-
- Version 1.1.1 (2009-10-28)
|
300
|
-
- Fixed some parentheses warnings that where reported
|
301
|
-
- Fixed a reported issue with arrangement
|
302
|
-
- Fixed issues with ancestors and path order on postgres
|
303
|
-
- Added ordered_by_ancestry scope (needed to fix issues)
|
304
|
-
- Version 1.1.0 (2009-10-22)
|
305
|
-
- Depth caching (and cache rebuilding)
|
306
|
-
- Depth method for nodes
|
307
|
-
- Named scopes for selecting by depth
|
308
|
-
- Relative depth options for tree navigation methods:
|
309
|
-
- ancestors
|
310
|
-
- path
|
311
|
-
- descendants
|
312
|
-
- descendant_ids
|
313
|
-
- subtree
|
314
|
-
- subtree_ids
|
315
|
-
- Updated README
|
316
|
-
- Easy migration from existing plugins/gems
|
317
|
-
- acts_as_tree checks unknown options
|
318
|
-
- acts_as_tree checks that options are hash
|
319
|
-
- Added a bang (!) to the integrity functions
|
320
|
-
- Since these functions should only be used from ./script/console and not from your application, this change is not considered as breaking backwards compatibility and the major version wasn't bumped.
|
321
|
-
- Updated install script to point to documentation
|
322
|
-
- Removed rails specific init
|
323
|
-
- Removed uninstall script
|
324
|
-
- Version 1.0.0 (2009-10-16)
|
325
|
-
- Initial version
|
326
|
-
- Tree building
|
327
|
-
- Tree navigation
|
328
|
-
- Integrity checking / restoration
|
329
|
-
- Arrangement
|
330
|
-
- Orphan strategies
|
331
|
-
- Subtree movement
|
332
|
-
- Named scopes
|
333
|
-
- Validations
|
334
|
-
|
335
|
-
= Future work
|
336
|
-
|
337
|
-
I will try to keep Ancestry up to date with changing versions of Rails and Ruby and also with any bug reports I might receive. I will implement new features on request as I see fit. One thing I definitely want to do soon is some proper performance testing.
|
338
|
-
|
339
|
-
= Contact and copyright
|
340
|
-
|
341
|
-
Bug report? Faulty/incomplete documentation? Feature request? Please post an issue on 'http://github.com/stefankroes/ancestry/issues'. Please also contact me at s.a.kroes[at]gmail.com if it's urgent.
|
342
|
-
|
343
|
-
Question? Contact me at s.a.kroes[at]gmail.com, make sure you read the documentation.
|
344
|
-
|
345
|
-
Copyright (c) 2009 Stefan Kroes, released under the MIT license
|
324
|
+
= Contributing and license
|
325
|
+
|
326
|
+
I will try to keep Ancestry up to date with changing versions of Rails and Ruby and also with any bug reports I might receive. I will implement new features on request as I see fit and have time.
|
327
|
+
|
328
|
+
Question? Bug report? Faulty/incomplete documentation? Feature request? Please post an issue on 'http://github.com/stefankroes/ancestry/issues'. Make sure you have read the documentation and you have included tests and documentation with any pull request.
|
329
|
+
|
330
|
+
Copyright (c) 2016 Stefan Kroes, released under the MIT license
|
data/ancestry.gemspec
CHANGED
@@ -1,26 +1,45 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
require 'ancestry/version'
|
4
|
+
|
1
5
|
Gem::Specification.new do |s|
|
2
6
|
s.name = 'ancestry'
|
3
|
-
s.
|
4
|
-
s.
|
7
|
+
s.summary = 'Organize ActiveRecord model into a tree structure'
|
8
|
+
s.description = <<-EOF
|
9
|
+
Ancestry allows the records of a ActiveRecord model to be organized in a tree
|
10
|
+
structure, using a single, intuitively formatted database column. It exposes
|
11
|
+
all the standard tree structure relations (ancestors, parent, root, children,
|
12
|
+
siblings, descendants) and all of them can be fetched in a single sql query.
|
13
|
+
Additional features are named_scopes, integrity checking, integrity restoration,
|
14
|
+
arrangement of (sub)tree into hashes and different strategies for dealing with
|
15
|
+
orphaned records.
|
16
|
+
EOF
|
5
17
|
|
6
|
-
s.version =
|
18
|
+
s.version = Ancestry::VERSION
|
7
19
|
|
8
20
|
s.author = 'Stefan Kroes'
|
9
21
|
s.email = 's.a.kroes@gmail.com'
|
10
22
|
s.homepage = 'http://github.com/stefankroes/ancestry'
|
23
|
+
s.license = 'MIT'
|
11
24
|
|
12
25
|
s.files = [
|
13
|
-
'ancestry.gemspec',
|
14
|
-
'init.rb',
|
15
|
-
'install.rb',
|
16
|
-
'lib/ancestry.rb',
|
17
|
-
'lib/ancestry/has_ancestry.rb',
|
18
|
-
'lib/ancestry/exceptions.rb',
|
19
|
-
'lib/ancestry/class_methods.rb',
|
20
|
-
'lib/ancestry/instance_methods.rb',
|
21
|
-
'MIT-LICENSE',
|
26
|
+
'ancestry.gemspec',
|
27
|
+
'init.rb',
|
28
|
+
'install.rb',
|
29
|
+
'lib/ancestry.rb',
|
30
|
+
'lib/ancestry/has_ancestry.rb',
|
31
|
+
'lib/ancestry/exceptions.rb',
|
32
|
+
'lib/ancestry/class_methods.rb',
|
33
|
+
'lib/ancestry/instance_methods.rb',
|
34
|
+
'MIT-LICENSE',
|
22
35
|
'README.rdoc'
|
23
36
|
]
|
24
37
|
|
25
|
-
s.
|
38
|
+
s.required_ruby_version = '>= 1.8.7'
|
39
|
+
s.add_runtime_dependency 'activerecord', '>= 3.0.0'
|
40
|
+
s.add_development_dependency 'yard'
|
41
|
+
s.add_development_dependency 'rake', '~> 10.0'
|
42
|
+
s.add_development_dependency 'test-unit'
|
43
|
+
s.add_development_dependency 'minitest'
|
44
|
+
s.add_development_dependency 'sqlite3'
|
26
45
|
end
|
@@ -3,8 +3,8 @@ module Ancestry
|
|
3
3
|
# Fetch tree node if necessary
|
4
4
|
def to_node object
|
5
5
|
if object.is_a?(self.ancestry_base_class) then object else find(object) end
|
6
|
-
end
|
7
|
-
|
6
|
+
end
|
7
|
+
|
8
8
|
# Scope on relative depth options
|
9
9
|
def scope_depth depth_options, depth
|
10
10
|
depth_options.inject(self.ancestry_base_class) do |scope, option|
|
@@ -26,7 +26,7 @@ module Ancestry
|
|
26
26
|
raise Ancestry::AncestryException.new("Invalid orphan strategy, valid ones are :rootify,:adopt, :restrict and :destroy.")
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
# Arrangement
|
31
31
|
def arrange options = {}
|
32
32
|
scope =
|
@@ -38,43 +38,60 @@ module Ancestry
|
|
38
38
|
# Get all nodes ordered by ancestry and start sorting them into an empty hash
|
39
39
|
arrange_nodes scope.where(options)
|
40
40
|
end
|
41
|
-
|
42
|
-
# Arrange array of nodes into a nested hash of the form
|
41
|
+
|
42
|
+
# Arrange array of nodes into a nested hash of the form
|
43
43
|
# {node => children}, where children = {} if the node has no children
|
44
44
|
def arrange_nodes(nodes)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
45
|
+
arranged = ActiveSupport::OrderedHash.new
|
46
|
+
min_depth = Float::INFINITY
|
47
|
+
index = Hash.new { |h, k| h[k] = ActiveSupport::OrderedHash.new }
|
48
|
+
|
49
|
+
nodes.each do |node|
|
50
|
+
children = index[node.id]
|
51
|
+
index[node.parent_id][node] = children
|
52
|
+
|
53
|
+
depth = node.depth
|
54
|
+
if depth < min_depth
|
55
|
+
min_depth = depth
|
56
|
+
arranged.clear
|
57
|
+
end
|
58
|
+
arranged[node] = children if depth == min_depth
|
59
|
+
end
|
60
|
+
|
61
|
+
arranged
|
62
|
+
end
|
63
|
+
|
64
|
+
# Arrangement to nested array
|
65
|
+
def arrange_serializable options={}, nodes=nil, &block
|
66
|
+
nodes = arrange(options) if nodes.nil?
|
67
|
+
nodes.map do |parent, children|
|
68
|
+
if block_given?
|
69
|
+
yield parent, arrange_serializable(options, children, &block)
|
70
|
+
else
|
71
|
+
parent.serializable_hash.merge 'children' => arrange_serializable(options, children)
|
72
|
+
end
|
56
73
|
end
|
57
74
|
end
|
58
|
-
|
59
|
-
# Pseudo-preordered array of nodes. Children will always follow parents,
|
75
|
+
|
76
|
+
# Pseudo-preordered array of nodes. Children will always follow parents,
|
60
77
|
# for ordering nodes within a rank provide block, eg. Node.sort_by_ancestry(Node.all) {|a, b| a.rank <=> b.rank}.
|
61
78
|
def sort_by_ancestry(nodes, &block)
|
62
79
|
arranged = nodes if nodes.is_a?(Hash)
|
63
|
-
|
80
|
+
|
64
81
|
unless arranged
|
65
82
|
presorted_nodes = nodes.sort do |a, b|
|
66
83
|
a_cestry, b_cestry = a.ancestry || '0', b.ancestry || '0'
|
67
|
-
|
84
|
+
|
68
85
|
if block_given? && a_cestry == b_cestry
|
69
86
|
yield a, b
|
70
87
|
else
|
71
88
|
a_cestry <=> b_cestry
|
72
89
|
end
|
73
90
|
end
|
74
|
-
|
91
|
+
|
75
92
|
arranged = arrange_nodes(presorted_nodes)
|
76
93
|
end
|
77
|
-
|
94
|
+
|
78
95
|
arranged.inject([]) do |sorted_nodes, pair|
|
79
96
|
node, children = pair
|
80
97
|
sorted_nodes << node
|
@@ -87,8 +104,8 @@ module Ancestry
|
|
87
104
|
def check_ancestry_integrity! options = {}
|
88
105
|
parents = {}
|
89
106
|
exceptions = [] if options[:report] == :list
|
90
|
-
|
91
|
-
self.ancestry_base_class.unscoped do
|
107
|
+
|
108
|
+
self.ancestry_base_class.unscoped do
|
92
109
|
# For each node ...
|
93
110
|
self.ancestry_base_class.find_each do |node|
|
94
111
|
begin
|
@@ -126,7 +143,7 @@ module Ancestry
|
|
126
143
|
parents = {}
|
127
144
|
# Wrap the whole thing in a transaction ...
|
128
145
|
self.ancestry_base_class.transaction do
|
129
|
-
self.ancestry_base_class.unscoped do
|
146
|
+
self.ancestry_base_class.unscoped do
|
130
147
|
# For each node ...
|
131
148
|
self.ancestry_base_class.find_each do |node|
|
132
149
|
# ... set its ancestry to nil if invalid
|
@@ -143,9 +160,9 @@ module Ancestry
|
|
143
160
|
until parent.nil? || parent == node.id
|
144
161
|
parent = parents[parent]
|
145
162
|
end
|
146
|
-
parents[node.id] = nil if parent == node.id
|
163
|
+
parents[node.id] = nil if parent == node.id
|
147
164
|
end
|
148
|
-
|
165
|
+
|
149
166
|
# For each node ...
|
150
167
|
self.ancestry_base_class.find_each do |node|
|
151
168
|
# ... rebuild ancestry from parents array
|
@@ -160,10 +177,10 @@ module Ancestry
|
|
160
177
|
end
|
161
178
|
end
|
162
179
|
end
|
163
|
-
|
180
|
+
|
164
181
|
# Build ancestry from parent id's for migration purposes
|
165
182
|
def build_ancestry_from_parent_ids! parent_id = nil, ancestry = nil
|
166
|
-
self.ancestry_base_class.unscoped do
|
183
|
+
self.ancestry_base_class.unscoped do
|
167
184
|
self.ancestry_base_class.where(:parent_id => parent_id).find_each do |node|
|
168
185
|
node.without_ancestry_callbacks do
|
169
186
|
node.update_attribute ancestry_column, ancestry
|
@@ -172,14 +189,16 @@ module Ancestry
|
|
172
189
|
end
|
173
190
|
end
|
174
191
|
end
|
175
|
-
|
192
|
+
|
176
193
|
# Rebuild depth cache if it got corrupted or if depth caching was just turned on
|
177
194
|
def rebuild_depth_cache!
|
178
195
|
raise Ancestry::AncestryException.new("Cannot rebuild depth cache for model without depth caching.") unless respond_to? :depth_cache_column
|
179
|
-
|
180
|
-
self.ancestry_base_class.
|
181
|
-
self.ancestry_base_class.
|
182
|
-
|
196
|
+
|
197
|
+
self.ancestry_base_class.transaction do
|
198
|
+
self.ancestry_base_class.unscoped do
|
199
|
+
self.ancestry_base_class.find_each do |node|
|
200
|
+
node.update_attribute depth_cache_column, node.depth
|
201
|
+
end
|
183
202
|
end
|
184
203
|
end
|
185
204
|
end
|
@@ -3,11 +3,11 @@ class << ActiveRecord::Base
|
|
3
3
|
# Check options
|
4
4
|
raise Ancestry::AncestryException.new("Options for has_ancestry must be in a hash.") unless options.is_a? Hash
|
5
5
|
options.each do |key, value|
|
6
|
-
unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column].include? key
|
6
|
+
unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch].include? key
|
7
7
|
raise Ancestry::AncestryException.new("Unknown option for has_ancestry: #{key.inspect} => #{value.inspect}.")
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
# Include instance methods
|
12
12
|
include Ancestry::InstanceMethods
|
13
13
|
|
@@ -25,13 +25,17 @@ class << ActiveRecord::Base
|
|
25
25
|
# Save self as base class (for STI)
|
26
26
|
cattr_accessor :ancestry_base_class
|
27
27
|
self.ancestry_base_class = self
|
28
|
-
|
28
|
+
|
29
|
+
# Touch ancestors after updating
|
30
|
+
cattr_accessor :touch_ancestors
|
31
|
+
self.touch_ancestors = options[:touch] || false
|
32
|
+
|
29
33
|
# Validate format of ancestry column value
|
30
34
|
validates_format_of ancestry_column, :with => Ancestry::ANCESTRY_PATTERN, :allow_nil => true
|
31
35
|
|
32
36
|
# Validate that the ancestor ids don't include own id
|
33
37
|
validate :ancestry_exclude_self
|
34
|
-
|
38
|
+
|
35
39
|
# Named scopes
|
36
40
|
scope :roots, lambda { where(ancestry_column => nil) }
|
37
41
|
scope :ancestors_of, lambda { |object| where(to_node(object).ancestor_conditions) }
|
@@ -39,9 +43,24 @@ class << ActiveRecord::Base
|
|
39
43
|
scope :descendants_of, lambda { |object| where(to_node(object).descendant_conditions) }
|
40
44
|
scope :subtree_of, lambda { |object| where(to_node(object).subtree_conditions) }
|
41
45
|
scope :siblings_of, lambda { |object| where(to_node(object).sibling_conditions) }
|
42
|
-
scope :ordered_by_ancestry, lambda {
|
43
|
-
|
44
|
-
|
46
|
+
scope :ordered_by_ancestry, lambda {
|
47
|
+
if %w(mysql mysql2 sqlite postgresql).include?(connection.adapter_name.downcase) &&
|
48
|
+
defined?(ActiveRecord.version) && ActiveRecord.version.to_s >= "5"
|
49
|
+
reorder("coalesce(#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, '')")
|
50
|
+
else
|
51
|
+
reorder("(CASE WHEN #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)} IS NULL THEN 0 ELSE 1 END), #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}")
|
52
|
+
end
|
53
|
+
}
|
54
|
+
scope :ordered_by_ancestry_and, lambda { |order|
|
55
|
+
if %w(mysql mysql2 sqlite postgresql).include?(connection.adapter_name.downcase) &&
|
56
|
+
defined?(ActiveRecord.version) && ActiveRecord.version.to_s >= "5"
|
57
|
+
reorder("coalesce(#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, ''), #{order}")
|
58
|
+
else
|
59
|
+
reorder("(CASE WHEN #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)} IS NULL THEN 0 ELSE 1 END), #{connection.quote_table_name(table_name)}.#{connection.quote_column_name(ancestry_column)}, #{order}")
|
60
|
+
end
|
61
|
+
}
|
62
|
+
scope :path_of, lambda { |object| to_node(object).path }
|
63
|
+
|
45
64
|
# Update descendants with new ancestry before save
|
46
65
|
before_save :update_descendants_with_new_ancestry
|
47
66
|
|
@@ -61,7 +80,7 @@ class << ActiveRecord::Base
|
|
61
80
|
# Validate depth column
|
62
81
|
validates_numericality_of depth_cache_column, :greater_than_or_equal_to => 0, :only_integer => true, :allow_nil => false
|
63
82
|
end
|
64
|
-
|
83
|
+
|
65
84
|
# Create named scopes for depth
|
66
85
|
{:before_depth => '<', :to_depth => '<=', :at_depth => '=', :from_depth => '>=', :after_depth => '>'}.each do |scope_name, operator|
|
67
86
|
scope scope_name, lambda { |depth|
|
@@ -69,10 +88,17 @@ class << ActiveRecord::Base
|
|
69
88
|
where("#{depth_cache_column} #{operator} ?", depth)
|
70
89
|
}
|
71
90
|
end
|
91
|
+
|
92
|
+
after_touch :touch_ancestors_callback
|
93
|
+
after_destroy :touch_ancestors_callback
|
94
|
+
after_save :touch_ancestors_callback, if: :changed?
|
72
95
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
96
|
+
end
|
97
|
+
|
98
|
+
ActiveSupport.on_load :active_record do
|
99
|
+
if not(ActiveRecord::Base.respond_to?(:acts_as_tree))
|
100
|
+
class << ActiveRecord::Base
|
101
|
+
alias_method :acts_as_tree, :has_ancestry
|
102
|
+
end
|
77
103
|
end
|
78
104
|
end
|
@@ -53,6 +53,8 @@ module Ancestry
|
|
53
53
|
descendants.each do |descendant|
|
54
54
|
descendant.without_ancestry_callbacks do
|
55
55
|
new_ancestry = descendant.ancestor_ids.delete_if { |x| x == self.id }.join("/")
|
56
|
+
# check for empty string if it's then set to nil
|
57
|
+
new_ancestry = nil if new_ancestry.empty?
|
56
58
|
descendant.update_attribute descendant.class.ancestry_column, new_ancestry || nil
|
57
59
|
end
|
58
60
|
end
|
@@ -64,6 +66,25 @@ module Ancestry
|
|
64
66
|
end
|
65
67
|
end
|
66
68
|
|
69
|
+
# Touch each of this record's ancestors
|
70
|
+
def touch_ancestors_callback
|
71
|
+
|
72
|
+
# Skip this if callbacks are disabled
|
73
|
+
unless ancestry_callbacks_disabled?
|
74
|
+
|
75
|
+
# Only touch if the option is enabled
|
76
|
+
if self.ancestry_base_class.touch_ancestors
|
77
|
+
|
78
|
+
# Touch each of the old *and* new ancestors
|
79
|
+
self.class.where(id: (ancestor_ids + ancestor_ids_was).uniq).each do |ancestor|
|
80
|
+
ancestor.without_ancestry_callbacks do
|
81
|
+
ancestor.touch
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
67
88
|
# The ancestry value for this record's children
|
68
89
|
def child_ancestry
|
69
90
|
# New records cannot have children
|
@@ -73,20 +94,34 @@ module Ancestry
|
|
73
94
|
end
|
74
95
|
|
75
96
|
# Ancestors
|
97
|
+
|
76
98
|
def ancestry_changed?
|
77
99
|
changed.include?(self.ancestry_base_class.ancestry_column.to_s)
|
78
100
|
end
|
79
101
|
|
102
|
+
def parse_ancestry_column obj
|
103
|
+
obj.to_s.split('/').map { |id| cast_primary_key(id) }
|
104
|
+
end
|
105
|
+
|
80
106
|
def ancestor_ids
|
81
|
-
read_attribute(self.ancestry_base_class.ancestry_column)
|
107
|
+
parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
|
82
108
|
end
|
83
109
|
|
84
110
|
def ancestor_conditions
|
85
|
-
|
111
|
+
t = get_arel_table
|
112
|
+
t[get_primary_key_column].in(ancestor_ids)
|
86
113
|
end
|
87
114
|
|
88
115
|
def ancestors depth_options = {}
|
89
|
-
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where
|
116
|
+
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
|
117
|
+
end
|
118
|
+
|
119
|
+
def ancestor_was_conditions
|
120
|
+
{primary_key_with_table => ancestor_ids_was}
|
121
|
+
end
|
122
|
+
|
123
|
+
def ancestor_ids_was
|
124
|
+
parse_ancestry_column(changed_attributes[self.ancestry_base_class.ancestry_column.to_s])
|
90
125
|
end
|
91
126
|
|
92
127
|
def path_ids
|
@@ -94,11 +129,12 @@ module Ancestry
|
|
94
129
|
end
|
95
130
|
|
96
131
|
def path_conditions
|
97
|
-
|
132
|
+
t = get_arel_table
|
133
|
+
t[get_primary_key_column].in(path_ids)
|
98
134
|
end
|
99
135
|
|
100
136
|
def path depth_options = {}
|
101
|
-
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where
|
137
|
+
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where path_conditions
|
102
138
|
end
|
103
139
|
|
104
140
|
def depth
|
@@ -109,7 +145,12 @@ module Ancestry
|
|
109
145
|
write_attribute self.ancestry_base_class.depth_cache_column, depth
|
110
146
|
end
|
111
147
|
|
148
|
+
def ancestor_of?(node)
|
149
|
+
node.ancestor_ids.include?(self.id)
|
150
|
+
end
|
151
|
+
|
112
152
|
# Parent
|
153
|
+
|
113
154
|
def parent= parent
|
114
155
|
write_attribute(self.ancestry_base_class.ancestry_column, if parent.nil? then nil else parent.child_ancestry end)
|
115
156
|
end
|
@@ -130,7 +171,12 @@ module Ancestry
|
|
130
171
|
parent_id.present?
|
131
172
|
end
|
132
173
|
|
174
|
+
def parent_of?(node)
|
175
|
+
self.id == node.parent_id
|
176
|
+
end
|
177
|
+
|
133
178
|
# Root
|
179
|
+
|
134
180
|
def root_id
|
135
181
|
if ancestor_ids.empty? then id else ancestor_ids.first end
|
136
182
|
end
|
@@ -144,9 +190,15 @@ module Ancestry
|
|
144
190
|
end
|
145
191
|
alias :root? :is_root?
|
146
192
|
|
193
|
+
def root_of?(node)
|
194
|
+
self.id == node.root_id
|
195
|
+
end
|
196
|
+
|
147
197
|
# Children
|
198
|
+
|
148
199
|
def child_conditions
|
149
|
-
|
200
|
+
t = get_arel_table
|
201
|
+
t[get_ancestry_column].eq(child_ancestry)
|
150
202
|
end
|
151
203
|
|
152
204
|
def children
|
@@ -160,14 +212,22 @@ module Ancestry
|
|
160
212
|
def has_children?
|
161
213
|
self.children.exists?({})
|
162
214
|
end
|
215
|
+
alias_method :children?, :has_children?
|
163
216
|
|
164
217
|
def is_childless?
|
165
218
|
!has_children?
|
166
219
|
end
|
220
|
+
alias_method :childless?, :is_childless?
|
221
|
+
|
222
|
+
def child_of?(node)
|
223
|
+
self.parent_id == node.id
|
224
|
+
end
|
167
225
|
|
168
226
|
# Siblings
|
227
|
+
|
169
228
|
def sibling_conditions
|
170
|
-
|
229
|
+
t = get_arel_table
|
230
|
+
t[get_ancestry_column].eq(read_attribute(self.ancestry_base_class.ancestry_column))
|
171
231
|
end
|
172
232
|
|
173
233
|
def siblings
|
@@ -181,14 +241,27 @@ module Ancestry
|
|
181
241
|
def has_siblings?
|
182
242
|
self.siblings.count > 1
|
183
243
|
end
|
244
|
+
alias_method :siblings?, :has_siblings?
|
184
245
|
|
185
246
|
def is_only_child?
|
186
247
|
!has_siblings?
|
187
248
|
end
|
249
|
+
alias_method :only_child?, :is_only_child?
|
250
|
+
|
251
|
+
def sibling_of?(node)
|
252
|
+
self.ancestry == node.ancestry
|
253
|
+
end
|
188
254
|
|
189
255
|
# Descendants
|
256
|
+
|
190
257
|
def descendant_conditions
|
191
|
-
|
258
|
+
t = get_arel_table
|
259
|
+
# rails has case sensitive matching.
|
260
|
+
if defined?(ActiveRecord.version) && ActiveRecord.version.to_s >= "5"
|
261
|
+
t[get_ancestry_column].matches("#{child_ancestry}/%", nil, true).or(t[get_ancestry_column].eq(child_ancestry))
|
262
|
+
else
|
263
|
+
t[get_ancestry_column].matches("#{child_ancestry}/%").or(t[get_ancestry_column].eq(child_ancestry))
|
264
|
+
end
|
192
265
|
end
|
193
266
|
|
194
267
|
def descendants depth_options = {}
|
@@ -199,9 +272,15 @@ module Ancestry
|
|
199
272
|
descendants(depth_options).select(self.ancestry_base_class.primary_key).collect(&self.ancestry_base_class.primary_key.to_sym)
|
200
273
|
end
|
201
274
|
|
275
|
+
def descendant_of?(node)
|
276
|
+
ancestor_ids.include?(node.id)
|
277
|
+
end
|
278
|
+
|
202
279
|
# Subtree
|
280
|
+
|
203
281
|
def subtree_conditions
|
204
|
-
|
282
|
+
t = get_arel_table
|
283
|
+
descendant_conditions.or(t[get_primary_key_column].eq(self.id))
|
205
284
|
end
|
206
285
|
|
207
286
|
def subtree depth_options = {}
|
@@ -213,6 +292,7 @@ module Ancestry
|
|
213
292
|
end
|
214
293
|
|
215
294
|
# Callback disabling
|
295
|
+
|
216
296
|
def without_ancestry_callbacks
|
217
297
|
@disable_ancestry_callbacks = true
|
218
298
|
yield
|
@@ -220,13 +300,13 @@ module Ancestry
|
|
220
300
|
end
|
221
301
|
|
222
302
|
def ancestry_callbacks_disabled?
|
223
|
-
|
303
|
+
defined?(@disable_ancestry_callbacks) && @disable_ancestry_callbacks
|
224
304
|
end
|
225
305
|
|
226
306
|
private
|
227
307
|
|
228
308
|
def cast_primary_key(key)
|
229
|
-
if
|
309
|
+
if [:string, :uuid, :text].include? primary_key_type
|
230
310
|
key
|
231
311
|
else
|
232
312
|
key.to_i
|
@@ -236,28 +316,33 @@ module Ancestry
|
|
236
316
|
def primary_key_type
|
237
317
|
@primary_key_type ||= column_for_attribute(self.class.primary_key).type
|
238
318
|
end
|
319
|
+
|
239
320
|
def unscoped_descendants
|
240
321
|
self.ancestry_base_class.unscoped do
|
241
322
|
self.ancestry_base_class.where descendant_conditions
|
242
323
|
end
|
243
324
|
end
|
244
325
|
|
245
|
-
#
|
246
|
-
# bypassed to determine if chidren should be affected
|
326
|
+
# Validates the ancestry, but can also be applied if validation is bypassed to determine if children should be affected
|
247
327
|
def sane_ancestry?
|
248
|
-
|
328
|
+
ancestry_value = read_attribute(self.ancestry_base_class.ancestry_column)
|
329
|
+
ancestry_value.nil? || (ancestry_value.to_s =~ Ancestry::ANCESTRY_PATTERN && !ancestor_ids.include?(self.id))
|
249
330
|
end
|
250
331
|
|
251
332
|
def unscoped_find id
|
252
333
|
self.ancestry_base_class.unscoped { self.ancestry_base_class.find(id) }
|
253
334
|
end
|
254
335
|
|
255
|
-
def
|
256
|
-
|
336
|
+
def get_arel_table
|
337
|
+
self.ancestry_base_class.arel_table
|
338
|
+
end
|
339
|
+
|
340
|
+
def get_primary_key_column
|
341
|
+
self.ancestry_base_class.primary_key.to_sym
|
257
342
|
end
|
258
343
|
|
259
|
-
def
|
260
|
-
|
344
|
+
def get_ancestry_column
|
345
|
+
self.ancestry_base_class.ancestry_column.to_sym
|
261
346
|
end
|
262
347
|
end
|
263
348
|
end
|
data/lib/ancestry.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require_relative 'ancestry/class_methods'
|
2
|
+
require_relative 'ancestry/instance_methods'
|
3
|
+
require_relative 'ancestry/exceptions'
|
4
|
+
require_relative 'ancestry/has_ancestry'
|
5
5
|
|
6
6
|
module Ancestry
|
7
7
|
ANCESTRY_PATTERN = /\A[0-9]+(\/[0-9]+)*\Z/
|
metadata
CHANGED
@@ -1,76 +1,144 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ancestry
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
5
|
-
prerelease:
|
4
|
+
version: 2.2.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Stefan Kroes
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2016-11-01 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: activerecord
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 3.0.0
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: 3.0.0
|
30
|
-
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: test-unit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: |2
|
98
|
+
Ancestry allows the records of a ActiveRecord model to be organized in a tree
|
99
|
+
structure, using a single, intuitively formatted database column. It exposes
|
100
|
+
all the standard tree structure relations (ancestors, parent, root, children,
|
101
|
+
siblings, descendants) and all of them can be fetched in a single sql query.
|
102
|
+
Additional features are named_scopes, integrity checking, integrity restoration,
|
103
|
+
arrangement of (sub)tree into hashes and different strategies for dealing with
|
104
|
+
orphaned records.
|
31
105
|
email: s.a.kroes@gmail.com
|
32
106
|
executables: []
|
33
107
|
extensions: []
|
34
108
|
extra_rdoc_files: []
|
35
109
|
files:
|
110
|
+
- MIT-LICENSE
|
111
|
+
- README.rdoc
|
36
112
|
- ancestry.gemspec
|
37
113
|
- init.rb
|
38
114
|
- install.rb
|
39
115
|
- lib/ancestry.rb
|
40
|
-
- lib/ancestry/has_ancestry.rb
|
41
|
-
- lib/ancestry/exceptions.rb
|
42
116
|
- lib/ancestry/class_methods.rb
|
117
|
+
- lib/ancestry/exceptions.rb
|
118
|
+
- lib/ancestry/has_ancestry.rb
|
43
119
|
- lib/ancestry/instance_methods.rb
|
44
|
-
- MIT-LICENSE
|
45
|
-
- README.rdoc
|
46
120
|
homepage: http://github.com/stefankroes/ancestry
|
47
|
-
licenses:
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
48
124
|
post_install_message:
|
49
125
|
rdoc_options: []
|
50
126
|
require_paths:
|
51
127
|
- lib
|
52
128
|
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
129
|
requirements:
|
55
|
-
- -
|
130
|
+
- - ">="
|
56
131
|
- !ruby/object:Gem::Version
|
57
|
-
version:
|
132
|
+
version: 1.8.7
|
58
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
-
none: false
|
60
134
|
requirements:
|
61
|
-
- -
|
135
|
+
- - ">="
|
62
136
|
- !ruby/object:Gem::Version
|
63
137
|
version: '0'
|
64
138
|
requirements: []
|
65
139
|
rubyforge_project:
|
66
|
-
rubygems_version:
|
140
|
+
rubygems_version: 2.5.1
|
67
141
|
signing_key:
|
68
|
-
specification_version:
|
69
|
-
summary:
|
70
|
-
tree structure, using a single, intuitively formatted database column. It exposes
|
71
|
-
all the standard tree structure relations (ancestors, parent, root, children, siblings,
|
72
|
-
descendants) and all of them can be fetched in a single sql query. Additional features
|
73
|
-
are named_scopes, integrity checking, integrity restoration, arrangement of (sub)tree
|
74
|
-
into hashes and different strategies for dealing with orphaned records.
|
142
|
+
specification_version: 4
|
143
|
+
summary: Organize ActiveRecord model into a tree structure
|
75
144
|
test_files: []
|
76
|
-
has_rdoc:
|