ancestry 3.0.7 → 3.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/README.md +82 -31
- data/ancestry.gemspec +5 -7
- data/lib/ancestry.rb +0 -1
- data/lib/ancestry/class_methods.rb +22 -18
- data/lib/ancestry/has_ancestry.rb +14 -22
- data/lib/ancestry/instance_methods.rb +21 -113
- data/lib/ancestry/materialized_path.rb +100 -17
- data/lib/ancestry/version.rb +1 -1
- metadata +10 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9455bbdda28f2f0084ff4b1e96af32ca5d6b3efe40ae062c51f75b3fee84fb30
|
4
|
+
data.tar.gz: 5b06653e17bbe2a238c4d49d9147dd79f2156c33432298290b5b17da190ce203
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cb481c338a78cd5c294a40ccc43ccbad27232cc0a646b4827fd2446ad3d3fd626410f4a35d3b9361b367c2474ba229113aa191ba7799ddcc7930b1d29eab106
|
7
|
+
data.tar.gz: 6876e974e00c0ee4c5bb4ea4facbfb9ce12fe04eebfab2bc4623d6a74c9591781d3945b4999437054a81fb1ea82a656822c679e38c434541173ee7d192d664dc
|
data/README.md
CHANGED
@@ -4,12 +4,12 @@
|
|
4
4
|
|
5
5
|
Ancestry is a gem that allows the records of a Ruby on Rails
|
6
6
|
ActiveRecord model to be organised as a tree structure (or hierarchy). It uses
|
7
|
-
a single database column,
|
8
|
-
relations (ancestors, parent, root, children, siblings, descendants) and all
|
9
|
-
of them
|
7
|
+
a single database column, employing the materialised path pattern. It exposes all the standard tree structure
|
8
|
+
relations (ancestors, parent, root, children, siblings, descendants) and allows all
|
9
|
+
of them to be fetched in a single SQL query. Additional features are STI
|
10
10
|
support, scopes, depth caching, depth constraints, easy migration from older
|
11
11
|
gems, integrity checking, integrity restoration, arrangement of
|
12
|
-
(sub)
|
12
|
+
(sub)trees into hashes, and various strategies for dealing with orphaned
|
13
13
|
records.
|
14
14
|
|
15
15
|
NOTE:
|
@@ -55,7 +55,7 @@ $ rake db:migrate
|
|
55
55
|
|
56
56
|
|
57
57
|
## Add ancestry to your model
|
58
|
-
* Add to
|
58
|
+
* Add to app/models/[model.rb]:
|
59
59
|
|
60
60
|
```ruby
|
61
61
|
# app/models/[model.rb]
|
@@ -69,9 +69,9 @@ Your model is now a tree!
|
|
69
69
|
|
70
70
|
# Using acts_as_tree instead of has_ancestry
|
71
71
|
|
72
|
-
In version 1.2.0 the **acts_as_tree** method was **renamed to has_ancestry**
|
72
|
+
In version 1.2.0, the **acts_as_tree** method was **renamed to has_ancestry**
|
73
73
|
in order to allow usage of both the acts_as_tree gem and the ancestry gem in a
|
74
|
-
single application.
|
74
|
+
single application. The `acts_as_tree` method will continue to be supported in the future.
|
75
75
|
|
76
76
|
# Organising records into a tree
|
77
77
|
|
@@ -107,13 +107,13 @@ To navigate an Ancestry model, use the following instance methods:
|
|
107
107
|
|`ancestors?` |true if the record has ancestors (aka not a root node)|
|
108
108
|
|`ancestor_ids` |ancestor ids of the record|
|
109
109
|
|`path` |path of the record, starting with the root and ending with self|
|
110
|
-
|`path_ids` |a list the path ids, starting with the root id and ending with the node's own id|
|
110
|
+
|`path_ids` |a list of the path ids, starting with the root id and ending with the node's own id|
|
111
111
|
|`children` |direct children of the record|
|
112
112
|
|`child_ids` |direct children's ids|
|
113
113
|
|`has_parent?` <br/> `ancestors?` |true if the record has a parent, false otherwise|
|
114
114
|
|`has_children?` <br/> `children?` |true if the record has any children, false otherwise|
|
115
115
|
|`is_childless?` <br/> `childless?` |true is the record has no children, false otherwise|
|
116
|
-
|`siblings` |siblings of the record, the record itself
|
116
|
+
|`siblings` |siblings of the record, including the record itself*|
|
117
117
|
|`sibling_ids` |sibling ids|
|
118
118
|
|`has_siblings?` <br/> `siblings?` |true if the record's parent has more than one child|
|
119
119
|
|`is_only_child?` <br/> `only_child?` |true if the record is the only child of its parent|
|
@@ -123,7 +123,7 @@ To navigate an Ancestry model, use the following instance methods:
|
|
123
123
|
|`indirect_ids` |indirect children's ids of the record|
|
124
124
|
|`subtree` |the model on descendants and itself|
|
125
125
|
|`subtree_ids` |a list of all ids in the record's subtree|
|
126
|
-
|`depth` |the depth of the node
|
126
|
+
|`depth` |the depth of the node (root nodes are at depth 0)|
|
127
127
|
|
128
128
|
\* If the record is a root, other root records are considered siblings
|
129
129
|
\* Siblings returns the record itself
|
@@ -139,6 +139,57 @@ There are also instance methods to determine the relationship between 2 nodes:
|
|
139
139
|
|`descendant_of?(node)` | node is one of this record's ancestors|
|
140
140
|
|`indirect_of?(node)` | node is one of this record's ancestors but not a parent|
|
141
141
|
|
142
|
+
## Visual guide for navigation
|
143
|
+
|
144
|
+
In all examples the node with the large border is the reference node, the node
|
145
|
+
from which the navigation method is invoked. The yellow nodes are the nodes
|
146
|
+
returned by the method.
|
147
|
+
|
148
|
+
<table>
|
149
|
+
<tr>
|
150
|
+
<td>
|
151
|
+
<p align="center">parent</p>
|
152
|
+
<img src="img/parent.png" alt="parent"/>
|
153
|
+
</td>
|
154
|
+
<td>
|
155
|
+
<p align="center">root</p>
|
156
|
+
<img src="img/root.png" alt="root"/>
|
157
|
+
</td>
|
158
|
+
<td>
|
159
|
+
<p align="center">ancestors</p>
|
160
|
+
<img src="img/ancestors.png" alt="ancestors"/>
|
161
|
+
</td>
|
162
|
+
</tr>
|
163
|
+
<tr>
|
164
|
+
<td>
|
165
|
+
<p align="center">path</p>
|
166
|
+
<img src="img/path.png" alt="path"/>
|
167
|
+
</td>
|
168
|
+
<td>
|
169
|
+
<p align="center">children</p>
|
170
|
+
<img src="img/children.png" alt="children"/>
|
171
|
+
</td>
|
172
|
+
<td>
|
173
|
+
<p align="center">siblings</p>
|
174
|
+
<img src="img/siblings.png" alt="siblings"/>
|
175
|
+
</td>
|
176
|
+
</tr>
|
177
|
+
<tr>
|
178
|
+
<td>
|
179
|
+
<p align="center">descendants</p>
|
180
|
+
<img src="img/descendants.png" alt="descendants"/>
|
181
|
+
</td>
|
182
|
+
<td>
|
183
|
+
<p align="center">indirects</p>
|
184
|
+
<img src="img/indirects.png" alt="indirects"/>
|
185
|
+
</td>
|
186
|
+
<td>
|
187
|
+
<p align="center">subtree</p>
|
188
|
+
<img src="img/subtree.png" alt="subtree"/>
|
189
|
+
</td>
|
190
|
+
</tr>
|
191
|
+
</table>
|
192
|
+
|
142
193
|
# Options for `has_ancestry`
|
143
194
|
|
144
195
|
The has_ancestry method supports the following options:
|
@@ -148,23 +199,23 @@ The has_ancestry method supports the following options:
|
|
148
199
|
:destroy All children are destroyed as well (default)
|
149
200
|
:rootify The children of the destroyed node become root nodes
|
150
201
|
:restrict An AncestryException is raised if any children exist
|
151
|
-
:adopt The orphan subtree is added to the parent of the deleted node
|
152
|
-
If the deleted node is Root, then rootify the orphan subtree
|
202
|
+
:adopt The orphan subtree is added to the parent of the deleted node
|
203
|
+
If the deleted node is Root, then rootify the orphan subtree
|
153
204
|
:cache_depth Cache the depth of each node in the 'ancestry_depth' column (default: false)
|
154
205
|
If you turn depth_caching on for an existing model:
|
155
206
|
- Migrate: add_column [table], :ancestry_depth, :integer, :default => 0
|
156
207
|
- Build cache: TreeNode.rebuild_depth_cache!
|
157
208
|
:depth_cache_column Pass in a symbol to store depth cache in a different column
|
158
|
-
:primary_key_format Supply a regular expression that matches the format of your primary key
|
159
|
-
By default, primary keys only match integers ([0-9]+)
|
209
|
+
:primary_key_format Supply a regular expression that matches the format of your primary key
|
210
|
+
By default, primary keys only match integers ([0-9]+)
|
160
211
|
:touch Instruct Ancestry to touch the ancestors of a node when it changes, to
|
161
212
|
invalidate nested key-based caches. (default: false)
|
162
213
|
|
163
214
|
# (Named) Scopes
|
164
215
|
|
165
|
-
Where possible, the navigation methods return scopes instead of records
|
216
|
+
Where possible, the navigation methods return scopes instead of records. This
|
166
217
|
means additional ordering, conditions, limits, etc. can be applied and that
|
167
|
-
the result can be either retrieved, counted or checked for existence. For
|
218
|
+
the result can be either retrieved, counted, or checked for existence. For
|
168
219
|
example:
|
169
220
|
|
170
221
|
```ruby
|
@@ -237,7 +288,7 @@ on type for that.
|
|
237
288
|
# Arrangement
|
238
289
|
|
239
290
|
Ancestry can arrange an entire subtree into nested hashes for easy navigation
|
240
|
-
after retrieval from the database. TreeNode.arrange could for example return:
|
291
|
+
after retrieval from the database. `TreeNode.arrange` could for example return:
|
241
292
|
|
242
293
|
```ruby
|
243
294
|
{
|
@@ -250,14 +301,14 @@ after retrieval from the database. TreeNode.arrange could for example return:
|
|
250
301
|
}
|
251
302
|
```
|
252
303
|
|
253
|
-
The arrange method also works on a scoped class, for example:
|
304
|
+
The `arrange` method also works on a scoped class, for example:
|
254
305
|
|
255
306
|
```ruby
|
256
307
|
TreeNode.find_by_name('Crunchy').subtree.arrange
|
257
308
|
```
|
258
309
|
|
259
|
-
The arrange method takes `ActiveRecord` find options. If you want your hashes to
|
260
|
-
be ordered, you should pass the order to the arrange method instead of to the
|
310
|
+
The `arrange` method takes `ActiveRecord` find options. If you want your hashes to
|
311
|
+
be ordered, you should pass the order to the `arrange` method instead of to the
|
261
312
|
scope. example:
|
262
313
|
|
263
314
|
```ruby
|
@@ -266,7 +317,7 @@ TreeNode.find_by_name('Crunchy').subtree.arrange(:order => :name)
|
|
266
317
|
|
267
318
|
To get the arranged nodes as a nested array of hashes for serialization:
|
268
319
|
|
269
|
-
TreeNode.arrange_serializable
|
320
|
+
`TreeNode.arrange_serializable`
|
270
321
|
|
271
322
|
```ruby
|
272
323
|
[
|
@@ -299,15 +350,15 @@ TreeNode.arrange_serializable do |parent, children|
|
|
299
350
|
end
|
300
351
|
```
|
301
352
|
|
302
|
-
The result of arrange_serializable can easily be serialized to json with
|
353
|
+
The result of `arrange_serializable` can easily be serialized to json with
|
303
354
|
`to_json`, or some other format:
|
304
355
|
|
305
356
|
```
|
306
357
|
TreeNode.arrange_serializable.to_json
|
307
358
|
```
|
308
359
|
|
309
|
-
You can also pass the order to the arrange_serializable method just as you can
|
310
|
-
pass it to the arrange method:
|
360
|
+
You can also pass the order to the `arrange_serializable` method just as you can
|
361
|
+
pass it to the `arrange` method:
|
311
362
|
|
312
363
|
```
|
313
364
|
TreeNode.arrange_serializable(:order => :name)
|
@@ -329,8 +380,8 @@ the order of siblings depends on their order in the original array.
|
|
329
380
|
|
330
381
|
Most current tree plugins use a parent_id column (has_ancestry,
|
331
382
|
awesome_nested_set, better_nested_set, acts_as_nested_set). With ancestry it is
|
332
|
-
easy to migrate from any of these plugins
|
333
|
-
build_ancestry_from_parent_ids
|
383
|
+
easy to migrate from any of these plugins. To do so, use the
|
384
|
+
`build_ancestry_from_parent_ids!` method on your ancestry model. These steps
|
334
385
|
provide a more detailed explanation:
|
335
386
|
|
336
387
|
1. Add ancestry column to your table
|
@@ -352,7 +403,7 @@ provide a more detailed explanation:
|
|
352
403
|
|
353
404
|
|
354
405
|
4. Generate ancestry columns
|
355
|
-
* In '
|
406
|
+
* In 'rails console': **[model].build_ancestry_from_parent_ids!**
|
356
407
|
* Make sure it worked ok: **[model].check_ancestry_integrity!**
|
357
408
|
|
358
409
|
|
@@ -371,14 +422,14 @@ provide a more detailed explanation:
|
|
371
422
|
|
372
423
|
I don't see any way Ancestry tree integrity could get compromised without
|
373
424
|
explicitly setting cyclic parents or invalid ancestry and circumventing
|
374
|
-
validation with update_attribute
|
425
|
+
validation with update_attribute. If you do, please let me know.
|
375
426
|
|
376
427
|
Ancestry includes some methods for detecting integrity problems and restoring
|
377
|
-
integrity just to be sure. To check integrity use:
|
378
|
-
[Model].check_ancestry_integrity
|
428
|
+
integrity just to be sure. To check integrity, use:
|
429
|
+
`[Model].check_ancestry_integrity!`. An AncestryIntegrityException will be
|
379
430
|
raised if there are any problems. You can also specify :report => :list to
|
380
431
|
return an array of exceptions or :report => :echo to echo any error messages.
|
381
|
-
To restore integrity use: [Model].restore_ancestry_integrity
|
432
|
+
To restore integrity use: `[Model].restore_ancestry_integrity!`.
|
382
433
|
|
383
434
|
For example, from IRB:
|
384
435
|
|
data/ancestry.gemspec
CHANGED
@@ -42,12 +42,10 @@ EOF
|
|
42
42
|
'README.md'
|
43
43
|
]
|
44
44
|
|
45
|
-
s.required_ruby_version = '>=
|
46
|
-
s.add_runtime_dependency 'activerecord', '>=
|
47
|
-
s.add_development_dependency '
|
48
|
-
s.add_development_dependency 'yard'
|
49
|
-
s.add_development_dependency 'rake', '~> 10.0'
|
50
|
-
s.add_development_dependency 'test-unit'
|
45
|
+
s.required_ruby_version = '>= 2.0.0'
|
46
|
+
s.add_runtime_dependency 'activerecord', '>= 4.2.0'
|
47
|
+
s.add_development_dependency 'appraisal'
|
51
48
|
s.add_development_dependency 'minitest'
|
52
|
-
s.add_development_dependency '
|
49
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
50
|
+
s.add_development_dependency 'yard'
|
53
51
|
end
|
data/lib/ancestry.rb
CHANGED
@@ -2,7 +2,11 @@ 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)
|
5
|
+
if object.is_a?(self.ancestry_base_class)
|
6
|
+
object
|
7
|
+
else
|
8
|
+
unscoped_where { |scope| scope.find(object.try(primary_key) || object) }
|
9
|
+
end
|
6
10
|
end
|
7
11
|
|
8
12
|
# Scope on relative depth options
|
@@ -129,7 +133,7 @@ module Ancestry
|
|
129
133
|
|
130
134
|
# Integrity restoration
|
131
135
|
def restore_ancestry_integrity!
|
132
|
-
|
136
|
+
parent_ids = {}
|
133
137
|
# Wrap the whole thing in a transaction ...
|
134
138
|
self.ancestry_base_class.transaction do
|
135
139
|
unscoped_where do |scope|
|
@@ -138,29 +142,29 @@ module Ancestry
|
|
138
142
|
# ... set its ancestry to nil if invalid
|
139
143
|
if !node.valid? and !node.errors[node.class.ancestry_column].blank?
|
140
144
|
node.without_ancestry_callbacks do
|
141
|
-
node.update_attribute
|
145
|
+
node.update_attribute :ancestor_ids, []
|
142
146
|
end
|
143
147
|
end
|
144
|
-
# ... save parent of this node in
|
145
|
-
|
148
|
+
# ... save parent id of this node in parent_ids array if it exists
|
149
|
+
parent_ids[node.id] = node.parent_id if exists? node.parent_id
|
146
150
|
|
147
151
|
# Reset parent id in array to nil if it introduces a cycle
|
148
|
-
|
149
|
-
until
|
150
|
-
|
152
|
+
parent_id = parent_ids[node.id]
|
153
|
+
until parent_id.nil? || parent_id == node.id
|
154
|
+
parent_id = parent_ids[parent_id]
|
151
155
|
end
|
152
|
-
|
156
|
+
parent_ids[node.id] = nil if parent_id == node.id
|
153
157
|
end
|
154
158
|
|
155
159
|
# For each node ...
|
156
160
|
scope.find_each do |node|
|
157
|
-
# ... rebuild ancestry from
|
158
|
-
|
159
|
-
until
|
160
|
-
|
161
|
+
# ... rebuild ancestry from parent_ids array
|
162
|
+
ancestor_ids, parent_id = [], parent_ids[node.id]
|
163
|
+
until parent_id.nil?
|
164
|
+
ancestor_ids, parent_id = [parent_id] + ancestor_ids, parent_ids[parent_id]
|
161
165
|
end
|
162
166
|
node.without_ancestry_callbacks do
|
163
|
-
node.update_attribute
|
167
|
+
node.update_attribute :ancestor_ids, ancestor_ids
|
164
168
|
end
|
165
169
|
end
|
166
170
|
end
|
@@ -168,13 +172,13 @@ module Ancestry
|
|
168
172
|
end
|
169
173
|
|
170
174
|
# Build ancestry from parent id's for migration purposes
|
171
|
-
def build_ancestry_from_parent_ids! parent_id = nil,
|
175
|
+
def build_ancestry_from_parent_ids! column=:parent_id, parent_id = nil, ancestor_ids = []
|
172
176
|
unscoped_where do |scope|
|
173
|
-
scope.where(
|
177
|
+
scope.where(column => parent_id).find_each do |node|
|
174
178
|
node.without_ancestry_callbacks do
|
175
|
-
node.update_attribute
|
179
|
+
node.update_attribute :ancestor_ids, ancestor_ids
|
176
180
|
end
|
177
|
-
build_ancestry_from_parent_ids! node.id,
|
181
|
+
build_ancestry_from_parent_ids! column, node.id, ancestor_ids + [node.id]
|
178
182
|
end
|
179
183
|
end
|
180
184
|
end
|
@@ -4,7 +4,7 @@ module Ancestry
|
|
4
4
|
# Check options
|
5
5
|
raise Ancestry::AncestryException.new("Options for has_ancestry must be in a 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].include? key
|
7
|
+
unless [:ancestry_column, :orphan_strategy, :cache_depth, :depth_cache_column, :touch, :counter_cache, :primary_key_format].include? key
|
8
8
|
raise Ancestry::AncestryException.new("Unknown option for has_ancestry: #{key.inspect} => #{value.inspect}.")
|
9
9
|
end
|
10
10
|
end
|
@@ -27,6 +27,7 @@ module Ancestry
|
|
27
27
|
# Include dynamic class methods
|
28
28
|
extend Ancestry::ClassMethods
|
29
29
|
|
30
|
+
validates_format_of self.ancestry_column, :with => derive_ancestry_pattern(options[:primary_key_format]), :allow_nil => true
|
30
31
|
extend Ancestry::MaterializedPath
|
31
32
|
|
32
33
|
# Create orphan strategy accessor and set to option or default (writer comes from DynamicClassMethods)
|
@@ -36,27 +37,6 @@ module Ancestry
|
|
36
37
|
# Validate that the ancestor ids don't include own id
|
37
38
|
validate :ancestry_exclude_self
|
38
39
|
|
39
|
-
# Named scopes
|
40
|
-
scope :roots, lambda { where(root_conditions) }
|
41
|
-
scope :ancestors_of, lambda { |object| where(ancestor_conditions(object)) }
|
42
|
-
scope :children_of, lambda { |object| where(child_conditions(object)) }
|
43
|
-
scope :indirects_of, lambda { |object| where(indirect_conditions(object)) }
|
44
|
-
scope :descendants_of, lambda { |object| where(descendant_conditions(object)) }
|
45
|
-
scope :subtree_of, lambda { |object| where(subtree_conditions(object)) }
|
46
|
-
scope :siblings_of, lambda { |object| where(sibling_conditions(object)) }
|
47
|
-
scope :ordered_by_ancestry, Proc.new { |order|
|
48
|
-
if %w(mysql mysql2 sqlite sqlite3 postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::MAJOR >= 5
|
49
|
-
reorder(
|
50
|
-
Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
|
51
|
-
order
|
52
|
-
)
|
53
|
-
else
|
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)
|
55
|
-
end
|
56
|
-
}
|
57
|
-
scope :ordered_by_ancestry_and, Proc.new { |order| ordered_by_ancestry(order) }
|
58
|
-
scope :path_of, lambda { |object| to_node(object).path }
|
59
|
-
|
60
40
|
# Update descendants with new ancestry before save
|
61
41
|
before_save :update_descendants_with_new_ancestry
|
62
42
|
|
@@ -114,6 +94,18 @@ module Ancestry
|
|
114
94
|
return super if defined?(super)
|
115
95
|
has_ancestry(*args)
|
116
96
|
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def derive_ancestry_pattern(primary_key_format, delimiter = '/')
|
101
|
+
primary_key_format ||= '[0-9]+'
|
102
|
+
|
103
|
+
if primary_key_format.to_s.include?('\A')
|
104
|
+
primary_key_format
|
105
|
+
else
|
106
|
+
/\A#{primary_key_format}(#{delimiter}#{primary_key_format})*\Z/
|
107
|
+
end
|
108
|
+
end
|
117
109
|
end
|
118
110
|
end
|
119
111
|
|
@@ -1,11 +1,8 @@
|
|
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
|
-
|
6
3
|
# Validate that the ancestors don't include itself
|
7
4
|
def ancestry_exclude_self
|
8
|
-
errors.add(:base, "#{self.class.
|
5
|
+
errors.add(:base, "#{self.class.model_name.human} cannot be a descendant of itself.") if ancestor_ids.include? self.id
|
9
6
|
end
|
10
7
|
|
11
8
|
# Update descendants with new ancestry (before save)
|
@@ -16,15 +13,8 @@ module Ancestry
|
|
16
13
|
unscoped_descendants.each do |descendant|
|
17
14
|
# ... replace old ancestry with new ancestry
|
18
15
|
descendant.without_ancestry_callbacks do
|
19
|
-
descendant.
|
20
|
-
|
21
|
-
descendant.read_attribute(descendant.class.ancestry_column).gsub(
|
22
|
-
# child_ancestry_was
|
23
|
-
/^#{self.child_ancestry}/,
|
24
|
-
# future child_ancestry
|
25
|
-
if ancestors? then "#{read_attribute self.class.ancestry_column }/#{id}" else id.to_s end
|
26
|
-
)
|
27
|
-
)
|
16
|
+
new_ancestor_ids = path_ids + (descendant.ancestor_ids - path_ids_in_database)
|
17
|
+
descendant.update_attribute(:ancestor_ids, new_ancestor_ids)
|
28
18
|
end
|
29
19
|
end
|
30
20
|
end
|
@@ -37,13 +27,7 @@ module Ancestry
|
|
37
27
|
when :rootify # make all children root if orphan strategy is rootify
|
38
28
|
unscoped_descendants.each do |descendant|
|
39
29
|
descendant.without_ancestry_callbacks do
|
40
|
-
|
41
|
-
nil
|
42
|
-
else
|
43
|
-
# child_ancestry did not change so child_ancestry_was will work here
|
44
|
-
descendant.ancestry.gsub(/^#{child_ancestry}\//, '')
|
45
|
-
end
|
46
|
-
descendant.update_attribute descendant.class.ancestry_column, new_ancestry
|
30
|
+
descendant.update_attribute :ancestor_ids, descendant.ancestor_ids - path_ids
|
47
31
|
end
|
48
32
|
end
|
49
33
|
when :destroy # destroy all descendants if orphan strategy is destroy
|
@@ -55,10 +39,7 @@ module Ancestry
|
|
55
39
|
when :adopt # make child elements of this node, child of its parent
|
56
40
|
descendants.each do |descendant|
|
57
41
|
descendant.without_ancestry_callbacks do
|
58
|
-
|
59
|
-
# check for empty string if it's then set to nil
|
60
|
-
new_ancestry = nil if new_ancestry.empty?
|
61
|
-
descendant.update_attribute descendant.class.ancestry_column, new_ancestry || nil
|
42
|
+
descendant.update_attribute :ancestor_ids, descendant.ancestor_ids.delete_if { |x| x == self.id }
|
62
43
|
end
|
63
44
|
end
|
64
45
|
when :restrict # throw an exception if it has children
|
@@ -79,19 +60,6 @@ module Ancestry
|
|
79
60
|
end
|
80
61
|
end
|
81
62
|
|
82
|
-
# The ancestry value for this record's children (before save)
|
83
|
-
# This is technically child_ancestry_was
|
84
|
-
def child_ancestry
|
85
|
-
# New records cannot have children
|
86
|
-
raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
87
|
-
|
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
|
93
|
-
end
|
94
|
-
|
95
63
|
# Counter Cache
|
96
64
|
def increase_parent_counter_cache
|
97
65
|
self.class.increment_counter _counter_cache_column, parent_id
|
@@ -133,8 +101,7 @@ module Ancestry
|
|
133
101
|
# Ancestors
|
134
102
|
|
135
103
|
def ancestors?
|
136
|
-
|
137
|
-
read_attribute(self.ancestry_base_class.ancestry_column).present?
|
104
|
+
ancestor_ids.present?
|
138
105
|
end
|
139
106
|
alias :has_parent? :ancestors?
|
140
107
|
|
@@ -149,50 +116,21 @@ module Ancestry
|
|
149
116
|
end
|
150
117
|
end
|
151
118
|
|
152
|
-
def ancestor_ids
|
153
|
-
parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
|
154
|
-
end
|
155
|
-
|
156
|
-
def ancestor_conditions
|
157
|
-
self.ancestry_base_class.ancestor_conditions(self)
|
158
|
-
end
|
159
|
-
|
160
119
|
def ancestors depth_options = {}
|
161
120
|
return self.ancestry_base_class.none unless ancestors?
|
162
|
-
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.
|
163
|
-
end
|
164
|
-
|
165
|
-
# deprecate
|
166
|
-
def ancestor_was_conditions
|
167
|
-
{primary_key_with_table => ancestor_ids_before_last_save}
|
168
|
-
end
|
169
|
-
|
170
|
-
# deprecated - probably don't want to use anymore
|
171
|
-
def ancestor_ids_was
|
172
|
-
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}_was"))
|
173
|
-
end
|
174
|
-
|
175
|
-
def ancestor_ids_before_last_save
|
176
|
-
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}"))
|
177
|
-
end
|
178
|
-
|
179
|
-
def parent_id_before_last_save
|
180
|
-
ancestry_was = send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}")
|
181
|
-
return unless ancestry_was.present?
|
182
|
-
|
183
|
-
ancestry_was.split(ANCESTRY_DELIMITER).last.to_i
|
121
|
+
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.ancestors_of(self)
|
184
122
|
end
|
185
123
|
|
186
124
|
def path_ids
|
187
125
|
ancestor_ids + [id]
|
188
126
|
end
|
189
127
|
|
190
|
-
def
|
191
|
-
|
128
|
+
def path_ids_in_database
|
129
|
+
ancestor_ids_in_database + [id]
|
192
130
|
end
|
193
131
|
|
194
132
|
def path depth_options = {}
|
195
|
-
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.
|
133
|
+
self.ancestry_base_class.scope_depth(depth_options, depth).ordered_by_ancestry.inpath_of(self)
|
196
134
|
end
|
197
135
|
|
198
136
|
def depth
|
@@ -212,7 +150,7 @@ module Ancestry
|
|
212
150
|
# currently parent= does not work in after save callbacks
|
213
151
|
# assuming that parent hasn't changed
|
214
152
|
def parent= parent
|
215
|
-
|
153
|
+
self.ancestor_ids = parent ? parent.path_ids : []
|
216
154
|
end
|
217
155
|
|
218
156
|
def parent_id= new_parent_id
|
@@ -222,15 +160,12 @@ module Ancestry
|
|
222
160
|
def parent_id
|
223
161
|
ancestor_ids.last if ancestors?
|
224
162
|
end
|
163
|
+
alias :parent_id? :ancestors?
|
225
164
|
|
226
165
|
def parent
|
227
166
|
unscoped_find(parent_id) if ancestors?
|
228
167
|
end
|
229
168
|
|
230
|
-
def parent_id?
|
231
|
-
ancestors?
|
232
|
-
end
|
233
|
-
|
234
169
|
def parent_of?(node)
|
235
170
|
self.id == node.parent_id
|
236
171
|
end
|
@@ -246,7 +181,7 @@ module Ancestry
|
|
246
181
|
end
|
247
182
|
|
248
183
|
def is_root?
|
249
|
-
|
184
|
+
!ancestors?
|
250
185
|
end
|
251
186
|
alias :root? :is_root?
|
252
187
|
|
@@ -256,12 +191,8 @@ module Ancestry
|
|
256
191
|
|
257
192
|
# Children
|
258
193
|
|
259
|
-
def child_conditions
|
260
|
-
self.ancestry_base_class.child_conditions(self)
|
261
|
-
end
|
262
|
-
|
263
194
|
def children
|
264
|
-
self.ancestry_base_class.
|
195
|
+
self.ancestry_base_class.children_of(self)
|
265
196
|
end
|
266
197
|
|
267
198
|
def child_ids
|
@@ -284,14 +215,11 @@ module Ancestry
|
|
284
215
|
|
285
216
|
# Siblings
|
286
217
|
|
287
|
-
def sibling_conditions
|
288
|
-
self.ancestry_base_class.sibling_conditions(self)
|
289
|
-
end
|
290
|
-
|
291
218
|
def siblings
|
292
|
-
self.ancestry_base_class.
|
219
|
+
self.ancestry_base_class.siblings_of(self)
|
293
220
|
end
|
294
221
|
|
222
|
+
# NOTE: includes self
|
295
223
|
def sibling_ids
|
296
224
|
siblings.pluck(self.ancestry_base_class.primary_key)
|
297
225
|
end
|
@@ -307,17 +235,13 @@ module Ancestry
|
|
307
235
|
alias_method :only_child?, :is_only_child?
|
308
236
|
|
309
237
|
def sibling_of?(node)
|
310
|
-
self.
|
238
|
+
self.ancestor_ids == node.ancestor_ids
|
311
239
|
end
|
312
240
|
|
313
241
|
# Descendants
|
314
242
|
|
315
|
-
def descendant_conditions
|
316
|
-
self.ancestry_base_class.descendant_conditions(self)
|
317
|
-
end
|
318
|
-
|
319
243
|
def descendants depth_options = {}
|
320
|
-
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).
|
244
|
+
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).descendants_of(self)
|
321
245
|
end
|
322
246
|
|
323
247
|
def descendant_ids depth_options = {}
|
@@ -330,12 +254,8 @@ module Ancestry
|
|
330
254
|
|
331
255
|
# Indirects
|
332
256
|
|
333
|
-
def indirect_conditions
|
334
|
-
self.ancestry_base_class.indirect_conditions(self)
|
335
|
-
end
|
336
|
-
|
337
257
|
def indirects depth_options = {}
|
338
|
-
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).
|
258
|
+
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).indirects_of(self)
|
339
259
|
end
|
340
260
|
|
341
261
|
def indirect_ids depth_options = {}
|
@@ -348,12 +268,8 @@ module Ancestry
|
|
348
268
|
|
349
269
|
# Subtree
|
350
270
|
|
351
|
-
def subtree_conditions
|
352
|
-
self.ancestry_base_class.subtree_conditions(self)
|
353
|
-
end
|
354
|
-
|
355
271
|
def subtree depth_options = {}
|
356
|
-
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).
|
272
|
+
self.ancestry_base_class.ordered_by_ancestry.scope_depth(depth_options, depth).subtree_of(self)
|
357
273
|
end
|
358
274
|
|
359
275
|
def subtree_ids depth_options = {}
|
@@ -373,17 +289,9 @@ module Ancestry
|
|
373
289
|
end
|
374
290
|
|
375
291
|
private
|
376
|
-
ANCESTRY_DELIMITER = '/'.freeze
|
377
|
-
|
378
|
-
def parse_ancestry_column obj
|
379
|
-
return [] unless obj
|
380
|
-
obj_ids = obj.split(ANCESTRY_DELIMITER)
|
381
|
-
self.class.primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
|
382
|
-
end
|
383
|
-
|
384
292
|
def unscoped_descendants
|
385
293
|
unscoped_where do |scope|
|
386
|
-
scope.where descendant_conditions
|
294
|
+
scope.where self.ancestry_base_class.descendant_conditions(self)
|
387
295
|
end
|
388
296
|
end
|
389
297
|
|
@@ -1,44 +1,56 @@
|
|
1
1
|
module Ancestry
|
2
2
|
module MaterializedPath
|
3
|
+
BEFORE_LAST_SAVE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_before_last_save'.freeze : '_was'.freeze
|
4
|
+
IN_DATABASE_SUFFIX = ActiveRecord::VERSION::STRING >= '5.1.0' ? '_in_database'.freeze : '_was'.freeze
|
5
|
+
ANCESTRY_DELIMITER='/'.freeze
|
6
|
+
|
3
7
|
def self.extended(base)
|
4
|
-
base.validates_format_of base.ancestry_column, :with => Ancestry::ANCESTRY_PATTERN, :allow_nil => true
|
5
8
|
base.send(:include, InstanceMethods)
|
6
9
|
end
|
7
10
|
|
8
|
-
def
|
9
|
-
|
11
|
+
def path_of(object)
|
12
|
+
to_node(object).path
|
13
|
+
end
|
14
|
+
|
15
|
+
def roots
|
16
|
+
where(arel_table[ancestry_column].eq(nil))
|
10
17
|
end
|
11
18
|
|
12
|
-
def
|
19
|
+
def ancestors_of(object)
|
13
20
|
t = arel_table
|
14
21
|
node = to_node(object)
|
15
|
-
t[primary_key].in(node.ancestor_ids)
|
22
|
+
where(t[primary_key].in(node.ancestor_ids))
|
16
23
|
end
|
17
24
|
|
18
|
-
def
|
25
|
+
def inpath_of(object)
|
19
26
|
t = arel_table
|
20
27
|
node = to_node(object)
|
21
|
-
t[primary_key].in(node.path_ids)
|
28
|
+
where(t[primary_key].in(node.path_ids))
|
22
29
|
end
|
23
30
|
|
24
|
-
def
|
31
|
+
def children_of(object)
|
25
32
|
t = arel_table
|
26
33
|
node = to_node(object)
|
27
|
-
t[ancestry_column].eq(node.child_ancestry)
|
34
|
+
where(t[ancestry_column].eq(node.child_ancestry))
|
28
35
|
end
|
29
36
|
|
30
37
|
# indirect = anyone who is a descendant, but not a child
|
31
|
-
def
|
38
|
+
def indirects_of(object)
|
32
39
|
t = arel_table
|
33
40
|
node = to_node(object)
|
34
41
|
# rails has case sensitive matching.
|
35
42
|
if ActiveRecord::VERSION::MAJOR >= 5
|
36
|
-
t[ancestry_column].matches("#{node.child_ancestry}/%", nil, true)
|
43
|
+
where(t[ancestry_column].matches("#{node.child_ancestry}/%", nil, true))
|
37
44
|
else
|
38
|
-
t[ancestry_column].matches("#{node.child_ancestry}/%")
|
45
|
+
where(t[ancestry_column].matches("#{node.child_ancestry}/%"))
|
39
46
|
end
|
40
47
|
end
|
41
48
|
|
49
|
+
def descendants_of(object)
|
50
|
+
where(descendant_conditions(object))
|
51
|
+
end
|
52
|
+
|
53
|
+
# deprecated
|
42
54
|
def descendant_conditions(object)
|
43
55
|
t = arel_table
|
44
56
|
node = to_node(object)
|
@@ -50,23 +62,94 @@ module Ancestry
|
|
50
62
|
end
|
51
63
|
end
|
52
64
|
|
53
|
-
def
|
65
|
+
def subtree_of(object)
|
54
66
|
t = arel_table
|
55
67
|
node = to_node(object)
|
56
|
-
descendant_conditions(node).or(t[primary_key].eq(node.id))
|
68
|
+
where(descendant_conditions(node).or(t[primary_key].eq(node.id)))
|
57
69
|
end
|
58
70
|
|
59
|
-
def
|
71
|
+
def siblings_of(object)
|
60
72
|
t = arel_table
|
61
73
|
node = to_node(object)
|
62
|
-
t[ancestry_column].eq(node[ancestry_column])
|
74
|
+
where(t[ancestry_column].eq(node[ancestry_column]))
|
75
|
+
end
|
76
|
+
|
77
|
+
def ordered_by_ancestry(order = nil)
|
78
|
+
if %w(mysql mysql2 sqlite sqlite3).include?(connection.adapter_name.downcase)
|
79
|
+
reorder(arel_table[ancestry_column], order)
|
80
|
+
elsif %w(postgresql).include?(connection.adapter_name.downcase) && ActiveRecord::VERSION::STRING >= "6.1"
|
81
|
+
reorder(Arel::Nodes.new(arel_table[ancestry_column]).nulls_first)
|
82
|
+
else
|
83
|
+
reorder(
|
84
|
+
Arel::Nodes::Ascending.new(Arel::Nodes::NamedFunction.new('COALESCE', [arel_table[ancestry_column], Arel.sql("''")])),
|
85
|
+
order
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def ordered_by_ancestry_and(order)
|
91
|
+
ordered_by_ancestry(order)
|
63
92
|
end
|
64
93
|
|
65
94
|
module InstanceMethods
|
95
|
+
|
66
96
|
# Validates the ancestry, but can also be applied if validation is bypassed to determine if children should be affected
|
67
97
|
def sane_ancestry?
|
68
98
|
ancestry_value = read_attribute(self.ancestry_base_class.ancestry_column)
|
69
|
-
ancestry_value.nil? ||
|
99
|
+
(ancestry_value.nil? || !ancestor_ids.include?(self.id)) && valid?
|
100
|
+
end
|
101
|
+
|
102
|
+
# optimization - better to go directly to column and avoid parsing
|
103
|
+
def ancestors?
|
104
|
+
read_attribute(self.ancestry_base_class.ancestry_column).present?
|
105
|
+
end
|
106
|
+
alias :has_parent? :ancestors?
|
107
|
+
|
108
|
+
def ancestor_ids=(value)
|
109
|
+
col = self.ancestry_base_class.ancestry_column
|
110
|
+
value.present? ? write_attribute(col, value.join(ANCESTRY_DELIMITER)) : write_attribute(col, nil)
|
111
|
+
end
|
112
|
+
|
113
|
+
def ancestor_ids
|
114
|
+
parse_ancestry_column(read_attribute(self.ancestry_base_class.ancestry_column))
|
115
|
+
end
|
116
|
+
|
117
|
+
def ancestor_ids_in_database
|
118
|
+
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}"))
|
119
|
+
end
|
120
|
+
|
121
|
+
def ancestor_ids_before_last_save
|
122
|
+
parse_ancestry_column(send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}"))
|
123
|
+
end
|
124
|
+
|
125
|
+
def parent_id_before_last_save
|
126
|
+
ancestry_was = send("#{self.ancestry_base_class.ancestry_column}#{BEFORE_LAST_SAVE_SUFFIX}")
|
127
|
+
return unless ancestry_was.present?
|
128
|
+
|
129
|
+
parse_ancestry_column(ancestry_was).last
|
130
|
+
end
|
131
|
+
|
132
|
+
# optimization - better to go directly to column and avoid parsing
|
133
|
+
def sibling_of?(node)
|
134
|
+
self.read_attribute(self.ancestry_base_class.ancestry_column) == node.read_attribute(self.ancestry_base_class.ancestry_column)
|
135
|
+
end
|
136
|
+
|
137
|
+
# private (public so class methods can find it)
|
138
|
+
# The ancestry value for this record's children (before save)
|
139
|
+
# This is technically child_ancestry_was
|
140
|
+
def child_ancestry
|
141
|
+
# New records cannot have children
|
142
|
+
raise Ancestry::AncestryException.new('No child ancestry for new record. Save record before performing tree operations.') if new_record?
|
143
|
+
path_was = self.send("#{self.ancestry_base_class.ancestry_column}#{IN_DATABASE_SUFFIX}")
|
144
|
+
path_was.blank? ? id.to_s : "#{path_was}/#{id}"
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def parse_ancestry_column obj
|
150
|
+
return [] unless obj
|
151
|
+
obj_ids = obj.split(ANCESTRY_DELIMITER)
|
152
|
+
self.class.primary_key_is_an_integer? ? obj_ids.map!(&:to_i) : obj_ids
|
70
153
|
end
|
71
154
|
end
|
72
155
|
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: 3.0
|
4
|
+
version: 3.1.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: 2020-08-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -17,16 +17,16 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
20
|
+
version: 4.2.0
|
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: 4.2.0
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
|
-
name:
|
29
|
+
name: appraisal
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '0'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
43
|
+
name: minitest
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
46
|
- - ">="
|
@@ -59,44 +59,16 @@ dependencies:
|
|
59
59
|
requirements:
|
60
60
|
- - "~>"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: '
|
62
|
+
version: '13.0'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - "~>"
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: '
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: test-unit
|
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'
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: minitest
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
91
|
-
type: :development
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - ">="
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
69
|
+
version: '13.0'
|
98
70
|
- !ruby/object:Gem::Dependency
|
99
|
-
name:
|
71
|
+
name: yard
|
100
72
|
requirement: !ruby/object:Gem::Requirement
|
101
73
|
requirements:
|
102
74
|
- - ">="
|
@@ -150,7 +122,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
150
122
|
requirements:
|
151
123
|
- - ">="
|
152
124
|
- !ruby/object:Gem::Version
|
153
|
-
version:
|
125
|
+
version: 2.0.0
|
154
126
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
127
|
requirements:
|
156
128
|
- - ">="
|