ancestry 5.0.0 → 5.1.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 +117 -79
- data/README.md +13 -0
- data/lib/ancestry/class_methods.rb +12 -4
- data/lib/ancestry/has_ancestry.rb +9 -2
- data/lib/ancestry/instance_methods.rb +26 -0
- data/lib/ancestry/materialized_path.rb +64 -42
- data/lib/ancestry/materialized_path2.rb +38 -14
- data/lib/ancestry/materialized_path_pg.rb +7 -3
- data/lib/ancestry/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fa438f6ed3ae400ec1783712fb569500f53ce4d419d3c3baa186bdad75307fd8
|
|
4
|
+
data.tar.gz: 61d26886919b74f482e96f9d59cc0fa4131068d61b3316747523c0b315ab57c4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6e238d9fb765a56d60c4bfe9f9ba06c926ffe0bc76155858024914a94a71034827877c3ef17258121e43c85b6a49aea61df8a44021f96b65341ef48fab62426
|
|
7
|
+
data.tar.gz: 7b1ca54b59347311d55375b5703c56134e8d11243a64ade0d5d94071a65311b683f593c8a8daa1aa5386bf2969e3588f3f7876ee2b5520440aff388fdac906bb
|
data/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,28 @@
|
|
|
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 [5.
|
|
6
|
+
## Version [5.1.0] <sub><sup>2026-03-10</sup></sub>
|
|
7
|
+
|
|
8
|
+
* `update_strategy: :sql` now works on all databases (SQLite, MySQL, PostgreSQL) [#715](https://github.com/stefankroes/ancestry/pull/715)
|
|
9
|
+
* Introduce depth constraint validation for subtree moves [#713](https://github.com/stefankroes/ancestry/pull/713) (thx @wilburhimself)
|
|
10
|
+
* Fix: CHANGELOG HTML entity fix [#712](https://github.com/stefankroes/ancestry/pull/712) (thx @biow0lf)
|
|
11
|
+
* Testing Ruby 4.0 support [#709](https://github.com/stefankroes/ancestry/pull/709) (thx @fkmy)
|
|
12
|
+
* Tested against Ruby 2.7–4.0 and Rails 6.0–8.1 [#716](https://github.com/stefankroes/ancestry/pull/716)
|
|
13
|
+
|
|
14
|
+
### Deprecations
|
|
15
|
+
|
|
16
|
+
These class-level accessors still work but are being phased out.
|
|
17
|
+
We are moving configuration into baked method bodies and away from class variables.
|
|
18
|
+
If you depend on reading these at runtime, now is a good time to find alternatives:
|
|
19
|
+
|
|
20
|
+
* `ancestry_column`, `ancestry_delimiter`, `depth_cache_column`, `counter_cache_column`, `touch_ancestors`
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
|
|
24
|
+
* `sort_by_ancestry` and `check_ancestry_integrity!` now delegate to `_sort_by_ancestry` and
|
|
25
|
+
`_check_ancestry_integrity!` with an explicit column parameter. The public API is unchanged.
|
|
26
|
+
|
|
27
|
+
## Version [5.0.0] <sub><sup>2026-02-08</sup></sub>
|
|
7
28
|
|
|
8
29
|
* Fix: `siblings` now excludes self [#710](https://github.com/stefankroes/ancestry/pull/710) (thx @chikamichi)
|
|
9
30
|
* Introduce `orphan_strategy: :none` [#658](https://github.com/stefankroes/ancestry/pull/658)
|
|
@@ -23,20 +44,20 @@ a nice looking [Changelog](http://keepachangelog.com).
|
|
|
23
44
|
* Ruby 3.4 support
|
|
24
45
|
* Rails 8.0 support
|
|
25
46
|
|
|
26
|
-
|
|
47
|
+
### Notable features
|
|
27
48
|
|
|
28
49
|
Depth scopes now work without `cache_depth`. But please only use this in background
|
|
29
50
|
jobs. If you need to do this in the ui, please use `cache_depth`.
|
|
30
51
|
|
|
31
52
|
`build_cache_depth_sql!` is quicker than `build_cache_depth!` (1 query instead of N+1 queries).
|
|
32
53
|
|
|
33
|
-
|
|
54
|
+
### Deprecations
|
|
34
55
|
|
|
35
|
-
|
|
56
|
+
* Option `:depth_cache_column` is going away.
|
|
36
57
|
Please use a single key instead: `cache_depth: :depth_cach_column_name`.
|
|
37
58
|
`cache_depth: true` still defaults to `ancestry_depth`.
|
|
38
59
|
|
|
39
|
-
|
|
60
|
+
### Breaking Changes
|
|
40
61
|
|
|
41
62
|
* `siblings` no longer returns self. This is a bug fix, but does change behavior.
|
|
42
63
|
* Dropped support for Rails < 6.1
|
|
@@ -45,34 +66,34 @@ jobs. If you need to do this in the ui, please use `cache_depth`.
|
|
|
45
66
|
* Options are no longer set via class methods. Using `has_ancestry` is now the only way to enable these features.
|
|
46
67
|
These are all not part of the public API.
|
|
47
68
|
* These are now class level read only accessors
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
* `ancestry_base_class` (introduced 1.1, removed by #633)
|
|
70
|
+
* `ancestry_column` (introduced 1.2, removed by #633)
|
|
71
|
+
* `ancestry_delimiter` (introduced 4.3.0, removed by #633)
|
|
72
|
+
* `depth_cache_column` (introduced 4.3.0, removed by #654)
|
|
52
73
|
* These no longer have any accessors
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
74
|
+
* `ancestry_format` (introduced 4.3.0, removed by #654)
|
|
75
|
+
* `orphan_strategy` (introduced 1.1, removed by #617)
|
|
76
|
+
* `ancestry_primary_key_format` (introduced 4.3.0, removed by #649)
|
|
77
|
+
* `touch_ancestors` (introduced 2.1, removed by TODO)
|
|
57
78
|
* These are seen as internal and may go away:
|
|
58
|
-
|
|
79
|
+
* `apply_orphan_strategy` Please use `orphan_strategy: :none` and a custom `before_destory` instead.
|
|
59
80
|
|
|
60
|
-
## Version [4.3.3] <sub><sup>2023-04-01</sub
|
|
81
|
+
## Version [4.3.3] <sub><sup>2023-04-01</sup></sub>
|
|
61
82
|
|
|
62
83
|
* Fix: sort_by_ancesty with custom ancestry_column [#656](https://github.com/stefankroes/ancestry/pull/656) (thx @mitsuru)
|
|
63
84
|
|
|
64
|
-
## Version [4.3.2] <sub><sup>2023-03-25</sub
|
|
85
|
+
## Version [4.3.2] <sub><sup>2023-03-25</sup></sub>
|
|
65
86
|
|
|
66
87
|
* Fix: added back fields that were removed in #589 [#647](https://github.com/stefankroes/ancestry/pull/647) (thx @rastamhadi)
|
|
67
|
-
|
|
88
|
+
* path_ids_in_database
|
|
68
89
|
|
|
69
|
-
## Version [4.3.1] <sub><sup>2023-03-19</sub
|
|
90
|
+
## Version [4.3.1] <sub><sup>2023-03-19</sup></sub>
|
|
70
91
|
|
|
71
92
|
* Fix: added back fields that were removed in #589 [#637](https://github.com/stefankroes/ancestry/pull/637) (thx @znz)
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
* ancestor_ids_in_database
|
|
94
|
+
* parent_id_in_database
|
|
74
95
|
|
|
75
|
-
## Version [4.3.0] <sub><sup>2023-03-09</sub
|
|
96
|
+
## Version [4.3.0] <sub><sup>2023-03-09</sup></sub>
|
|
76
97
|
|
|
77
98
|
* Fix: materialized_path2 strategy [#597](https://github.com/stefankroes/ancestry/pull/597) (thx @kshnurov)
|
|
78
99
|
* Fix: descendants ancestry is now updated in after_update callbacks [#589](https://github.com/stefankroes/ancestry/pull/589) (thx @kshnurov)
|
|
@@ -84,7 +105,7 @@ jobs. If you need to do this in the ui, please use `cache_depth`.
|
|
|
84
105
|
* ruby 3.2 support [#596](https://github.com/stefankroes/ancestry/pull/596) (thx @petergoldstein)
|
|
85
106
|
* Reduce memory for sort_by_ancestry [#415](https://github.com/stefankroes/ancestry/pull/415)
|
|
86
107
|
|
|
87
|
-
|
|
108
|
+
### Notable features
|
|
88
109
|
|
|
89
110
|
Default configuration values are provided for a few options: `update_strategy`, `ancestry_format`, and `primary_key_format`.
|
|
90
111
|
These can be set in an initializer via `Ancestry.default_{ancestry_format} = value`
|
|
@@ -94,12 +115,12 @@ It shows promise to make the `ancestry` field more sql friendly.
|
|
|
94
115
|
|
|
95
116
|
Both of these are better documented in [the readme](/README.md).
|
|
96
117
|
|
|
97
|
-
|
|
118
|
+
### Breaking changes
|
|
98
119
|
|
|
99
|
-
|
|
120
|
+
* `ancestry_primary_key_format` is now specified or a single key not the whole regular expression.
|
|
100
121
|
We used to accept `/\A[0-9]+(/[0-9]+)*` or `'[0-9]'`, but now we only accept `'[0-9]'`.
|
|
101
122
|
|
|
102
|
-
## Version [4.2.0] <sub><sup>2022-06-09</sub
|
|
123
|
+
## Version [4.2.0] <sub><sup>2022-06-09</sup></sub>
|
|
103
124
|
|
|
104
125
|
* added strategy: materialized_path2 [#571](https://github.com/stefankroes/ancestry/pull/571)
|
|
105
126
|
* Added tree_view method [#561](https://github.com/stefankroes/ancestry/pull/561) (thx @bizcho)
|
|
@@ -108,7 +129,7 @@ Both of these are better documented in [the readme](/README.md).
|
|
|
108
129
|
* rails 7.0 support (thx @chenillen, @petergoldstein)
|
|
109
130
|
* Documentation fixes (thx @benkoshy, @mijoharas)
|
|
110
131
|
|
|
111
|
-
## Version [4.1.0] <sub><sup>2021-06-25</sub
|
|
132
|
+
## Version [4.1.0] <sub><sup>2021-06-25</sup></sub>
|
|
112
133
|
|
|
113
134
|
* `parent` with an invalid id now returns nil (thx @vanboom)
|
|
114
135
|
* `root` returns self if ancestry is invalid (thx @vanboom)
|
|
@@ -116,29 +137,29 @@ Both of these are better documented in [the readme](/README.md).
|
|
|
116
137
|
* oracleenhanced uses nulls first for sorting (thx @lual)
|
|
117
138
|
* fix counter cache and STI (thx @mattvague)
|
|
118
139
|
|
|
119
|
-
## Version [4.0.0] <sub><sup>2021-04-12</sub
|
|
140
|
+
## Version [4.0.0] <sub><sup>2021-04-12</sup></sub>
|
|
120
141
|
|
|
121
142
|
* dropped support for rails 4.2 and 5.0 (thx @d-m-u)
|
|
122
143
|
* better documentation counter cache option (thx @pustomytnyk)
|
|
123
144
|
* clean up code (thx @amatsuda @d-m-u)
|
|
124
|
-
* fixed rails 6.1 support (thx @cmr119 @d-staehler @danini-the-panini
|
|
145
|
+
* fixed rails 6.1 support (thx @cmr119 @d-staehler @danini-the-panini)
|
|
125
146
|
* phasing out `parent_id?`, `ancestors?` and using `has_parent?` instead
|
|
126
147
|
* fixed postgres order bug on rails 6.2 and higher (thx @smoyt)
|
|
127
148
|
|
|
128
|
-
## Version [3.2.1] <sub><sup>2020-09-23</sub
|
|
149
|
+
## Version [3.2.1] <sub><sup>2020-09-23</sup></sub>
|
|
129
150
|
|
|
130
151
|
* fixed gemspec to include locales and pg (thx @HectorMF)
|
|
131
152
|
|
|
132
|
-
## Version [3.2.0] <sub><sup>2020-09-23</sub
|
|
153
|
+
## Version [3.2.0] <sub><sup>2020-09-23</sup></sub>
|
|
133
154
|
|
|
134
155
|
* introduce i18n
|
|
135
156
|
* pg sql optimization for ancestry changes (thx @suonlight and @geis)
|
|
136
157
|
* pg sql optimization for sorting (thx @brendon and @d-m-u)
|
|
137
|
-
* fix to
|
|
158
|
+
* fix to humanize model name (thx @mkllnk)
|
|
138
159
|
* able to convert to ancestry from a parent_id column with a different name
|
|
139
160
|
* documentation fixes for better diagrams and grammar (thx @dtamais, @d-m-u, and @CamilleDrapier)
|
|
140
161
|
|
|
141
|
-
## Version [3.1.0] <sub><sup>2020-08-03</sub
|
|
162
|
+
## Version [3.1.0] <sub><sup>2020-08-03</sup></sub>
|
|
142
163
|
|
|
143
164
|
* `:primary_key_format` method lets you change syntax. good for uuids.
|
|
144
165
|
* changed code from being `ancestry` string to `ancestry_ids` focused. May break monkey patches.
|
|
@@ -147,40 +168,40 @@ Both of these are better documented in [the readme](/README.md).
|
|
|
147
168
|
* Better documentation for relationships (thnx @dtamai and @d-m-u)
|
|
148
169
|
* Fix creating children in `after_*` callbacks (thx @jstirk)
|
|
149
170
|
|
|
150
|
-
## Version [3.0.7] <sub><sup>2018-11-06</sub
|
|
171
|
+
## Version [3.0.7] <sub><sup>2018-11-06</sup></sub>
|
|
151
172
|
|
|
152
173
|
* Fixed rails 5.1 change detection (thx @jrafanie)
|
|
153
174
|
* Introduce counter cache (thx @hw676018683)
|
|
154
175
|
|
|
155
|
-
## Version [3.0.6] <sub><sup>2018-11-06</sub
|
|
176
|
+
## Version [3.0.6] <sub><sup>2018-11-06</sup></sub>
|
|
156
177
|
|
|
157
178
|
* Fixed rails 4.1 version check (thx @myxoh)
|
|
158
179
|
|
|
159
|
-
## Version [3.0.5] <sub><sup>2018-11-06</sub
|
|
180
|
+
## Version [3.0.5] <sub><sup>2018-11-06</sup></sub>
|
|
160
181
|
|
|
161
|
-
|
|
182
|
+
### Changed
|
|
162
183
|
|
|
163
184
|
* Added indirect children support (thx @tilo)
|
|
164
185
|
* Fixed test sorting for pg on mac osx
|
|
165
186
|
|
|
166
|
-
|
|
187
|
+
### Fixes
|
|
167
188
|
|
|
168
189
|
* Reduced memory footprint of parsing ancestry column (thx @NickLaMuro)
|
|
169
190
|
|
|
170
|
-
## Version [3.0.4] <sub><sup>2018-10-27</sub
|
|
191
|
+
## Version [3.0.4] <sub><sup>2018-10-27</sup></sub>
|
|
171
192
|
|
|
172
|
-
|
|
193
|
+
### Fixes
|
|
173
194
|
|
|
174
195
|
* Properly detects non-integer columns (thx @adam101)
|
|
175
196
|
* Arrange no longer drops nodes due to missing parents (thx @trafium)
|
|
176
197
|
|
|
177
|
-
## Version [3.0.3] <sub><sup>2018-10-23</sub
|
|
198
|
+
## Version [3.0.3] <sub><sup>2018-10-23</sup></sub>
|
|
178
199
|
|
|
179
200
|
This branch (3.x) should still be compatible with rails 3 and 4.
|
|
180
201
|
Rails 5.1 and 5.2 support were introduced in this version, but ongoing support
|
|
181
202
|
has been moved to ancestry 4.0
|
|
182
203
|
|
|
183
|
-
|
|
204
|
+
### Fixes
|
|
184
205
|
|
|
185
206
|
* Reduce object allocation (thx @NickLaMuro)
|
|
186
207
|
* Rails 5.1 fixes (thx @ctrombley)
|
|
@@ -190,9 +211,9 @@ has been moved to ancestry 4.0
|
|
|
190
211
|
* Dropped builds for ruby 1.9.3, 2.0, 2.1, and 2.2
|
|
191
212
|
* Dropped builds for Rails 3.x and 4.x (will use Active Record `or` syntax)
|
|
192
213
|
|
|
193
|
-
## Version [3.0.2] <sub><sup>2018-04-24</sub
|
|
214
|
+
## Version [3.0.2] <sub><sup>2018-04-24</sup></sub>
|
|
194
215
|
|
|
195
|
-
|
|
216
|
+
### Fixes
|
|
196
217
|
|
|
197
218
|
* fixed `order_by_ancestry` bug
|
|
198
219
|
* fixed order tests for postgres on mac (it uses a different collation)
|
|
@@ -200,9 +221,9 @@ has been moved to ancestry 4.0
|
|
|
200
221
|
* added missing `Ancestry::version`
|
|
201
222
|
* added Rails 5.2 support (thx @jjuliano)
|
|
202
223
|
|
|
203
|
-
## Version [3.0.1] <sub><sup>2017-07-05</sub
|
|
224
|
+
## Version [3.0.1] <sub><sup>2017-07-05</sup></sub>
|
|
204
225
|
|
|
205
|
-
|
|
226
|
+
### Fixes
|
|
206
227
|
|
|
207
228
|
* added gem metadata
|
|
208
229
|
* fixed keep a changelog link (thx @mattbrictson)
|
|
@@ -211,14 +232,14 @@ has been moved to ancestry 4.0
|
|
|
211
232
|
* fixed tests on mysql 5.7 and rails 3.2
|
|
212
233
|
* Dropped 3.1 scope changes
|
|
213
234
|
|
|
214
|
-
## Version [3.0.0] <sub><sup>2017-05-18</sub
|
|
235
|
+
## Version [3.0.0] <sub><sup>2017-05-18</sup></sub>
|
|
215
236
|
|
|
216
|
-
|
|
237
|
+
### Changed
|
|
217
238
|
|
|
218
239
|
* Dropping Rails 3.0, and 3.1. Added Rails 5.1 support (thx @ledermann)
|
|
219
240
|
* Dropping Rails 4.0, 4.1 for build reasons. Since 4.2 is supported, all 4.x should still work.
|
|
220
241
|
|
|
221
|
-
|
|
242
|
+
### Fixes
|
|
222
243
|
|
|
223
244
|
* Performance: Use `pluck` vs `map` for ids (thx @njakobsen and @culturecode)
|
|
224
245
|
* Fixed acts_as_tree compatibility (thx @crazymykl)
|
|
@@ -227,7 +248,7 @@ has been moved to ancestry 4.0
|
|
|
227
248
|
* Properly touches parents when different class for STI (thx @samtgarson)
|
|
228
249
|
* Fixed issues with parent_id (only present on master) (thx @domcleal)
|
|
229
250
|
|
|
230
|
-
## Version [2.2.2] <sub><sup>2016-11-01</sub
|
|
251
|
+
## Version [2.2.2] <sub><sup>2016-11-01</sup></sub>
|
|
231
252
|
|
|
232
253
|
### Changed
|
|
233
254
|
|
|
@@ -235,22 +256,25 @@ has been moved to ancestry 4.0
|
|
|
235
256
|
* Fixed bug with explicit order clauses (introduced in 2.2.0)
|
|
236
257
|
* No longer load schema on `has_ancestry` load (thx @ledermann)
|
|
237
258
|
|
|
238
|
-
## Version [2.2.1] <sub><sup>2016-10-25</sub
|
|
259
|
+
## Version [2.2.1] <sub><sup>2016-10-25</sup></sub>
|
|
239
260
|
|
|
240
261
|
Sorry for blip, local master got out of sync with upstream master.
|
|
241
262
|
Missed 2 commits (which are feature adds)
|
|
242
263
|
|
|
243
264
|
### Added
|
|
265
|
+
|
|
244
266
|
* Use like (vs ilike) for rails 5.0 (performance enhancement)
|
|
245
267
|
* Use `COALESCE` for sorting on pg, mysql, and sqlite vs `CASE`
|
|
246
268
|
|
|
247
|
-
## Version [2.2.0] <sub><sup>2016-10-25</sub
|
|
269
|
+
## Version [2.2.0] <sub><sup>2016-10-25</sup></sub>
|
|
248
270
|
|
|
249
271
|
### Added
|
|
272
|
+
|
|
250
273
|
* Predicates for scopes: e.g.: `ancestor_of?`, `parent_of?` (thx @neglectedvalue)
|
|
251
274
|
* Scope `path_of`
|
|
252
275
|
|
|
253
276
|
### Changed
|
|
277
|
+
|
|
254
278
|
* `arrange` now accepts blocks (thx @mastfish)
|
|
255
279
|
* Performance tuning `arrange_node` (thx @fryguy)
|
|
256
280
|
* In orphan strategy, set `ancestry` to `nil` for no parents (thx @haslinger)
|
|
@@ -259,7 +283,8 @@ Missed 2 commits (which are feature adds)
|
|
|
259
283
|
* Upgrading tests for ruby versions (thx @brocktimus, @fryguy, @yui-knk)
|
|
260
284
|
* Fix non-default ancestry not getting used properly (thx @javiyu)
|
|
261
285
|
|
|
262
|
-
## Version [2.1.0] <sub><sup>2014-04-16</sub
|
|
286
|
+
## Version [2.1.0] <sub><sup>2014-04-16</sup></sub>
|
|
287
|
+
|
|
263
288
|
* Added arrange_serializable (thx @krishandley, @chicagogrrl)
|
|
264
289
|
* Add the :touch to update ancestors on save (thx @adammck)
|
|
265
290
|
* Change conditions into arel (thx @mlitwiniuk)
|
|
@@ -268,7 +293,8 @@ Missed 2 commits (which are feature adds)
|
|
|
268
293
|
* Performance tweak (thx @mjc)
|
|
269
294
|
* Improvements to organization (thx @xsuchy, @ryakh)
|
|
270
295
|
|
|
271
|
-
## Version [2.0.0] <sub><sup>2013-05-17</sub
|
|
296
|
+
## Version [2.0.0] <sub><sup>2013-05-17</sup></sub>
|
|
297
|
+
|
|
272
298
|
* Removed rails 2 compatibility
|
|
273
299
|
* Added table name to condition constructing methods (thx @aflatter)
|
|
274
300
|
* Fix depth_cache not being updated when moving up to ancestors (thx @scottatron)
|
|
@@ -280,34 +306,40 @@ Missed 2 commits (which are feature adds)
|
|
|
280
306
|
* New adopt strategy (thx unknown)
|
|
281
307
|
* Many more improvements
|
|
282
308
|
|
|
283
|
-
## Version [1.3.0] <sub><sup>2012-05-04</sub
|
|
309
|
+
## Version [1.3.0] <sub><sup>2012-05-04</sup></sub>
|
|
310
|
+
|
|
284
311
|
* Ancestry now ignores default scopes when moving or destroying nodes, ensuring tree consistency
|
|
285
312
|
* Changed ActiveRecord dependency to 2.3.14
|
|
286
313
|
|
|
287
|
-
## Version [1.2.5] <sub><sup>2012-03-15</sub
|
|
314
|
+
## Version [1.2.5] <sub><sup>2012-03-15</sup></sub>
|
|
315
|
+
|
|
288
316
|
* Fixed warnings: "parenthesize argument(s) for future version"
|
|
289
317
|
* Fixed a bug in the restore_ancestry_integrity! method (thx Arthur Holstvoogd)
|
|
290
318
|
|
|
291
|
-
## Version [1.2.4] <sub><sup>2011-04-22</sub
|
|
319
|
+
## Version [1.2.4] <sub><sup>2011-04-22</sup></sub>
|
|
320
|
+
|
|
292
321
|
* Prepended table names to column names in queries (thx @raelik)
|
|
293
322
|
* Better check to see if acts_as_tree can be overloaded (thx @jims)
|
|
294
|
-
* Performance
|
|
323
|
+
* Performance improvements (thx @kueda)
|
|
324
|
+
|
|
325
|
+
## Version [1.2.3] <sub><sup>2010-10-28</sup></sub>
|
|
295
326
|
|
|
296
|
-
## Version [1.2.3] <sub><sup>2010-10-28</sub></sup>
|
|
297
327
|
* Fixed error with determining ActiveRecord version
|
|
298
328
|
* Added option to specify :primary_key_format (thx @rolftimmermans)
|
|
299
329
|
|
|
300
|
-
## Version [1.2.2] <sub><sup>2010-10-24</sub
|
|
330
|
+
## Version [1.2.2] <sub><sup>2010-10-24</sup></sub>
|
|
331
|
+
|
|
301
332
|
* Fixed all deprecation warnings for rails 3.0.X
|
|
302
333
|
* Added `:report` option to `check_ancestry_integrity!`
|
|
303
334
|
* Changed ActiveRecord dependency to 2.2.2
|
|
304
335
|
* Tested and fixed for ruby 1.8.7 and 1.9.2
|
|
305
336
|
* Changed usage of `update_attributes` to `update_attribute` to allow ancestry column protection
|
|
306
337
|
|
|
307
|
-
## Version [1.2.0] <sub><sup>2009-11-07</sub
|
|
338
|
+
## Version [1.2.0] <sub><sup>2009-11-07</sup></sub>
|
|
339
|
+
|
|
308
340
|
* Removed some duplication in has_ancestry
|
|
309
341
|
* Cleaned up plugin pattern according to http://yehudakatz.com/2009/11/12/better-ruby-idioms/
|
|
310
|
-
* Moved parts of ancestry into
|
|
342
|
+
* Moved parts of ancestry into separate files
|
|
311
343
|
* Made it possible to pass options into the arrange method
|
|
312
344
|
* Renamed acts_as_tree to has_ancestry
|
|
313
345
|
* Aliased has_ancestry as acts_as_tree if acts_as_tree is available
|
|
@@ -315,46 +347,52 @@ Missed 2 commits (which are feature adds)
|
|
|
315
347
|
* Updated ordered_by_ancestry scope to support Microsoft SQL Server
|
|
316
348
|
* Added empty hash as parameter to exists? calls for older ActiveRecord versions
|
|
317
349
|
|
|
318
|
-
## Version [1.1.4] <sub><sup>2009-11-07</sub
|
|
350
|
+
## Version [1.1.4] <sub><sup>2009-11-07</sup></sub>
|
|
351
|
+
|
|
319
352
|
* Thanks to a patch from tom taylor, Ancestry now works with different primary keys
|
|
320
353
|
|
|
321
|
-
## Version [1.1.3] <sub><sup>2009-11-01</sub
|
|
354
|
+
## Version [1.1.3] <sub><sup>2009-11-01</sup></sub>
|
|
355
|
+
|
|
322
356
|
* Fixed a pretty bad bug where several operations took far too many queries
|
|
323
357
|
|
|
324
|
-
## Version [1.1.2] <sub><sup>2009-10-29</sub
|
|
358
|
+
## Version [1.1.2] <sub><sup>2009-10-29</sup></sub>
|
|
359
|
+
|
|
325
360
|
* Added validation for depth cache column
|
|
326
361
|
* Added STI support (reported broken)
|
|
327
362
|
|
|
328
|
-
## Version [1.1.1] <sub><sup>2009-10-28</sub
|
|
363
|
+
## Version [1.1.1] <sub><sup>2009-10-28</sup></sub>
|
|
364
|
+
|
|
329
365
|
* Fixed some parentheses warnings that where reported
|
|
330
366
|
* Fixed a reported issue with arrangement
|
|
331
367
|
* Fixed issues with ancestors and path order on postgres
|
|
332
368
|
* Added ordered_by_ancestry scope (needed to fix issues)
|
|
333
369
|
|
|
334
|
-
## Version [1.1.0] <sub><sup>2009-10-22</sub
|
|
370
|
+
## Version [1.1.0] <sub><sup>2009-10-22</sup></sub>
|
|
371
|
+
|
|
335
372
|
* Depth caching (and cache rebuilding)
|
|
336
373
|
* Depth method for nodes
|
|
337
374
|
* Named scopes for selecting by depth
|
|
338
|
-
* Relative depth options for tree navigation methods:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
375
|
+
* Relative depth options for tree navigation methods:
|
|
376
|
+
* ancestors
|
|
377
|
+
* path
|
|
378
|
+
* descendants
|
|
379
|
+
* descendant_ids
|
|
380
|
+
* subtree
|
|
381
|
+
* subtree_ids
|
|
345
382
|
* Updated README
|
|
346
383
|
* Easy migration from existing plugins/gems
|
|
347
384
|
* acts_as_tree checks unknown options
|
|
348
385
|
* acts_as_tree checks that options are hash
|
|
349
386
|
* Added a bang (!) to the integrity functions
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
387
|
+
* Since these functions should only be used from ./script/console and not
|
|
388
|
+
from your application, this change is not considered as breaking backwards
|
|
389
|
+
compatibility and the major version wasn't bumped.
|
|
353
390
|
* Updated install script to point to documentation
|
|
354
391
|
* Removed rails specific init
|
|
355
392
|
* Removed uninstall script
|
|
356
393
|
|
|
357
|
-
## Version 1.0.0 <sub><sup>2009-10-16</sub
|
|
394
|
+
## Version 1.0.0 <sub><sup>2009-10-16</sup></sub>
|
|
395
|
+
|
|
358
396
|
* Initial version
|
|
359
397
|
* Tree building
|
|
360
398
|
* Tree navigation
|
|
@@ -365,8 +403,8 @@ Missed 2 commits (which are feature adds)
|
|
|
365
403
|
* Named scopes
|
|
366
404
|
* Validations
|
|
367
405
|
|
|
368
|
-
|
|
369
|
-
[
|
|
406
|
+
[HEAD]: https://github.com/stefankroes/ancestry/compare/v5.1.0...HEAD
|
|
407
|
+
[5.1.0]: https://github.com/stefankroes/ancestry/compare/v5.0.0...v5.1.0
|
|
370
408
|
[5.0.0]: https://github.com/stefankroes/ancestry/compare/v4.3.3...v5.0.0
|
|
371
409
|
[4.3.3]: https://github.com/stefankroes/ancestry/compare/v4.3.2...v4.3.3
|
|
372
410
|
[4.3.2]: https://github.com/stefankroes/ancestry/compare/v4.3.1...v4.3.2
|
data/README.md
CHANGED
|
@@ -519,6 +519,19 @@ Some use migrations, but that can make the migration suite fragile. The command
|
|
|
519
519
|
Model.rebuild_depth_cache!
|
|
520
520
|
```
|
|
521
521
|
|
|
522
|
+
## Depth Constraints
|
|
523
|
+
|
|
524
|
+
You can use standard Rails validations on your depth cache column to restrict the depth of your tree. For example, to ensure no nodes are deeper than 2:
|
|
525
|
+
|
|
526
|
+
```ruby
|
|
527
|
+
class TreeNode < ActiveRecord::Base
|
|
528
|
+
has_ancestry cache_depth: true
|
|
529
|
+
validates :ancestry_depth, numericality: { less_than_or_equal_to: 2 }
|
|
530
|
+
end
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Ancestry will automatically validate not just the node itself, but also ensure that moving a subtree does not cause any of its descendants to exceed the maximum depth.
|
|
534
|
+
|
|
522
535
|
# Running Tests
|
|
523
536
|
|
|
524
537
|
```bash
|
|
@@ -108,13 +108,17 @@ module Ancestry
|
|
|
108
108
|
|
|
109
109
|
# Pseudo-preordered array of nodes. Children will always follow parents,
|
|
110
110
|
# This is deterministic unless the parents are missing *and* a sort block is specified
|
|
111
|
-
def sort_by_ancestry(nodes)
|
|
111
|
+
def sort_by_ancestry(nodes, &block)
|
|
112
|
+
_sort_by_ancestry(nodes, ancestry_column, &block)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def _sort_by_ancestry(nodes, column, &block)
|
|
112
116
|
arranged = nodes if nodes.is_a?(Hash)
|
|
113
117
|
|
|
114
118
|
unless arranged
|
|
115
119
|
presorted_nodes = nodes.sort do |a, b|
|
|
116
|
-
rank = (a.public_send(
|
|
117
|
-
rank =
|
|
120
|
+
rank = (a.public_send(column) || ' ') <=> (b.public_send(column) || ' ')
|
|
121
|
+
rank = block.call(a, b) if rank == 0 && block
|
|
118
122
|
rank
|
|
119
123
|
end
|
|
120
124
|
|
|
@@ -129,6 +133,10 @@ module Ancestry
|
|
|
129
133
|
# just in case, raise an AncestryIntegrityException if issues are detected
|
|
130
134
|
# specify :report => :list to return an array of exceptions or :report => :echo to echo any error messages
|
|
131
135
|
def check_ancestry_integrity!(options = {})
|
|
136
|
+
_check_ancestry_integrity!(ancestry_column, options)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def _check_ancestry_integrity!(column, options = {})
|
|
132
140
|
parents = {}
|
|
133
141
|
exceptions = [] if options[:report] == :list
|
|
134
142
|
|
|
@@ -139,7 +147,7 @@ module Ancestry
|
|
|
139
147
|
if !node.sane_ancestor_ids?
|
|
140
148
|
raise Ancestry::AncestryIntegrityException, I18n.t("ancestry.invalid_ancestry_column",
|
|
141
149
|
:node_id => node.id,
|
|
142
|
-
:ancestry_column => node.read_attribute(
|
|
150
|
+
:ancestry_column => node.read_attribute(column))
|
|
143
151
|
end
|
|
144
152
|
# ... check that all ancestors exist
|
|
145
153
|
node.ancestor_ids.each do |ancestor_id|
|
|
@@ -50,13 +50,20 @@ module Ancestry
|
|
|
50
50
|
validates ancestry_column, ancestry_validation_options(primary_key_format)
|
|
51
51
|
|
|
52
52
|
update_strategy = options[:update_strategy] || Ancestry.default_update_strategy
|
|
53
|
-
include Ancestry::MaterializedPathPg
|
|
53
|
+
include Ancestry::MaterializedPathPg
|
|
54
54
|
|
|
55
55
|
# Validate that the ancestor ids don't include own id
|
|
56
56
|
validate :ancestry_exclude_self
|
|
57
57
|
|
|
58
|
+
# Validate descendants' depths don't exceed max depth when moving them
|
|
59
|
+
validate :ancestry_depth_of_descendants, if: :ancestry_changed?
|
|
60
|
+
|
|
58
61
|
# Update descendants with new ancestry after update
|
|
59
|
-
|
|
62
|
+
if update_strategy == :sql
|
|
63
|
+
after_update :update_descendants_with_new_ancestry_sql, if: :ancestry_changed?
|
|
64
|
+
else
|
|
65
|
+
after_update :update_descendants_with_new_ancestry, if: :ancestry_changed?
|
|
66
|
+
end
|
|
60
67
|
|
|
61
68
|
# Apply orphan strategy before destroy
|
|
62
69
|
orphan_strategy_helper = "apply_orphan_strategy_#{orphan_strategy}"
|
|
@@ -7,6 +7,32 @@ module Ancestry
|
|
|
7
7
|
errors.add(:base, I18n.t("ancestry.exclude_self", class_name: self.class.model_name.human)) if ancestor_ids.include?(id)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
|
+
# Validate that descendants' depths don't exceed max depth when moving them
|
|
11
|
+
def ancestry_depth_of_descendants
|
|
12
|
+
return if new_record? || (respond_to?(:previously_new_record?) && previously_new_record?)
|
|
13
|
+
return unless self.class.respond_to?(:depth_cache_column) && self.class.depth_cache_column
|
|
14
|
+
|
|
15
|
+
column = self.class.depth_cache_column
|
|
16
|
+
validator = self.class.validators_on(column).find do |v|
|
|
17
|
+
v.is_a?(ActiveModel::Validations::NumericalityValidator) &&
|
|
18
|
+
(v.options[:less_than_or_equal_to] || v.options[:less_than])
|
|
19
|
+
end
|
|
20
|
+
return unless validator
|
|
21
|
+
|
|
22
|
+
max_depth = validator.options[:less_than_or_equal_to] || (validator.options[:less_than] - 1)
|
|
23
|
+
|
|
24
|
+
old_value = attribute_in_database(self.class.ancestry_column)
|
|
25
|
+
new_value = read_attribute(self.class.ancestry_column)
|
|
26
|
+
depth_change = self.class.ancestry_depth_change(old_value, new_value)
|
|
27
|
+
|
|
28
|
+
if depth_change > 0
|
|
29
|
+
max_descendant_depth = unscoped_descendants.maximum(column) || attribute_in_database(column) || 0
|
|
30
|
+
if max_descendant_depth + depth_change > max_depth
|
|
31
|
+
errors.add(column, :less_than_or_equal_to, count: max_depth)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
10
36
|
# Update descendants with new ancestry (after update)
|
|
11
37
|
def update_descendants_with_new_ancestry
|
|
12
38
|
# If enabled and the new ancestry is sane ...
|
|
@@ -30,16 +30,13 @@ module Ancestry
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def children_of(object)
|
|
33
|
-
t = arel_table
|
|
34
33
|
node = to_node(object)
|
|
35
|
-
where(
|
|
34
|
+
where(arel_table[ancestry_column].eq(node.child_ancestry))
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
# indirect = anyone who is a descendant, but not a child
|
|
39
37
|
def indirects_of(object)
|
|
40
|
-
t = arel_table
|
|
41
38
|
node = to_node(object)
|
|
42
|
-
where(
|
|
39
|
+
where(MaterializedPath.indirects_condition(arel_table[ancestry_column], node.child_ancestry, ancestry_delimiter))
|
|
43
40
|
end
|
|
44
41
|
|
|
45
42
|
def descendants_of(object)
|
|
@@ -47,8 +44,7 @@ module Ancestry
|
|
|
47
44
|
end
|
|
48
45
|
|
|
49
46
|
def descendants_by_ancestry(ancestry)
|
|
50
|
-
|
|
51
|
-
t[ancestry_column].matches("#{ancestry}#{ancestry_delimiter}%", nil, true).or(t[ancestry_column].eq(ancestry))
|
|
47
|
+
MaterializedPath.descendants_condition(arel_table[ancestry_column], ancestry, ancestry_delimiter)
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
def descendant_conditions(object)
|
|
@@ -62,15 +58,13 @@ module Ancestry
|
|
|
62
58
|
end
|
|
63
59
|
|
|
64
60
|
def subtree_of(object)
|
|
65
|
-
t = arel_table
|
|
66
61
|
node = to_node(object)
|
|
67
|
-
descendants_of(node).or(where(
|
|
62
|
+
descendants_of(node).or(where(arel_table[primary_key].eq(node.id)))
|
|
68
63
|
end
|
|
69
64
|
|
|
70
65
|
def siblings_of(object)
|
|
71
|
-
t = arel_table
|
|
72
66
|
node = to_node(object)
|
|
73
|
-
where(
|
|
67
|
+
where(arel_table[ancestry_column].eq(node[ancestry_column].presence))
|
|
74
68
|
end
|
|
75
69
|
|
|
76
70
|
def ordered_by_ancestry(order = nil)
|
|
@@ -95,11 +89,7 @@ module Ancestry
|
|
|
95
89
|
end
|
|
96
90
|
|
|
97
91
|
def child_ancestry_sql
|
|
98
|
-
|
|
99
|
-
CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN #{concat("#{table_name}.#{primary_key}")}
|
|
100
|
-
ELSE #{concat("#{table_name}.#{ancestry_column}", "'#{ancestry_delimiter}'", "#{table_name}.#{primary_key}")}
|
|
101
|
-
END
|
|
102
|
-
}
|
|
92
|
+
MaterializedPath.child_ancestry_sql(table_name, ancestry_column, primary_key, ancestry_delimiter, connection.adapter_name.downcase)
|
|
103
93
|
end
|
|
104
94
|
|
|
105
95
|
def ancestry_depth_sql
|
|
@@ -107,53 +97,85 @@ module Ancestry
|
|
|
107
97
|
end
|
|
108
98
|
|
|
109
99
|
def generate_ancestry(ancestor_ids)
|
|
100
|
+
MaterializedPath.generate(ancestor_ids, ancestry_delimiter, ancestry_root)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_ancestry_column(obj)
|
|
104
|
+
MaterializedPath.parse(obj, ancestry_root, ancestry_delimiter, primary_key_is_an_integer?)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def ancestry_depth_change(old_value, new_value)
|
|
108
|
+
parse_ancestry_column(new_value).size - parse_ancestry_column(old_value).size
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.generate(ancestor_ids, delimiter, root)
|
|
110
112
|
if ancestor_ids.present? && ancestor_ids.any?
|
|
111
|
-
ancestor_ids.join(
|
|
113
|
+
ancestor_ids.join(delimiter)
|
|
112
114
|
else
|
|
113
|
-
|
|
115
|
+
root
|
|
114
116
|
end
|
|
115
117
|
end
|
|
116
118
|
|
|
117
|
-
def
|
|
118
|
-
return [] if obj.nil? || obj ==
|
|
119
|
+
def self.parse(obj, root, delimiter, integer_pk)
|
|
120
|
+
return [] if obj.nil? || obj == root
|
|
119
121
|
|
|
120
|
-
obj_ids = obj.split(
|
|
121
|
-
|
|
122
|
+
obj_ids = obj.split(delimiter).delete_if(&:blank?)
|
|
123
|
+
integer_pk ? obj_ids.map!(&:to_i) : obj_ids
|
|
122
124
|
end
|
|
123
125
|
|
|
124
|
-
def
|
|
125
|
-
|
|
126
|
+
def self.child_ancestry_value(ancestry_value, id, delimiter)
|
|
127
|
+
[ancestry_value, id].compact.join(delimiter)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Arel condition: descendants have ancestry matching child_ancestry or starting with child_ancestry/
|
|
131
|
+
def self.descendants_condition(attr, child_ancestry, delimiter)
|
|
132
|
+
attr.matches("#{child_ancestry}#{delimiter}%", nil, true).or(attr.eq(child_ancestry))
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Arel condition: indirects have ancestry matching child_ancestry/*/
|
|
136
|
+
def self.indirects_condition(attr, child_ancestry, delimiter)
|
|
137
|
+
attr.matches("#{child_ancestry}#{delimiter}%", nil, true)
|
|
126
138
|
end
|
|
127
139
|
|
|
128
140
|
def concat(*args)
|
|
129
|
-
|
|
141
|
+
MaterializedPath.concat(connection.adapter_name.downcase, *args)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.concat(adapter, *args)
|
|
145
|
+
if %w(sqlite sqlite3).include?(adapter)
|
|
130
146
|
args.join('||')
|
|
131
147
|
else
|
|
132
148
|
%{CONCAT(#{args.join(', ')})}
|
|
133
149
|
end
|
|
134
150
|
end
|
|
135
151
|
|
|
152
|
+
def self.child_ancestry_sql(table_name, ancestry_column, primary_key, delimiter, adapter)
|
|
153
|
+
pk_sql = concat(adapter, "#{table_name}.#{primary_key}")
|
|
154
|
+
full_sql = concat(adapter, "#{table_name}.#{ancestry_column}", "'#{delimiter}'", "#{table_name}.#{primary_key}")
|
|
155
|
+
%{
|
|
156
|
+
CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN #{pk_sql}
|
|
157
|
+
ELSE #{full_sql}
|
|
158
|
+
END
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
136
162
|
def self.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter)
|
|
137
163
|
tmp = %{(LENGTH(#{table_name}.#{ancestry_column}) - LENGTH(REPLACE(#{table_name}.#{ancestry_column},'#{ancestry_delimiter}','')))}
|
|
138
164
|
tmp += "/#{ancestry_delimiter.size}" if ancestry_delimiter.size > 1
|
|
139
165
|
"(CASE WHEN #{table_name}.#{ancestry_column} IS NULL THEN 0 ELSE 1 + #{tmp} END)"
|
|
140
166
|
end
|
|
141
167
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def ancestry_validation_options(ancestry_primary_key_format)
|
|
168
|
+
def self.validation_options(primary_key_format, delimiter)
|
|
145
169
|
{
|
|
146
|
-
format: {with:
|
|
147
|
-
allow_nil:
|
|
170
|
+
format: {with: /\A#{primary_key_format}(#{Regexp.escape(delimiter)}#{primary_key_format})*\z/.freeze},
|
|
171
|
+
allow_nil: true
|
|
148
172
|
}
|
|
149
173
|
end
|
|
150
174
|
|
|
151
|
-
|
|
152
|
-
true
|
|
153
|
-
end
|
|
175
|
+
private
|
|
154
176
|
|
|
155
|
-
def
|
|
156
|
-
|
|
177
|
+
def ancestry_validation_options(ancestry_primary_key_format)
|
|
178
|
+
MaterializedPath.validation_options(ancestry_primary_key_format, ancestry_delimiter)
|
|
157
179
|
end
|
|
158
180
|
|
|
159
181
|
module InstanceMethods
|
|
@@ -168,23 +190,23 @@ module Ancestry
|
|
|
168
190
|
end
|
|
169
191
|
|
|
170
192
|
def ancestor_ids
|
|
171
|
-
|
|
193
|
+
MaterializedPath.parse(read_attribute(self.class.ancestry_column), self.class.ancestry_root, self.class.ancestry_delimiter, self.class.primary_key_is_an_integer?)
|
|
172
194
|
end
|
|
173
195
|
|
|
174
196
|
def ancestor_ids_in_database
|
|
175
|
-
|
|
197
|
+
MaterializedPath.parse(attribute_in_database(self.class.ancestry_column), self.class.ancestry_root, self.class.ancestry_delimiter, self.class.primary_key_is_an_integer?)
|
|
176
198
|
end
|
|
177
199
|
|
|
178
200
|
def ancestor_ids_before_last_save
|
|
179
|
-
|
|
201
|
+
MaterializedPath.parse(attribute_before_last_save(self.class.ancestry_column), self.class.ancestry_root, self.class.ancestry_delimiter, self.class.primary_key_is_an_integer?)
|
|
180
202
|
end
|
|
181
203
|
|
|
182
204
|
def parent_id_in_database
|
|
183
|
-
|
|
205
|
+
MaterializedPath.parse(attribute_in_database(self.class.ancestry_column), self.class.ancestry_root, self.class.ancestry_delimiter, self.class.primary_key_is_an_integer?).last
|
|
184
206
|
end
|
|
185
207
|
|
|
186
208
|
def parent_id_before_last_save
|
|
187
|
-
|
|
209
|
+
MaterializedPath.parse(attribute_before_last_save(self.class.ancestry_column), self.class.ancestry_root, self.class.ancestry_delimiter, self.class.primary_key_is_an_integer?).last
|
|
188
210
|
end
|
|
189
211
|
|
|
190
212
|
# optimization - better to go directly to column and avoid parsing
|
|
@@ -200,7 +222,7 @@ module Ancestry
|
|
|
200
222
|
def child_ancestry
|
|
201
223
|
raise(Ancestry::AncestryException, I18n.t("ancestry.no_child_for_new_record")) if new_record?
|
|
202
224
|
|
|
203
|
-
|
|
225
|
+
MaterializedPath.child_ancestry_value(attribute_in_database(self.class.ancestry_column), id, self.class.ancestry_delimiter)
|
|
204
226
|
end
|
|
205
227
|
|
|
206
228
|
# The ancestry value for this record's old children
|
|
@@ -212,7 +234,7 @@ module Ancestry
|
|
|
212
234
|
raise Ancestry::AncestryException, I18n.t("ancestry.no_child_for_new_record")
|
|
213
235
|
end
|
|
214
236
|
|
|
215
|
-
|
|
237
|
+
MaterializedPath.child_ancestry_value(attribute_before_last_save(self.class.ancestry_column), id, self.class.ancestry_delimiter)
|
|
216
238
|
end
|
|
217
239
|
end
|
|
218
240
|
end
|
|
@@ -13,9 +13,8 @@ module Ancestry
|
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def indirects_of(object)
|
|
16
|
-
t = arel_table
|
|
17
16
|
node = to_node(object)
|
|
18
|
-
where(
|
|
17
|
+
where(MaterializedPath2.indirects_condition(arel_table[ancestry_column], node.child_ancestry, ancestry_delimiter))
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
def ordered_by_ancestry(order = nil)
|
|
@@ -23,7 +22,7 @@ module Ancestry
|
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def descendants_by_ancestry(ancestry)
|
|
26
|
-
arel_table[ancestry_column]
|
|
25
|
+
MaterializedPath2.descendants_condition(arel_table[ancestry_column], ancestry, ancestry_delimiter)
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def ancestry_root
|
|
@@ -31,7 +30,7 @@ module Ancestry
|
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
def child_ancestry_sql
|
|
34
|
-
|
|
33
|
+
MaterializedPath2.child_ancestry_sql(table_name, ancestry_column, primary_key, ancestry_delimiter, connection.adapter_name.downcase)
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
def ancestry_depth_sql
|
|
@@ -39,13 +38,35 @@ module Ancestry
|
|
|
39
38
|
end
|
|
40
39
|
|
|
41
40
|
def generate_ancestry(ancestor_ids)
|
|
41
|
+
MaterializedPath2.generate(ancestor_ids, ancestry_delimiter, ancestry_root)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.generate(ancestor_ids, delimiter, root)
|
|
42
45
|
if ancestor_ids.present? && ancestor_ids.any?
|
|
43
|
-
"#{
|
|
46
|
+
"#{delimiter}#{ancestor_ids.join(delimiter)}#{delimiter}"
|
|
44
47
|
else
|
|
45
|
-
|
|
48
|
+
root
|
|
46
49
|
end
|
|
47
50
|
end
|
|
48
51
|
|
|
52
|
+
def self.child_ancestry_value(ancestry_value, id, delimiter)
|
|
53
|
+
"#{ancestry_value}#{id}#{delimiter}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.child_ancestry_sql(table_name, ancestry_column, primary_key, delimiter, adapter)
|
|
57
|
+
MaterializedPath.concat(adapter, "#{table_name}.#{ancestry_column}", "#{table_name}.#{primary_key}", "'#{delimiter}'")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# mp2: descendants just use LIKE (trailing delimiter prevents false prefix matches)
|
|
61
|
+
def self.descendants_condition(attr, child_ancestry, _delimiter)
|
|
62
|
+
attr.matches("#{child_ancestry}%", nil, true)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# mp2: indirects match child_ancestry + at least one more segment
|
|
66
|
+
def self.indirects_condition(attr, child_ancestry, delimiter)
|
|
67
|
+
attr.matches("#{child_ancestry}%#{delimiter}%", nil, true)
|
|
68
|
+
end
|
|
69
|
+
|
|
49
70
|
# module method
|
|
50
71
|
def self.construct_depth_sql(table_name, ancestry_column, ancestry_delimiter)
|
|
51
72
|
tmp = %{(LENGTH(#{table_name}.#{ancestry_column}) - LENGTH(REPLACE(#{table_name}.#{ancestry_column},'#{ancestry_delimiter}','')))}
|
|
@@ -53,14 +74,17 @@ module Ancestry
|
|
|
53
74
|
"(#{tmp} -1)"
|
|
54
75
|
end
|
|
55
76
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
def self.validation_options(primary_key_format, delimiter)
|
|
78
|
+
{
|
|
79
|
+
format: {with: /\A#{Regexp.escape(delimiter)}(#{primary_key_format}#{Regexp.escape(delimiter)})*\z/.freeze},
|
|
80
|
+
allow_nil: false
|
|
81
|
+
}
|
|
60
82
|
end
|
|
61
83
|
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def ancestry_validation_options(ancestry_primary_key_format)
|
|
87
|
+
MaterializedPath2.validation_options(ancestry_primary_key_format, ancestry_delimiter)
|
|
64
88
|
end
|
|
65
89
|
|
|
66
90
|
module InstanceMethods
|
|
@@ -68,7 +92,7 @@ module Ancestry
|
|
|
68
92
|
def child_ancestry
|
|
69
93
|
raise(Ancestry::AncestryException, I18n.t("ancestry.no_child_for_new_record")) if new_record?
|
|
70
94
|
|
|
71
|
-
|
|
95
|
+
MaterializedPath2.child_ancestry_value(attribute_in_database(self.class.ancestry_column), id, self.class.ancestry_delimiter)
|
|
72
96
|
end
|
|
73
97
|
|
|
74
98
|
# Please see notes for MaterializedPath#child_ancestry_before_last_save
|
|
@@ -77,7 +101,7 @@ module Ancestry
|
|
|
77
101
|
raise(Ancestry::AncestryException, I18n.t("ancestry.no_child_for_new_record"))
|
|
78
102
|
end
|
|
79
103
|
|
|
80
|
-
|
|
104
|
+
MaterializedPath2.child_ancestry_value(attribute_before_last_save(self.class.ancestry_column), id, self.class.ancestry_delimiter)
|
|
81
105
|
end
|
|
82
106
|
end
|
|
83
107
|
end
|
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Ancestry
|
|
4
4
|
module MaterializedPathPg
|
|
5
|
-
# Update descendants with new ancestry (after update)
|
|
6
|
-
def
|
|
5
|
+
# Update descendants with new ancestry using a single SQL statement (after update)
|
|
6
|
+
def update_descendants_with_new_ancestry_sql
|
|
7
7
|
# If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
|
|
8
8
|
# The only way the ancestry could be bad is via `update_attribute` with a bad value
|
|
9
9
|
if !ancestry_callbacks_disabled? && sane_ancestor_ids?
|
|
10
10
|
old_ancestry = self.class.generate_ancestry(path_ids_before_last_save)
|
|
11
11
|
new_ancestry = self.class.generate_ancestry(path_ids)
|
|
12
|
+
# Replace old ancestry prefix with new ancestry:
|
|
13
|
+
# CONCAT(new_ancestry, SUBSTRING(column, LENGTH(old_ancestry) + 1))
|
|
14
|
+
column = self.class.ancestry_column
|
|
15
|
+
replace_sql = self.class.concat("'#{new_ancestry}'", "SUBSTRING(#{column}, #{old_ancestry.length + 1})")
|
|
12
16
|
update_clause = {
|
|
13
|
-
|
|
17
|
+
column => Arel.sql(replace_sql)
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
current_time = current_time_from_proper_timezone
|
data/lib/ancestry/version.rb
CHANGED