ancestry 3.0.2 → 3.0.3
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/README.md +10 -0
- data/ancestry.gemspec +1 -1
- data/lib/ancestry/class_methods.rb +20 -1
- data/lib/ancestry/has_ancestry.rb +4 -1
- data/lib/ancestry/instance_methods.rb +31 -21
- data/lib/ancestry/materialized_path.rb +1 -1
- data/lib/ancestry/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1c70fafe20e537ed6cb98a60e671dc01fd540ac
|
4
|
+
data.tar.gz: 583d3382374a761c553d99329f71be36ac423222
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55df9269223d805cd09f8fb87b5e0a25cad36aaad892e3527217cd086e15c6105e5552c0d7cd108645fe9a947712986a0885252f4b71a861eec1525be374f0b7
|
7
|
+
data.tar.gz: 3f8ea178c2f3cc918ddd1fb79f8af4e3b8ed63704cd764bed42d8fb79da3b5316ab7160871eade25f8af9aac3bb004a298abc59f17e5b9759085c1ef8eb3c41d
|
data/README.md
CHANGED
@@ -12,13 +12,20 @@ gems, integrity checking, integrity restoration, arrangement of
|
|
12
12
|
(sub)tree into hashes and different strategies for dealing with orphaned
|
13
13
|
records.
|
14
14
|
|
15
|
+
NOTE:
|
16
|
+
|
17
|
+
- Ancestry 3.x supports Rails 5.0 and earlier.
|
18
|
+
- Ancestry 4.0 only supports rails 5.0 and higher
|
19
|
+
|
15
20
|
# Installation
|
16
21
|
|
17
22
|
To apply Ancestry to any `ActiveRecord` model, follow these simple steps:
|
18
23
|
|
24
|
+
|
19
25
|
## Install
|
20
26
|
|
21
27
|
* Add to Gemfile:
|
28
|
+
|
22
29
|
```ruby
|
23
30
|
# Gemfile
|
24
31
|
|
@@ -26,6 +33,7 @@ gem 'ancestry'
|
|
26
33
|
```
|
27
34
|
|
28
35
|
* Install required gems:
|
36
|
+
|
29
37
|
```bash
|
30
38
|
$ bundle install
|
31
39
|
```
|
@@ -33,11 +41,13 @@ $ bundle install
|
|
33
41
|
|
34
42
|
## Add ancestry column to your table
|
35
43
|
* Create migration:
|
44
|
+
|
36
45
|
```bash
|
37
46
|
$ rails g migration add_ancestry_to_[table] ancestry:string:index
|
38
47
|
```
|
39
48
|
|
40
49
|
* Migrate your database:
|
50
|
+
|
41
51
|
```bash
|
42
52
|
$ rake db:migrate
|
43
53
|
```
|
data/ancestry.gemspec
CHANGED
@@ -2,7 +2,7 @@ module Ancestry
|
|
2
2
|
module ClassMethods
|
3
3
|
# Fetch tree node if necessary
|
4
4
|
def to_node object
|
5
|
-
if object.is_a?(self.ancestry_base_class) then object else find
|
5
|
+
if object.is_a?(self.ancestry_base_class) then object else unscoped_where{|scope| scope.find object} end
|
6
6
|
end
|
7
7
|
|
8
8
|
# Scope on relative depth options
|
@@ -206,5 +206,24 @@ module Ancestry
|
|
206
206
|
yield self.ancestry_base_class.unscope(:where)
|
207
207
|
end
|
208
208
|
end
|
209
|
+
|
210
|
+
ANCESTRY_UNCAST_TYPES = [:string, :uuid, :text].freeze
|
211
|
+
if ActiveSupport::VERSION::STRING < "4.0"
|
212
|
+
def primary_key_is_an_integer?
|
213
|
+
if defined?(@primary_key_is_an_integer)
|
214
|
+
@primary_key_is_an_integer
|
215
|
+
else
|
216
|
+
@primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(columns_hash[primary_key.to_s].type)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
else
|
220
|
+
def primary_key_is_an_integer?
|
221
|
+
if defined?(@primary_key_is_an_integer)
|
222
|
+
@primary_key_is_an_integer
|
223
|
+
else
|
224
|
+
@primary_key_is_an_integer = !ANCESTRY_UNCAST_TYPES.include?(type_for_attribute(primary_key))
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
209
228
|
end
|
210
229
|
end
|
@@ -46,7 +46,10 @@ module Ancestry
|
|
46
46
|
scope :siblings_of, lambda { |object| where(sibling_conditions(object)) }
|
47
47
|
scope :ordered_by_ancestry, Proc.new { |order|
|
48
48
|
if %w(mysql mysql2 sqlite sqlite3 postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::MAJOR >= 5
|
49
|
-
reorder(
|
49
|
+
reorder(
|
50
|
+
Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
|
51
|
+
order
|
52
|
+
)
|
50
53
|
else
|
51
54
|
reorder(Arel.sql("(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)
|
52
55
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module Ancestry
|
2
2
|
module InstanceMethods
|
3
|
+
BEFORE_LAST_SAVE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_before_last_save' : '_was'
|
4
|
+
IN_DATABASE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_in_database' : '_was'
|
5
|
+
|
3
6
|
# Validate that the ancestors don't include itself
|
4
7
|
def ancestry_exclude_self
|
5
8
|
errors.add(:base, "#{self.class.name.humanize} cannot be a descendant of itself.") if ancestor_ids.include? self.id
|
6
9
|
end
|
7
10
|
|
8
|
-
# Update descendants with new ancestry
|
11
|
+
# Update descendants with new ancestry (before save)
|
9
12
|
def update_descendants_with_new_ancestry
|
10
13
|
# If enabled and node is existing and ancestry was updated and the new ancestry is sane ...
|
11
14
|
if !ancestry_callbacks_disabled? && !new_record? && ancestry_changed? && sane_ancestry?
|
@@ -16,7 +19,9 @@ module Ancestry
|
|
16
19
|
descendant.update_attribute(
|
17
20
|
self.ancestry_base_class.ancestry_column,
|
18
21
|
descendant.read_attribute(descendant.class.ancestry_column).gsub(
|
22
|
+
# child_ancestry_was
|
19
23
|
/^#{self.child_ancestry}/,
|
24
|
+
# future child_ancestry
|
20
25
|
if ancestors? then "#{read_attribute self.class.ancestry_column }/#{id}" else id.to_s end
|
21
26
|
)
|
22
27
|
)
|
@@ -25,7 +30,7 @@ module Ancestry
|
|
25
30
|
end
|
26
31
|
end
|
27
32
|
|
28
|
-
# Apply orphan strategy
|
33
|
+
# Apply orphan strategy (before destroy - no changes)
|
29
34
|
def apply_orphan_strategy
|
30
35
|
if !ancestry_callbacks_disabled? && !new_record?
|
31
36
|
case self.ancestry_base_class.orphan_strategy
|
@@ -35,6 +40,7 @@ module Ancestry
|
|
35
40
|
new_ancestry = if descendant.ancestry == child_ancestry
|
36
41
|
nil
|
37
42
|
else
|
43
|
+
# child_ancestry did not change so child_ancestry_was will work here
|
38
44
|
descendant.ancestry.gsub(/^#{child_ancestry}\//, '')
|
39
45
|
end
|
40
46
|
descendant.update_attribute descendant.class.ancestry_column, new_ancestry
|
@@ -61,7 +67,7 @@ module Ancestry
|
|
61
67
|
end
|
62
68
|
end
|
63
69
|
|
64
|
-
# Touch each of this record's ancestors
|
70
|
+
# Touch each of this record's ancestors (after save)
|
65
71
|
def touch_ancestors_callback
|
66
72
|
if !ancestry_callbacks_disabled? && self.ancestry_base_class.touch_ancestors
|
67
73
|
# Touch each of the old *and* new ancestors
|
@@ -73,12 +79,17 @@ module Ancestry
|
|
73
79
|
end
|
74
80
|
end
|
75
81
|
|
76
|
-
# The ancestry value for this record's children
|
82
|
+
# The ancestry value for this record's children (before save)
|
83
|
+
# This is technically child_ancestry_was
|
77
84
|
def child_ancestry
|
78
85
|
# New records cannot have children
|
79
86
|
raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
80
87
|
|
81
|
-
if self.send("#{self.ancestry_base_class.ancestry_column}
|
88
|
+
if self.send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}").blank?
|
89
|
+
id.to_s
|
90
|
+
else
|
91
|
+
"#{self.send "#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}"}/#{id}"
|
92
|
+
end
|
82
93
|
end
|
83
94
|
|
84
95
|
# Ancestors
|
@@ -93,10 +104,6 @@ module Ancestry
|
|
93
104
|
changed.include?(self.ancestry_base_class.ancestry_column.to_s)
|
94
105
|
end
|
95
106
|
|
96
|
-
def parse_ancestry_column obj
|
97
|
-
obj.to_s.split('/').map { |id| cast_primary_key(id) }
|
98
|
-
end
|
99
|
-
|
100
107
|
def ancestor_ids
|
101
108
|
parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
|
102
109
|
end
|
@@ -109,14 +116,20 @@ module Ancestry
|
|
109
116
|
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.where ancestor_conditions
|
110
117
|
end
|
111
118
|
|
119
|
+
# deprecate
|
112
120
|
def ancestor_was_conditions
|
113
|
-
{primary_key_with_table =>
|
121
|
+
{primary_key_with_table => ancestor_ids_before_last_save}
|
114
122
|
end
|
115
123
|
|
124
|
+
# deprecated - probably don't want to use anymore
|
116
125
|
def ancestor_ids_was
|
117
126
|
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}_was"))
|
118
127
|
end
|
119
128
|
|
129
|
+
def ancestor_ids_before_last_save
|
130
|
+
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}"))
|
131
|
+
end
|
132
|
+
|
120
133
|
def path_ids
|
121
134
|
ancestor_ids + [id]
|
122
135
|
end
|
@@ -143,6 +156,8 @@ module Ancestry
|
|
143
156
|
|
144
157
|
# Parent
|
145
158
|
|
159
|
+
# currently parent= does not work in after save callbacks
|
160
|
+
# assuming that parent hasn't changed
|
146
161
|
def parent= parent
|
147
162
|
write_attribute(self.ancestry_base_class.ancestry_column, if parent.nil? then nil else parent.child_ancestry end)
|
148
163
|
end
|
@@ -288,16 +303,10 @@ module Ancestry
|
|
288
303
|
|
289
304
|
private
|
290
305
|
|
291
|
-
def
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
key.to_i
|
296
|
-
end
|
297
|
-
end
|
298
|
-
|
299
|
-
def primary_key_type
|
300
|
-
@primary_key_type ||= column_for_attribute(self.class.primary_key).type
|
306
|
+
def parse_ancestry_column obj
|
307
|
+
obj_ids = obj.to_s.split('/')
|
308
|
+
obj_ids.map!(&:to_i) if self.class.primary_key_is_an_integer?
|
309
|
+
obj_ids
|
301
310
|
end
|
302
311
|
|
303
312
|
def unscoped_descendants
|
@@ -306,9 +315,10 @@ module Ancestry
|
|
306
315
|
end
|
307
316
|
end
|
308
317
|
|
318
|
+
# works with after save context (hence before_last_save)
|
309
319
|
def unscoped_current_and_previous_ancestors
|
310
320
|
unscoped_where do |scope|
|
311
|
-
scope.where id: (ancestor_ids +
|
321
|
+
scope.where id: (ancestor_ids + ancestor_ids_before_last_save).uniq
|
312
322
|
end
|
313
323
|
end
|
314
324
|
|
@@ -41,7 +41,7 @@ module Ancestry
|
|
41
41
|
def subtree_conditions(object)
|
42
42
|
t = arel_table
|
43
43
|
node = to_node(object)
|
44
|
-
descendant_conditions(
|
44
|
+
descendant_conditions(node).or(t[primary_key].eq(node.id))
|
45
45
|
end
|
46
46
|
|
47
47
|
def sibling_conditions(object)
|
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: 3.0.
|
4
|
+
version: 3.0.3
|
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: 2018-
|
12
|
+
date: 2018-10-23 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -134,7 +134,7 @@ files:
|
|
134
134
|
- lib/ancestry/instance_methods.rb
|
135
135
|
- lib/ancestry/materialized_path.rb
|
136
136
|
- lib/ancestry/version.rb
|
137
|
-
homepage:
|
137
|
+
homepage: https://github.com/stefankroes/ancestry
|
138
138
|
licenses:
|
139
139
|
- MIT
|
140
140
|
metadata:
|
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
158
|
version: '0'
|
159
159
|
requirements: []
|
160
160
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.6.
|
161
|
+
rubygems_version: 2.6.14.1
|
162
162
|
signing_key:
|
163
163
|
specification_version: 4
|
164
164
|
summary: Organize ActiveRecord model into a tree structure
|