ancestry 3.2.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -32
- data/README.md +22 -2
- data/lib/ancestry/class_methods.rb +21 -23
- data/lib/ancestry/has_ancestry.rb +22 -12
- data/lib/ancestry/instance_methods.rb +39 -26
- data/lib/ancestry/materialized_path.rb +24 -34
- data/lib/ancestry/materialized_path2.rb +54 -0
- data/lib/ancestry/materialized_path_pg.rb +15 -15
- data/lib/ancestry/version.rb +1 -1
- metadata +21 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82bcd1895093ab9b569806ef05fc307fc2ca9ab53ffeb09199bd66fe3672ecfb
|
|
4
|
+
data.tar.gz: fe7d0d356641658be2953309c27dd28a42e81ed569690cc5e26b2721b1aa6a37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f33384a1114d865662be0133c532249be0e7ea438a66e469947b3aaba3172378ae50309e02c4f219c1f161281a9e060bff6087a496850c26351fd83348627e79
|
|
7
|
+
data.tar.gz: 2a6cb3fb28f6c9a8228b522c590c2125c4e31056a97ac6a6cda96ea4152725fe89e375b59c894091ebcd3d5be36ad18b348c6691211bc6bea2b0a49502c9f423
|
data/CHANGELOG.md
CHANGED
|
@@ -3,15 +3,37 @@
|
|
|
3
3
|
Doing our best at supporting [SemVer](http://semver.org/) with
|
|
4
4
|
a nice looking [Changelog](http://keepachangelog.com).
|
|
5
5
|
|
|
6
|
-
## Version [
|
|
6
|
+
## Version [4.2.0] <sub><sup>2022-06-09</sub></sup>
|
|
7
7
|
|
|
8
|
-
*
|
|
8
|
+
* added strategy: materialized_path2 [#571](https://github.com/stefankroes/ancestry/pull/571)
|
|
9
|
+
* Added tree_view method [#561](https://github.com/stefankroes/ancestry/pull/561) (thx @bizcho)
|
|
10
|
+
* Fixed bug when errors would not undo callbacks [#566](https://github.com/stefankroes/ancestry/pull/566) (thx @daniloisr)
|
|
11
|
+
* ruby 3.0 support
|
|
12
|
+
* rails 7.0 support (thx @chenillen, @petergoldstein)
|
|
13
|
+
* Documentation fixes (thx @benkoshy, @mijoharas)
|
|
9
14
|
|
|
10
|
-
##
|
|
15
|
+
## Version [4.1.0] <sub><sup>2021-06-25</sub></sup>
|
|
16
|
+
|
|
17
|
+
* `parent` with an invalid id now returns nil (thx @vanboom)
|
|
18
|
+
* `root` returns self if ancestry is invalid (thx @vanboom)
|
|
19
|
+
* fix case where invalid object prevented ancestry updates (thx @d-m-u)
|
|
20
|
+
* oracleenhanced uses nulls first for sorting (thx @lual)
|
|
21
|
+
* fix counter cache and STI (thx @mattvague)
|
|
22
|
+
|
|
23
|
+
## Version [4.0.0] <sub><sup>2021-04-12</sub></sup>
|
|
24
|
+
|
|
25
|
+
* dropped support for rails 4.2 and 5.0 (thx @d-m-u)
|
|
26
|
+
* better documentation counter cache option (thx @pustomytnyk)
|
|
27
|
+
* clean up code (thx @amatsuda @d-m-u)
|
|
28
|
+
* fixed rails 6.1 support (thx @cmr119 @d-staehler @danini-the-panini )
|
|
29
|
+
* phasing out `parent_id?`, `ancestors?` and using `has_parent?` instead
|
|
30
|
+
* fixed postgres order bug on rails 6.2 and higher (thx @smoyt)
|
|
31
|
+
|
|
32
|
+
## Version [3.2.1] <sub><sup>2020-09-23</sub></sup>
|
|
11
33
|
|
|
12
34
|
* fixed gemspec to include locales and pg (thx @HectorMF)
|
|
13
35
|
|
|
14
|
-
##
|
|
36
|
+
## Version [3.2.0] <sub><sup>2020-09-23</sub></sup>
|
|
15
37
|
|
|
16
38
|
* introduce i18n
|
|
17
39
|
* pg sql optimization for ancestry changes (thx @suonlight and @geis)
|
|
@@ -20,7 +42,7 @@ a nice looking [Changelog](http://keepachangelog.com).
|
|
|
20
42
|
* able to convert to ancestry from a parent_id column with a different name
|
|
21
43
|
* documentation fixes for better diagrams and grammar (thx @dtamais, @d-m-u, and @CamilleDrapier)
|
|
22
44
|
|
|
23
|
-
##
|
|
45
|
+
## Version [3.1.0] <sub><sup>2020-08-03</sub></sup>
|
|
24
46
|
|
|
25
47
|
* `:primary_key_format` method lets you change syntax. good for uuids.
|
|
26
48
|
* changed code from being `ancestry` string to `ancestry_ids` focused. May break monkey patches.
|
|
@@ -29,16 +51,16 @@ a nice looking [Changelog](http://keepachangelog.com).
|
|
|
29
51
|
* Better documentation for relationships (thnx @dtamai and @d-m-u)
|
|
30
52
|
* Fix creating children in `after_*` callbacks (thx @jstirk)
|
|
31
53
|
|
|
32
|
-
## Version [3.0.7] <
|
|
54
|
+
## Version [3.0.7] <sub><sup>2018-11-06</sub></sup>
|
|
33
55
|
|
|
34
56
|
* Fixed rails 5.1 change detection (thx @jrafanie)
|
|
35
57
|
* Introduce counter cache (thx @hw676018683)
|
|
36
58
|
|
|
37
|
-
## Version [3.0.6] <
|
|
59
|
+
## Version [3.0.6] <sub><sup>2018-11-06</sub></sup>
|
|
38
60
|
|
|
39
61
|
* Fixed rails 4.1 version check (thx @myxoh)
|
|
40
62
|
|
|
41
|
-
## Version [3.0.5] <
|
|
63
|
+
## Version [3.0.5] <sub><sup>2018-11-06</sub></sup>
|
|
42
64
|
|
|
43
65
|
## Changed
|
|
44
66
|
|
|
@@ -49,14 +71,14 @@ a nice looking [Changelog](http://keepachangelog.com).
|
|
|
49
71
|
|
|
50
72
|
* Reduced memory footprint of parsing ancestry column (thx @NickLaMuro)
|
|
51
73
|
|
|
52
|
-
## Version [3.0.4] <
|
|
74
|
+
## Version [3.0.4] <sub><sup>2018-10-27</sub></sup>
|
|
53
75
|
|
|
54
76
|
## Fixes
|
|
55
77
|
|
|
56
78
|
* Properly detects non-integer columns (thx @adam101)
|
|
57
79
|
* Arrange no longer drops nodes due to missing parents (thx @trafium)
|
|
58
80
|
|
|
59
|
-
## Version [3.0.3] <
|
|
81
|
+
## Version [3.0.3] <sub><sup>2018-10-23</sub></sup>
|
|
60
82
|
|
|
61
83
|
This branch (3.x) should still be compatible with rails 3 and 4.
|
|
62
84
|
Rails 5.1 and 5.2 support were introduced in this version, but ongoing support
|
|
@@ -72,7 +94,7 @@ has been moved to ancestry 4.0
|
|
|
72
94
|
* Dropped builds for ruby 1.9.3, 2.0, 2.1, and 2.2
|
|
73
95
|
* Dropped builds for Rails 3.x and 4.x (will use Active Record `or` syntax)
|
|
74
96
|
|
|
75
|
-
## Version [3.0.2] <
|
|
97
|
+
## Version [3.0.2] <sub><sup>2018-04-24</sub></sup>
|
|
76
98
|
|
|
77
99
|
## Fixes
|
|
78
100
|
|
|
@@ -82,7 +104,7 @@ has been moved to ancestry 4.0
|
|
|
82
104
|
* added missing `Ancestry::version`
|
|
83
105
|
* added Rails 5.2 support (thx @jjuliano)
|
|
84
106
|
|
|
85
|
-
## Version [3.0.1] <
|
|
107
|
+
## Version [3.0.1] <sub><sup>2017-07-05</sub></sup>
|
|
86
108
|
|
|
87
109
|
## Fixes
|
|
88
110
|
|
|
@@ -93,7 +115,7 @@ has been moved to ancestry 4.0
|
|
|
93
115
|
* fixed tests on mysql 5.7 and rails 3.2
|
|
94
116
|
* Dropped 3.1 scope changes
|
|
95
117
|
|
|
96
|
-
## Version [3.0.0] <
|
|
118
|
+
## Version [3.0.0] <sub><sup>2017-05-18</sub></sup>
|
|
97
119
|
|
|
98
120
|
## Changed
|
|
99
121
|
|
|
@@ -109,7 +131,7 @@ has been moved to ancestry 4.0
|
|
|
109
131
|
* Properly touches parents when different class for STI (thx @samtgarson)
|
|
110
132
|
* Fixed issues with parent_id (only present on master) (thx @domcleal)
|
|
111
133
|
|
|
112
|
-
## Version [2.2.2] <
|
|
134
|
+
## Version [2.2.2] <sub><sup>2016-11-01</sub></sup>
|
|
113
135
|
|
|
114
136
|
### Changed
|
|
115
137
|
|
|
@@ -117,7 +139,7 @@ has been moved to ancestry 4.0
|
|
|
117
139
|
* Fixed bug with explicit order clauses (introduced in 2.2.0)
|
|
118
140
|
* No longer load schema on `has_ancestry` load (thx @ledermann)
|
|
119
141
|
|
|
120
|
-
## Version [2.2.1] <
|
|
142
|
+
## Version [2.2.1] <sub><sup>2016-10-25</sub></sup>
|
|
121
143
|
|
|
122
144
|
Sorry for blip, local master got out of sync with upstream master.
|
|
123
145
|
Missed 2 commits (which are feature adds)
|
|
@@ -126,7 +148,7 @@ Missed 2 commits (which are feature adds)
|
|
|
126
148
|
* Use like (vs ilike) for rails 5.0 (performance enhancement)
|
|
127
149
|
* Use `COALESCE` for sorting on pg, mysql, and sqlite vs `CASE`
|
|
128
150
|
|
|
129
|
-
## Version [2.2.0] <
|
|
151
|
+
## Version [2.2.0] <sub><sup>2016-10-25</sub></sup>
|
|
130
152
|
|
|
131
153
|
### Added
|
|
132
154
|
* Predicates for scopes: e.g.: `ancestor_of?`, `parent_of?` (thx @neglectedvalue)
|
|
@@ -141,7 +163,7 @@ Missed 2 commits (which are feature adds)
|
|
|
141
163
|
* Upgrading tests for ruby versions (thx @brocktimus, @fryguy, @yui-knk)
|
|
142
164
|
* Fix non-default ancestry not getting used properly (thx @javiyu)
|
|
143
165
|
|
|
144
|
-
## Version [2.1.0]
|
|
166
|
+
## Version [2.1.0] <sub><sup>2014-04-16</sub></sup>
|
|
145
167
|
* Added arrange_serializable (thx @krishandley, @chicagogrrl)
|
|
146
168
|
* Add the :touch to update ancestors on save (thx @adammck)
|
|
147
169
|
* Change conditions into arel (thx @mlitwiniuk)
|
|
@@ -150,7 +172,7 @@ Missed 2 commits (which are feature adds)
|
|
|
150
172
|
* Performance tweak (thx @mjc)
|
|
151
173
|
* Improvements to organization (thx @xsuchy, @ryakh)
|
|
152
174
|
|
|
153
|
-
## Version [2.0.0] <
|
|
175
|
+
## Version [2.0.0] <sub><sup>2013-05-17</sub></sup>
|
|
154
176
|
* Removed rails 2 compatibility
|
|
155
177
|
* Added table name to condition constructing methods (thx @aflatter)
|
|
156
178
|
* Fix depth_cache not being updated when moving up to ancestors (thx @scottatron)
|
|
@@ -162,31 +184,31 @@ Missed 2 commits (which are feature adds)
|
|
|
162
184
|
* New adopt strategy (thx unknown)
|
|
163
185
|
* Many more improvements
|
|
164
186
|
|
|
165
|
-
## Version [1.3.0] <
|
|
187
|
+
## Version [1.3.0] <sub><sup>2012-05-04</sub></sup>
|
|
166
188
|
* Ancestry now ignores default scopes when moving or destroying nodes, ensuring tree consistency
|
|
167
189
|
* Changed ActiveRecord dependency to 2.3.14
|
|
168
190
|
|
|
169
|
-
## Version [1.2.5] <
|
|
191
|
+
## Version [1.2.5] <sub><sup>2012-03-15</sub></sup>
|
|
170
192
|
* Fixed warnings: "parenthesize argument(s) for future version"
|
|
171
193
|
* Fixed a bug in the restore_ancestry_integrity! method (thx Arthur Holstvoogd)
|
|
172
194
|
|
|
173
|
-
## Version [1.2.4] <
|
|
195
|
+
## Version [1.2.4] <sub><sup>2011-04-22</sub></sup>
|
|
174
196
|
* Prepended table names to column names in queries (thx @raelik)
|
|
175
197
|
* Better check to see if acts_as_tree can be overloaded (thx @jims)
|
|
176
198
|
* Performance inprovements (thx @kueda)
|
|
177
199
|
|
|
178
|
-
## Version [1.2.3] <
|
|
200
|
+
## Version [1.2.3] <sub><sup>2010-10-28</sub></sup>
|
|
179
201
|
* Fixed error with determining ActiveRecord version
|
|
180
202
|
* Added option to specify :primary_key_format (thx @rolftimmermans)
|
|
181
203
|
|
|
182
|
-
## Version [1.2.2] <
|
|
204
|
+
## Version [1.2.2] <sub><sup>2010-10-24</sub></sup>
|
|
183
205
|
* Fixed all deprecation warnings for rails 3.0.X
|
|
184
206
|
* Added `:report` option to `check_ancestry_integrity!`
|
|
185
207
|
* Changed ActiveRecord dependency to 2.2.2
|
|
186
208
|
* Tested and fixed for ruby 1.8.7 and 1.9.2
|
|
187
209
|
* Changed usage of `update_attributes` to `update_attribute` to allow ancestry column protection
|
|
188
210
|
|
|
189
|
-
## Version [1.2.0] <
|
|
211
|
+
## Version [1.2.0] <sub><sup>2009-11-07</sub></sup>
|
|
190
212
|
* Removed some duplication in has_ancestry
|
|
191
213
|
* Cleaned up plugin pattern according to http://yehudakatz.com/2009/11/12/better-ruby-idioms/
|
|
192
214
|
* Moved parts of ancestry into seperate files
|
|
@@ -197,23 +219,23 @@ Missed 2 commits (which are feature adds)
|
|
|
197
219
|
* Updated ordered_by_ancestry scope to support Microsoft SQL Server
|
|
198
220
|
* Added empty hash as parameter to exists? calls for older ActiveRecord versions
|
|
199
221
|
|
|
200
|
-
## Version [1.1.4] <
|
|
222
|
+
## Version [1.1.4] <sub><sup>2009-11-07</sub></sup>
|
|
201
223
|
* Thanks to a patch from tom taylor, Ancestry now works with different primary keys
|
|
202
224
|
|
|
203
|
-
## Version [1.1.3] <
|
|
225
|
+
## Version [1.1.3] <sub><sup>2009-11-01</sub></sup>
|
|
204
226
|
* Fixed a pretty bad bug where several operations took far too many queries
|
|
205
227
|
|
|
206
|
-
## Version [1.1.2] <
|
|
228
|
+
## Version [1.1.2] <sub><sup>2009-10-29</sub></sup>
|
|
207
229
|
* Added validation for depth cache column
|
|
208
230
|
* Added STI support (reported broken)
|
|
209
231
|
|
|
210
|
-
## Version [1.1.1] <
|
|
232
|
+
## Version [1.1.1] <sub><sup>2009-10-28</sub></sup>
|
|
211
233
|
* Fixed some parentheses warnings that where reported
|
|
212
234
|
* Fixed a reported issue with arrangement
|
|
213
235
|
* Fixed issues with ancestors and path order on postgres
|
|
214
236
|
* Added ordered_by_ancestry scope (needed to fix issues)
|
|
215
237
|
|
|
216
|
-
## Version [1.1.0] <
|
|
238
|
+
## Version [1.1.0] <sub><sup>2009-10-22</sub></sup>
|
|
217
239
|
* Depth caching (and cache rebuilding)
|
|
218
240
|
* Depth method for nodes
|
|
219
241
|
* Named scopes for selecting by depth
|
|
@@ -236,7 +258,7 @@ Missed 2 commits (which are feature adds)
|
|
|
236
258
|
* Removed rails specific init
|
|
237
259
|
* Removed uninstall script
|
|
238
260
|
|
|
239
|
-
## Version 1.0.0 <
|
|
261
|
+
## Version 1.0.0 <sub><sup>2009-10-16</sub></sup>
|
|
240
262
|
* Initial version
|
|
241
263
|
* Tree building
|
|
242
264
|
* Tree navigation
|
|
@@ -248,8 +270,12 @@ Missed 2 commits (which are feature adds)
|
|
|
248
270
|
* Validations
|
|
249
271
|
|
|
250
272
|
|
|
251
|
-
[HEAD]: https://github.com/stefankroes/ancestry/compare/
|
|
252
|
-
[
|
|
273
|
+
[HEAD]: https://github.com/stefankroes/ancestry/compare/v4.2.0...HEAD
|
|
274
|
+
[4.2.0]: https://github.com/stefankroes/ancestry/compare/v4.1.0...v4.2.0
|
|
275
|
+
[4.1.0]: https://github.com/stefankroes/ancestry/compare/v4.0.0...v4.1.0
|
|
276
|
+
[4.0.0]: https://github.com/stefankroes/ancestry/compare/v3.2.1...v4.0.0
|
|
277
|
+
[3.2.1]: https://github.com/stefankroes/ancestry/compare/v3.2.0...v3.2.1
|
|
278
|
+
[3.2.0]: https://github.com/stefankroes/ancestry/compare/v3.1.0...v3.2.0
|
|
253
279
|
[3.1.0]: https://github.com/stefankroes/ancestry/compare/v3.0.7...v3.1.0
|
|
254
280
|
[3.0.7]: https://github.com/stefankroes/ancestry/compare/v3.0.6...v3.0.7
|
|
255
281
|
[3.0.6]: https://github.com/stefankroes/ancestry/compare/v3.0.5...v3.0.6
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](https://gitter.im/stefankroes/ancestry?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
2
2
|
|
|
3
3
|
# Ancestry
|
|
4
4
|
|
|
@@ -44,6 +44,8 @@ $ bundle install
|
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
$ rails g migration add_ancestry_to_[table] ancestry:string:index
|
|
47
|
+
# or use different column name of your choosing. e.g. name:
|
|
48
|
+
# rails g migration add_name_to_[people] name:string:index
|
|
47
49
|
```
|
|
48
50
|
|
|
49
51
|
* Migrate your database:
|
|
@@ -52,6 +54,20 @@ $ rails g migration add_ancestry_to_[table] ancestry:string:index
|
|
|
52
54
|
$ rake db:migrate
|
|
53
55
|
```
|
|
54
56
|
|
|
57
|
+
Depending upon your comfort with databases, you may want to create the column
|
|
58
|
+
with `C` or `POSIX` encoding. This is a more primitive encoding and just compares
|
|
59
|
+
bytes. Since this column will just contains numbers and slashes, it works much
|
|
60
|
+
better. It also works better for the uuid case as well.
|
|
61
|
+
|
|
62
|
+
Alternatively, if you create a [`text_pattern_ops`](https://www.postgresql.org/docs/current/indexes-opclass.html) index for your postgresql column, subtree selection will use an efficient index for you regardless of whether you created the column with `POSIX` encoding.
|
|
63
|
+
|
|
64
|
+
If you opt out of this, and are trying to run tests on postgres, you may need to
|
|
65
|
+
set the environment variable `COLLATE_SYMBOLS=false`. Sorry to say that a discussion
|
|
66
|
+
on this topic is out of scope. The important take away is postgres sort order is
|
|
67
|
+
not consistent across operating systems but other databases do not have this same
|
|
68
|
+
issue.
|
|
69
|
+
|
|
70
|
+
NOTE: A Btree index (as is recommended) has a limitaton of 2704 characters for the ancestry column. This means you can't have an tree with a depth that is too great (~> 900 items at most).
|
|
55
71
|
|
|
56
72
|
## Add ancestry to your model
|
|
57
73
|
* Add to app/models/[model.rb]:
|
|
@@ -60,7 +76,8 @@ $ rake db:migrate
|
|
|
60
76
|
# app/models/[model.rb]
|
|
61
77
|
|
|
62
78
|
class [Model] < ActiveRecord::Base
|
|
63
|
-
has_ancestry
|
|
79
|
+
has_ancestry # or alternatively as below:
|
|
80
|
+
# has_ancestry ancestry_column: :name ## if you've used a different column name
|
|
64
81
|
end
|
|
65
82
|
```
|
|
66
83
|
|
|
@@ -132,6 +149,9 @@ The has_ancestry method supports the following options:
|
|
|
132
149
|
By default, primary keys only match integers ([0-9]+)
|
|
133
150
|
:touch Instruct Ancestry to touch the ancestors of a node when it changes, to
|
|
134
151
|
invalidate nested key-based caches. (default: false)
|
|
152
|
+
:counter_cache Boolean whether to create counter cache column accessor.
|
|
153
|
+
Default column name is `children_count`.
|
|
154
|
+
Pass symbol to use different column name (default: false)
|
|
135
155
|
|
|
136
156
|
# (Named) Scopes
|
|
137
157
|
|
|
@@ -16,7 +16,7 @@ module Ancestry
|
|
|
16
16
|
if [:before_depth, :to_depth, :at_depth, :from_depth, :after_depth].include? scope_name
|
|
17
17
|
scope.send scope_name, depth + relative_depth
|
|
18
18
|
else
|
|
19
|
-
raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_depth_option",
|
|
19
|
+
raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_depth_option", scope_name: scope_name))
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -74,6 +74,20 @@ module Ancestry
|
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
def tree_view(column, data = nil)
|
|
78
|
+
data = arrange unless data
|
|
79
|
+
data.each do |parent, children|
|
|
80
|
+
if parent.depth == 0
|
|
81
|
+
puts parent[column]
|
|
82
|
+
else
|
|
83
|
+
num = parent.depth - 1
|
|
84
|
+
indent = " "*num
|
|
85
|
+
puts " #{"|" if parent.depth > 1}#{indent}|_ #{parent[column]}"
|
|
86
|
+
end
|
|
87
|
+
tree_view(column, children) if children
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
77
91
|
# Pseudo-preordered array of nodes. Children will always follow parents,
|
|
78
92
|
def sort_by_ancestry(nodes, &block)
|
|
79
93
|
arranged = nodes if nodes.is_a?(Hash)
|
|
@@ -217,31 +231,15 @@ module Ancestry
|
|
|
217
231
|
end
|
|
218
232
|
|
|
219
233
|
def unscoped_where
|
|
220
|
-
|
|
221
|
-
self.ancestry_base_class.unscoped do
|
|
222
|
-
yield self.ancestry_base_class
|
|
223
|
-
end
|
|
224
|
-
else
|
|
225
|
-
yield self.ancestry_base_class.unscope(:where)
|
|
226
|
-
end
|
|
234
|
+
yield self.ancestry_base_class.default_scoped.unscope(:where)
|
|
227
235
|
end
|
|
228
236
|
|
|
229
237
|
ANCESTRY_UNCAST_TYPES = [:string, :uuid, :text].freeze
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
@primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(columns_hash[primary_key.to_s].type)
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
else
|
|
239
|
-
def primary_key_is_an_integer?
|
|
240
|
-
if defined?(@primary_key_is_an_integer)
|
|
241
|
-
@primary_key_is_an_integer
|
|
242
|
-
else
|
|
243
|
-
@primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(type_for_attribute(primary_key).type)
|
|
244
|
-
end
|
|
238
|
+
def primary_key_is_an_integer?
|
|
239
|
+
if defined?(@primary_key_is_an_integer)
|
|
240
|
+
@primary_key_is_an_integer
|
|
241
|
+
else
|
|
242
|
+
@primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(type_for_attribute(primary_key).type)
|
|
245
243
|
end
|
|
246
244
|
end
|
|
247
245
|
end
|
|
@@ -4,8 +4,8 @@ module Ancestry
|
|
|
4
4
|
# Check options
|
|
5
5
|
raise Ancestry::AncestryException.new(I18n.t("ancestry.option_must_be_hash")) unless options.is_a? Hash
|
|
6
6
|
options.each do |key, value|
|
|
7
|
-
unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch, :counter_cache, :primary_key_format, :update_strategy].include? key
|
|
8
|
-
raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_option",
|
|
7
|
+
unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch, :counter_cache, :primary_key_format, :update_strategy, :strategy].include? key
|
|
8
|
+
raise Ancestry::AncestryException.new(I18n.t("ancestry.unknown_option", key: key.inspect, value: value.inspect))
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -27,8 +27,13 @@ module Ancestry
|
|
|
27
27
|
# Include dynamic class methods
|
|
28
28
|
extend Ancestry::ClassMethods
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
if options[:strategy] == :materialized_path2
|
|
31
|
+
validates_format_of self.ancestry_column, :with => derive_materialized2_pattern(options[:primary_key_format]), :allow_nil => false
|
|
32
|
+
extend Ancestry::MaterializedPath2
|
|
33
|
+
else
|
|
34
|
+
validates_format_of self.ancestry_column, :with => derive_materialized_pattern(options[:primary_key_format]), :allow_nil => true
|
|
35
|
+
extend Ancestry::MaterializedPath
|
|
36
|
+
end
|
|
32
37
|
|
|
33
38
|
update_strategy = options[:update_strategy] || Ancestry.default_update_strategy
|
|
34
39
|
include Ancestry::MaterializedPathPg if update_strategy == :sql
|
|
@@ -87,12 +92,7 @@ module Ancestry
|
|
|
87
92
|
|
|
88
93
|
after_touch :touch_ancestors_callback
|
|
89
94
|
after_destroy :touch_ancestors_callback
|
|
90
|
-
|
|
91
|
-
if ActiveRecord::VERSION::STRING >= '5.1.0'
|
|
92
|
-
after_save :touch_ancestors_callback, if: :saved_changes?
|
|
93
|
-
else
|
|
94
|
-
after_save :touch_ancestors_callback, if: :changed?
|
|
95
|
-
end
|
|
95
|
+
after_save :touch_ancestors_callback, if: :saved_changes?
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def acts_as_tree(*args)
|
|
@@ -102,7 +102,7 @@ module Ancestry
|
|
|
102
102
|
|
|
103
103
|
private
|
|
104
104
|
|
|
105
|
-
def
|
|
105
|
+
def derive_materialized_pattern(primary_key_format, delimiter = '/')
|
|
106
106
|
primary_key_format ||= '[0-9]+'
|
|
107
107
|
|
|
108
108
|
if primary_key_format.to_s.include?('\A')
|
|
@@ -111,9 +111,19 @@ module Ancestry
|
|
|
111
111
|
/\A#{primary_key_format}(#{delimiter}#{primary_key_format})*\Z/
|
|
112
112
|
end
|
|
113
113
|
end
|
|
114
|
+
|
|
115
|
+
def derive_materialized2_pattern(primary_key_format, delimiter = '/')
|
|
116
|
+
primary_key_format ||= '[0-9]+'
|
|
117
|
+
|
|
118
|
+
if primary_key_format.to_s.include?('\A')
|
|
119
|
+
primary_key_format
|
|
120
|
+
else
|
|
121
|
+
/\A#{delimiter}(#{primary_key_format}#{delimiter})*\Z/
|
|
122
|
+
end
|
|
123
|
+
end
|
|
114
124
|
end
|
|
115
125
|
end
|
|
116
126
|
|
|
117
127
|
ActiveSupport.on_load :active_record do
|
|
118
|
-
|
|
128
|
+
extend Ancestry::HasAncestry
|
|
119
129
|
end
|
|
@@ -2,13 +2,13 @@ module Ancestry
|
|
|
2
2
|
module InstanceMethods
|
|
3
3
|
# Validate that the ancestors don't include itself
|
|
4
4
|
def ancestry_exclude_self
|
|
5
|
-
errors.add(:base, I18n.t("ancestry.exclude_self",
|
|
5
|
+
errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.name.humanize)) if ancestor_ids.include? self.id
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
# Update descendants with new ancestry (before save)
|
|
9
9
|
def update_descendants_with_new_ancestry
|
|
10
10
|
# If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
|
|
11
|
-
if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? &&
|
|
11
|
+
if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids?
|
|
12
12
|
# ... for each descendant ...
|
|
13
13
|
unscoped_descendants.each do |descendant|
|
|
14
14
|
# ... replace old ancestry with new ancestry
|
|
@@ -39,7 +39,7 @@ module Ancestry
|
|
|
39
39
|
when :adopt # make child elements of this node, child of its parent
|
|
40
40
|
descendants.each do |descendant|
|
|
41
41
|
descendant.without_ancestry_callbacks do
|
|
42
|
-
descendant.update_attribute :ancestor_ids, descendant.ancestor_ids.delete_if { |x| x == self.id }
|
|
42
|
+
descendant.update_attribute :ancestor_ids, (descendant.ancestor_ids.delete_if { |x| x == self.id })
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
when :restrict # throw an exception if it has children
|
|
@@ -62,7 +62,7 @@ module Ancestry
|
|
|
62
62
|
|
|
63
63
|
# Counter Cache
|
|
64
64
|
def increase_parent_counter_cache
|
|
65
|
-
self.
|
|
65
|
+
self.ancestry_base_class.increment_counter _counter_cache_column, parent_id
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def decrease_parent_counter_cache
|
|
@@ -74,24 +74,19 @@ module Ancestry
|
|
|
74
74
|
return if defined?(@_trigger_destroy_callback) && !@_trigger_destroy_callback
|
|
75
75
|
return if ancestry_callbacks_disabled?
|
|
76
76
|
|
|
77
|
-
self.
|
|
77
|
+
self.ancestry_base_class.decrement_counter _counter_cache_column, parent_id
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def update_parent_counter_cache
|
|
81
|
-
changed =
|
|
82
|
-
if ActiveRecord::VERSION::STRING >= '5.1.0'
|
|
83
|
-
saved_change_to_attribute?(self.ancestry_base_class.ancestry_column)
|
|
84
|
-
else
|
|
85
|
-
ancestry_changed?
|
|
86
|
-
end
|
|
81
|
+
changed = saved_change_to_attribute?(self.ancestry_base_class.ancestry_column)
|
|
87
82
|
|
|
88
83
|
return unless changed
|
|
89
84
|
|
|
90
85
|
if parent_id_was = parent_id_before_last_save
|
|
91
|
-
self.
|
|
86
|
+
self.ancestry_base_class.decrement_counter _counter_cache_column, parent_id_was
|
|
92
87
|
end
|
|
93
88
|
|
|
94
|
-
parent_id && self.
|
|
89
|
+
parent_id && self.ancestry_base_class.increment_counter(_counter_cache_column, parent_id)
|
|
95
90
|
end
|
|
96
91
|
|
|
97
92
|
def _counter_cache_column
|
|
@@ -100,28 +95,37 @@ module Ancestry
|
|
|
100
95
|
|
|
101
96
|
# Ancestors
|
|
102
97
|
|
|
103
|
-
def
|
|
98
|
+
def has_parent?
|
|
104
99
|
ancestor_ids.present?
|
|
105
100
|
end
|
|
106
|
-
alias :
|
|
101
|
+
alias :ancestors? :has_parent?
|
|
107
102
|
|
|
108
103
|
def ancestry_changed?
|
|
109
104
|
column = self.ancestry_base_class.ancestry_column.to_s
|
|
110
|
-
if ActiveRecord::VERSION::STRING >= '5.1.0'
|
|
111
105
|
# These methods return nil if there are no changes.
|
|
112
106
|
# This was fixed in a refactoring in rails 6.0: https://github.com/rails/rails/pull/35933
|
|
113
107
|
!!(will_save_change_to_attribute?(column) || saved_change_to_attribute?(column))
|
|
114
|
-
else
|
|
115
|
-
changed.include?(column)
|
|
116
|
-
end
|
|
117
108
|
end
|
|
118
109
|
|
|
119
110
|
def sane_ancestor_ids?
|
|
120
|
-
|
|
111
|
+
current_context, self.validation_context = validation_context, nil
|
|
112
|
+
errors.clear
|
|
113
|
+
|
|
114
|
+
attribute = ancestry_base_class.ancestry_column
|
|
115
|
+
ancestry_value = send(attribute)
|
|
116
|
+
return true unless ancestry_value
|
|
117
|
+
|
|
118
|
+
self.class.validators_on(attribute).each do |validator|
|
|
119
|
+
validator.validate_each(self, attribute, ancestry_value)
|
|
120
|
+
end
|
|
121
|
+
ancestry_exclude_self
|
|
122
|
+
errors.none?
|
|
123
|
+
ensure
|
|
124
|
+
self.validation_context = current_context
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def ancestors depth_options = {}
|
|
124
|
-
return self.ancestry_base_class.none unless
|
|
128
|
+
return self.ancestry_base_class.none unless has_parent?
|
|
125
129
|
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
|
|
126
130
|
end
|
|
127
131
|
|
|
@@ -162,12 +166,16 @@ module Ancestry
|
|
|
162
166
|
end
|
|
163
167
|
|
|
164
168
|
def parent_id
|
|
165
|
-
ancestor_ids.last if
|
|
169
|
+
ancestor_ids.last if has_parent?
|
|
166
170
|
end
|
|
167
171
|
alias :parent_id? :ancestors?
|
|
168
172
|
|
|
169
173
|
def parent
|
|
170
|
-
|
|
174
|
+
if has_parent?
|
|
175
|
+
unscoped_where do |scope|
|
|
176
|
+
scope.find_by scope.primary_key => parent_id
|
|
177
|
+
end
|
|
178
|
+
end
|
|
171
179
|
end
|
|
172
180
|
|
|
173
181
|
def parent_of?(node)
|
|
@@ -177,15 +185,19 @@ module Ancestry
|
|
|
177
185
|
# Root
|
|
178
186
|
|
|
179
187
|
def root_id
|
|
180
|
-
|
|
188
|
+
has_parent? ? ancestor_ids.first : id
|
|
181
189
|
end
|
|
182
190
|
|
|
183
191
|
def root
|
|
184
|
-
|
|
192
|
+
if has_parent?
|
|
193
|
+
unscoped_where { |scope| scope.find_by(id: root_id) } || self
|
|
194
|
+
else
|
|
195
|
+
self
|
|
196
|
+
end
|
|
185
197
|
end
|
|
186
198
|
|
|
187
199
|
def is_root?
|
|
188
|
-
!
|
|
200
|
+
!has_parent?
|
|
189
201
|
end
|
|
190
202
|
alias :root? :is_root?
|
|
191
203
|
|
|
@@ -285,6 +297,7 @@ module Ancestry
|
|
|
285
297
|
def without_ancestry_callbacks
|
|
286
298
|
@disable_ancestry_callbacks = true
|
|
287
299
|
yield
|
|
300
|
+
ensure
|
|
288
301
|
@disable_ancestry_callbacks = false
|
|
289
302
|
end
|
|
290
303
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
module Ancestry
|
|
2
|
+
# store ancestry as grandparent_id/parent_id
|
|
3
|
+
# root a=nil,id=1 children=id,id/% == 1, 1/%
|
|
4
|
+
# 3: a=1/2,id=3 children=a/id,a/id/% == 1/2/3, 1/2/3/%
|
|
2
5
|
module MaterializedPath
|
|
3
|
-
BEFORE_LAST_SAVE_SUFFIX =
|
|
4
|
-
IN_DATABASE_SUFFIX =
|
|
6
|
+
BEFORE_LAST_SAVE_SUFFIX = '_before_last_save'.freeze
|
|
7
|
+
IN_DATABASE_SUFFIX = '_in_database'.freeze
|
|
5
8
|
ANCESTRY_DELIMITER='/'.freeze
|
|
9
|
+
ROOT=nil
|
|
6
10
|
|
|
7
11
|
def self.extended(base)
|
|
8
12
|
base.send(:include, InstanceMethods)
|
|
@@ -13,7 +17,7 @@ module Ancestry
|
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def roots
|
|
16
|
-
where(arel_table[ancestry_column].eq(
|
|
20
|
+
where(arel_table[ancestry_column].eq(ROOT))
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def ancestors_of(object)
|
|
@@ -38,34 +42,25 @@ module Ancestry
|
|
|
38
42
|
def indirects_of(object)
|
|
39
43
|
t = arel_table
|
|
40
44
|
node = to_node(object)
|
|
41
|
-
#
|
|
42
|
-
if ActiveRecord::VERSION::MAJOR >= 5
|
|
43
|
-
where(t[ancestry_column].matches("#{node.child_ancestry}/%", nil, true))
|
|
44
|
-
else
|
|
45
|
-
where(t[ancestry_column].matches("#{node.child_ancestry}/%"))
|
|
46
|
-
end
|
|
45
|
+
where(t[ancestry_column].matches("#{node.child_ancestry}#{ANCESTRY_DELIMITER}%", nil, true))
|
|
47
46
|
end
|
|
48
47
|
|
|
49
48
|
def descendants_of(object)
|
|
50
|
-
|
|
49
|
+
node = to_node(object)
|
|
50
|
+
indirects_of(node).or(children_of(node))
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
# deprecated
|
|
54
54
|
def descendant_conditions(object)
|
|
55
55
|
t = arel_table
|
|
56
56
|
node = to_node(object)
|
|
57
|
-
#
|
|
58
|
-
if ActiveRecord::VERSION::MAJOR >= 5
|
|
59
|
-
t[ancestry_column].matches("#{node.child_ancestry}/%", nil, true).or(t[ancestry_column].eq(node.child_ancestry))
|
|
60
|
-
else
|
|
61
|
-
t[ancestry_column].matches("#{node.child_ancestry}/%").or(t[ancestry_column].eq(node.child_ancestry))
|
|
62
|
-
end
|
|
57
|
+
t[ancestry_column].matches("#{node.child_ancestry}/%", nil, true).or(t[ancestry_column].eq(node.child_ancestry))
|
|
63
58
|
end
|
|
64
59
|
|
|
65
60
|
def subtree_of(object)
|
|
66
61
|
t = arel_table
|
|
67
62
|
node = to_node(object)
|
|
68
|
-
|
|
63
|
+
descendants_of(node).or(where(t[primary_key].eq(node.id)))
|
|
69
64
|
end
|
|
70
65
|
|
|
71
66
|
def siblings_of(object)
|
|
@@ -77,8 +72,8 @@ module Ancestry
|
|
|
77
72
|
def ordered_by_ancestry(order = nil)
|
|
78
73
|
if %w(mysql mysql2 sqlite sqlite3).include?(connection.adapter_name.downcase)
|
|
79
74
|
reorder(arel_table[ancestry_column], order)
|
|
80
|
-
elsif %w(postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::STRING >= "6.1"
|
|
81
|
-
reorder(Arel::Nodes::Ascending.new(arel_table[ancestry_column]).nulls_first)
|
|
75
|
+
elsif %w(postgresql oracleenhanced).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::STRING >= "6.1"
|
|
76
|
+
reorder(Arel::Nodes::Ascending.new(arel_table[ancestry_column]).nulls_first, order)
|
|
82
77
|
else
|
|
83
78
|
reorder(
|
|
84
79
|
Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
|
|
@@ -92,22 +87,15 @@ module Ancestry
|
|
|
92
87
|
end
|
|
93
88
|
|
|
94
89
|
module InstanceMethods
|
|
95
|
-
|
|
96
|
-
# Validates the ancestry, but can also be applied if validation is bypassed to determine if children should be affected
|
|
97
|
-
def sane_ancestry?
|
|
98
|
-
ancestry_value = read_attribute(self.ancestry_base_class.ancestry_column)
|
|
99
|
-
(ancestry_value.nil? || !ancestor_ids.include?(self.id)) && valid?
|
|
100
|
-
end
|
|
101
|
-
|
|
102
90
|
# optimization - better to go directly to column and avoid parsing
|
|
103
91
|
def ancestors?
|
|
104
|
-
read_attribute(self.ancestry_base_class.ancestry_column)
|
|
92
|
+
read_attribute(self.ancestry_base_class.ancestry_column) != ROOT
|
|
105
93
|
end
|
|
106
94
|
alias :has_parent? :ancestors?
|
|
107
95
|
|
|
108
96
|
def ancestor_ids=(value)
|
|
109
97
|
col = self.ancestry_base_class.ancestry_column
|
|
110
|
-
value.present? ? write_attribute(col, value
|
|
98
|
+
value.present? ? write_attribute(col, generate_ancestry(value)) : write_attribute(col, ROOT)
|
|
111
99
|
end
|
|
112
100
|
|
|
113
101
|
def ancestor_ids
|
|
@@ -124,7 +112,7 @@ module Ancestry
|
|
|
124
112
|
|
|
125
113
|
def parent_id_before_last_save
|
|
126
114
|
ancestry_was = send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}")
|
|
127
|
-
return
|
|
115
|
+
return if ancestry_was == ROOT
|
|
128
116
|
|
|
129
117
|
parse_ancestry_column(ancestry_was).last
|
|
130
118
|
end
|
|
@@ -141,16 +129,18 @@ module Ancestry
|
|
|
141
129
|
# New records cannot have children
|
|
142
130
|
raise Ancestry::AncestryException.new(I18n.t("ancestry.no_child_for_new_record")) if new_record?
|
|
143
131
|
path_was = self.send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}")
|
|
144
|
-
path_was.blank? ? id.to_s : "#{path_was}
|
|
132
|
+
path_was.blank? ? id.to_s : "#{path_was}#{ANCESTRY_DELIMITER}#{id}"
|
|
145
133
|
end
|
|
146
134
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def parse_ancestry_column obj
|
|
150
|
-
return [] unless obj
|
|
135
|
+
def parse_ancestry_column(obj)
|
|
136
|
+
return [] if obj == ROOT
|
|
151
137
|
obj_ids = obj.split(ANCESTRY_DELIMITER)
|
|
152
138
|
self.class.primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
|
|
153
139
|
end
|
|
140
|
+
|
|
141
|
+
def generate_ancestry(ancestor_ids)
|
|
142
|
+
ancestor_ids.join(ANCESTRY_DELIMITER)
|
|
143
|
+
end
|
|
154
144
|
end
|
|
155
145
|
end
|
|
156
146
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Ancestry
|
|
2
|
+
# store ancestry as /grandparent_id/parent_id/
|
|
3
|
+
# root: a=/,id=1 children=a.id/% == /1/%
|
|
4
|
+
# 3: a=/1/2/,id=3 children=a.id/% == /1/2/3/%
|
|
5
|
+
module MaterializedPath2 < MaterializedPath
|
|
6
|
+
def indirects_of(object)
|
|
7
|
+
t = arel_table
|
|
8
|
+
node = to_node(object)
|
|
9
|
+
where(t[ancestry_column].matches("#{node.child_ancestry}%#{ANCESTRY_DELIMITER}%", nil, true))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def subtree_of(object)
|
|
13
|
+
t = arel_table
|
|
14
|
+
node = to_node(object)
|
|
15
|
+
where(descendant_conditions(node).or(t[primary_key].eq(node.id)))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def siblings_of(object)
|
|
19
|
+
t = arel_table
|
|
20
|
+
node = to_node(object)
|
|
21
|
+
where(t[ancestry_column].eq(node[ancestry_column]))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ordered_by_ancestry(order = nil)
|
|
25
|
+
reorder(Arel::Nodes::Ascending.new(arel_table[ancestry_column]), order)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# deprecated
|
|
29
|
+
def descendant_conditions(object)
|
|
30
|
+
t = arel_table
|
|
31
|
+
node = to_node(object)
|
|
32
|
+
t[ancestry_column].matches("#{node.child_ancestry}%", nil, true)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module InstanceMethods
|
|
36
|
+
def child_ancestry
|
|
37
|
+
# New records cannot have children
|
|
38
|
+
raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
|
39
|
+
path_was = self.send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}")
|
|
40
|
+
"#{path_was}#{id}#{ANCESTRY_DELIMITER}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_ancestry_column(obj)
|
|
44
|
+
return [] if obj == ROOT
|
|
45
|
+
obj_ids = obj.split(ANCESTRY_DELIMITER).delete_if(&:blank?)
|
|
46
|
+
self.class.primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def generate_ancestry(ancestor_ids)
|
|
50
|
+
"#{ANCESTRY_DELIMITER}#{ancestor_ids.join(ANCESTRY_DELIMITER)}#{ANCESTRY_DELIMITER}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
module Ancestry
|
|
2
2
|
module MaterializedPathPg
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
# Update descendants with new ancestry (before save)
|
|
4
|
+
def update_descendants_with_new_ancestry
|
|
5
|
+
# If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
|
|
6
|
+
if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestor_ids?
|
|
7
|
+
ancestry_column = ancestry_base_class.ancestry_column
|
|
8
|
+
old_ancestry = path_ids_in_database.join(Ancestry::MaterializedPath::ANCESTRY_DELIMITER)
|
|
9
|
+
new_ancestry = path_ids.join(Ancestry::MaterializedPath::ANCESTRY_DELIMITER)
|
|
10
|
+
update_clause = [
|
|
11
|
+
"#{ancestry_column} = regexp_replace(#{ancestry_column}, '^#{old_ancestry}', '#{new_ancestry}')"
|
|
12
|
+
]
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
if ancestry_base_class.respond_to?(:depth_cache_column) && respond_to?(ancestry_base_class.depth_cache_column)
|
|
15
|
+
depth_cache_column = ancestry_base_class.depth_cache_column.to_s
|
|
16
|
+
update_clause << "#{depth_cache_column} = length(regexp_replace(regexp_replace(ancestry, '^#{old_ancestry}', '#{new_ancestry}'), '\\d', '', 'g')) + 1"
|
|
17
|
+
end
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
unscoped_descendants.update_all update_clause.join(', ')
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
data/lib/ancestry/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ancestry
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 4.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Kroes
|
|
@@ -9,7 +9,7 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2022-06-10 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: activerecord
|
|
@@ -17,14 +17,14 @@ dependencies:
|
|
|
17
17
|
requirements:
|
|
18
18
|
- - ">="
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version:
|
|
20
|
+
version: 5.2.6
|
|
21
21
|
type: :runtime
|
|
22
22
|
prerelease: false
|
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
24
|
requirements:
|
|
25
25
|
- - ">="
|
|
26
26
|
- !ruby/object:Gem::Version
|
|
27
|
-
version:
|
|
27
|
+
version: 5.2.6
|
|
28
28
|
- !ruby/object:Gem::Dependency
|
|
29
29
|
name: appraisal
|
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -67,6 +67,20 @@ dependencies:
|
|
|
67
67
|
- - "~>"
|
|
68
68
|
- !ruby/object:Gem::Version
|
|
69
69
|
version: '13.0'
|
|
70
|
+
- !ruby/object:Gem::Dependency
|
|
71
|
+
name: simplecov
|
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
|
+
requirements:
|
|
74
|
+
- - ">="
|
|
75
|
+
- !ruby/object:Gem::Version
|
|
76
|
+
version: '0'
|
|
77
|
+
type: :development
|
|
78
|
+
prerelease: false
|
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
80
|
+
requirements:
|
|
81
|
+
- - ">="
|
|
82
|
+
- !ruby/object:Gem::Version
|
|
83
|
+
version: '0'
|
|
70
84
|
- !ruby/object:Gem::Dependency
|
|
71
85
|
name: yard
|
|
72
86
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -104,6 +118,7 @@ files:
|
|
|
104
118
|
- lib/ancestry/instance_methods.rb
|
|
105
119
|
- lib/ancestry/locales/en.yml
|
|
106
120
|
- lib/ancestry/materialized_path.rb
|
|
121
|
+
- lib/ancestry/materialized_path2.rb
|
|
107
122
|
- lib/ancestry/materialized_path_pg.rb
|
|
108
123
|
- lib/ancestry/version.rb
|
|
109
124
|
homepage: https://github.com/stefankroes/ancestry
|
|
@@ -123,15 +138,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
123
138
|
requirements:
|
|
124
139
|
- - ">="
|
|
125
140
|
- !ruby/object:Gem::Version
|
|
126
|
-
version: 2.
|
|
141
|
+
version: '2.5'
|
|
127
142
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
143
|
requirements:
|
|
129
144
|
- - ">="
|
|
130
145
|
- !ruby/object:Gem::Version
|
|
131
146
|
version: '0'
|
|
132
147
|
requirements: []
|
|
133
|
-
|
|
134
|
-
rubygems_version: 2.7.6.2
|
|
148
|
+
rubygems_version: 3.3.7
|
|
135
149
|
signing_key:
|
|
136
150
|
specification_version: 4
|
|
137
151
|
summary: Organize ActiveRecord model into a tree structure
|