ancestry 3.0.2 → 3.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|