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 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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Stefan Kroes
1
+ Copyright (c) 2016 Stefan Kroes
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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
- 1. Install
10
- - <b>Rails 2</b>
11
- - See 1-3-stable branch
12
- - <b>Rails 3</b>
13
- - Add to Gemfile: <b>gem 'ancestry'</b>
14
- - Install required gems: <b>bundle install</b>
15
-
16
- 2. Add ancestry column to your table
17
- - Create migration: <b>rails g migration add_ancestry_to_[table] ancestry:string</b>
18
- - Add index to migration: <b>add_index [table], :ancestry</b> (UP) / <b>remove_index [table], :ancestry</b> (DOWN)
19
- - Migrate your database: <b>rake db:migrate</b>
20
-
21
- 3. Add ancestry to your model
22
- - Add to app/models/[model].rb: <b>has_ancestry</b>
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 childen, false otherwise
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
- = Options for has_ancestry
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.If the deleted node is Root, then rootify the orphan subtree.
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 1800 assertions in about 30 tests. It takes about 10 seconds to run on sqlite. To run it yourself:
228
- - check out the repository from GitHub
229
- - copy test/database.example.yml to test/database.yml
230
- - run <tt>bundle</tt>
231
- - run <tt>rake [test]</tt>
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
- To run the test suite multiple times against several databases and all supported activerecord versions, run <tt>rake test_all</tt>
238
- The test suite is located in test/has_ancestry_test.rb.
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
- = Version history
249
-
250
- The latest version of ancestry is recommended. The three numbers of each version numbers are respectively the major, minor and patch versions. We started with major version 1 because it looks so much better and ancestry was already quite mature and complete when it was published. The major version is only bumped when backwards compatibility is broken. The minor version is bumped when new features are added. The patch version is bumped when bugs are fixed.
251
-
252
- - Version 2.0.0 (2013-05-17)
253
- - Removed rails 2 compatibility
254
- - Added table name to condition constructing methods (thx aflatter)
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.description = 'Organise ActiveRecord model into a tree structure'
4
- s.summary = 'Ancestry allows the records of a ActiveRecord model to be organised in a tree structure, using a single, intuitively formatted database column. 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 named_scopes, integrity checking, integrity restoration, arrangement of (sub)tree into hashes and different strategies for dealing with orphaned records.'
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 = '2.0.0'
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.add_dependency 'activerecord', '>= 3.0.0'
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
- # Get all nodes ordered by ancestry and start sorting them into an empty hash
46
- nodes.inject(ActiveSupport::OrderedHash.new) do |arranged_nodes, node|
47
- # Find the insertion point for that node by going through its ancestors
48
- node.ancestor_ids.inject(arranged_nodes) do |insertion_point, ancestor_id|
49
- insertion_point.each do |parent, children|
50
- # Change the insertion point to children if node is a descendant of this parent
51
- insertion_point = children if ancestor_id == parent.id
52
- end
53
- insertion_point
54
- end[node] = ActiveSupport::OrderedHash.new
55
- arranged_nodes
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.unscoped do
181
- self.ancestry_base_class.find_each do |node|
182
- node.update_attribute depth_cache_column, node.depth
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 { reorder("(case when #{table_name}.#{ancestry_column} is null then 0 else 1 end), #{table_name}.#{ancestry_column}") }
43
- scope :ordered_by_ancestry_and, lambda { |order| reorder("(case when #{table_name}.#{ancestry_column} is null then 0 else 1 end), #{table_name}.#{ancestry_column}, #{order}") }
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
- # Alias has_ancestry with acts_as_tree, if it's available.
75
- if !defined?(ActsAsTree)
76
- alias_method :acts_as_tree, :has_ancestry
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).to_s.split('/').map { |id| cast_primary_key(id) }
107
+ parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
82
108
  end
83
109
 
84
110
  def ancestor_conditions
85
- {primary_key_with_table => ancestor_ids}
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 ancestor_conditions
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
- {primary_key_with_table => path_ids}
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 path_conditions
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
- {ancestry_column_with_table => child_ancestry}
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
- {ancestry_column_with_table => read_attribute(self.ancestry_base_class.ancestry_column)}
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
- ["#{ancestry_column_with_table} like ? or #{ancestry_column_with_table} = ?", "#{child_ancestry}/%", child_ancestry]
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
- ["#{primary_key_with_table} = ? or #{ancestry_column_with_table} like ? or #{ancestry_column_with_table} = ?", self.id, "#{child_ancestry}/%", child_ancestry]
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
- !!@disable_ancestry_callbacks
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 primary_key_type == :string
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
- # basically validates the ancestry, but also applied if validation is
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
- ancestry.nil? || (ancestry.to_s =~ Ancestry::ANCESTRY_PATTERN && !ancestor_ids.include?(self.id))
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 primary_key_with_table
256
- "#{self.ancestry_base_class.table_name}.#{self.ancestry_base_class.primary_key}"
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 ancestry_column_with_table
260
- "#{self.ancestry_base_class.table_name}.#{self.ancestry_base_class.ancestry_column}"
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
- require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/class_methods')
2
- require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/instance_methods')
3
- require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/exceptions')
4
- require File.join(File.expand_path(File.dirname(__FILE__)), 'ancestry/has_ancestry')
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.0.0
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: 2013-05-17 00:00:00.000000000 Z
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
- description: Organise ActiveRecord model into a tree structure
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: '0'
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: 1.8.25
140
+ rubygems_version: 2.5.1
67
141
  signing_key:
68
- specification_version: 3
69
- summary: Ancestry allows the records of a ActiveRecord model to be organised in a
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: