closure_tree 6.3.0 → 6.4.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/.travis.yml +3 -3
- data/Appraisals +0 -7
- data/CHANGELOG.md +5 -0
- data/README.md +46 -7
- data/gemfiles/activerecord_4.2.gemfile +1 -1
- data/lib/closure_tree/has_closure_tree_root.rb +8 -10
- data/lib/closure_tree/version.rb +1 -1
- data/spec/db/schema.rb +1 -0
- data/spec/has_closure_tree_root_spec.rb +29 -7
- metadata +2 -3
- data/gemfiles/activerecord_4.1.gemfile +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c6312f8ac4880ea36a31ef47a6c1d851307ee5b9
|
4
|
+
data.tar.gz: 431fe393f396890c009da5f4a74e402db077a4a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e54d08cac5aebf07217e42beb28c35fd6f2487751481f9828537097f401f73b106756a6c511ad4b5cd2adec9235fa1319e77f85093be6b9e88313e44219f604
|
7
|
+
data.tar.gz: 0c98c0c6fc5f27aed4a9d1ebe7e2012356ef0d305fee0a06a941322c9509d78dcf9739a72d135a0414ed1a49c7ded4f9f308e00e9f8a86b015829ea9b498d3b6
|
data/.travis.yml
CHANGED
@@ -2,14 +2,14 @@ cache: bundler
|
|
2
2
|
sudo: false
|
3
3
|
language: ruby
|
4
4
|
rvm:
|
5
|
-
- 2.
|
6
|
-
- 2.
|
5
|
+
- 2.4.0
|
6
|
+
- 2.3.3
|
7
|
+
- 2.2.6
|
7
8
|
# these haven't been passing for a while:
|
8
9
|
# - jruby-head
|
9
10
|
# - rbx
|
10
11
|
|
11
12
|
gemfile:
|
12
|
-
- gemfiles/activerecord_4.1.gemfile
|
13
13
|
- gemfiles/activerecord_4.2.gemfile
|
14
14
|
- gemfiles/activerecord_5.0_foreigner.gemfile
|
15
15
|
- gemfiles/activerecord_5.0.gemfile
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 6.4.0
|
4
|
+
|
5
|
+
* Merged [PR 236](https://github.com/mceachen/closure_tree/pull/236) which adds documentation for `has_closure_tree_root`.
|
6
|
+
* Added ruby 2.4 and dropped Rails 4.1 from the build matrix.
|
7
|
+
|
3
8
|
### 6.3.0
|
4
9
|
|
5
10
|
* `prepend_child` [handles invalid children properly now](https://github.com/mceachen/closure_tree/issues/249).
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ closure_tree has some great features:
|
|
27
27
|
* 2 SQL INSERTs on node creation
|
28
28
|
* 3 SQL INSERT/UPDATEs on node reparenting
|
29
29
|
* __Support for [concurrency](#concurrency)__ (using [with_advisory_lock](https://github.com/mceachen/with_advisory_lock))
|
30
|
-
* __Support for ActiveRecord 4.
|
30
|
+
* __Support for ActiveRecord 4.2 and 5.0__
|
31
31
|
* __Support for Ruby 2.2 and 2.3__
|
32
32
|
* Support for reparenting children (and all their descendants)
|
33
33
|
* Support for [single-table inheritance (STI)](#sti) within the hierarchy
|
@@ -58,7 +58,7 @@ for a description of different tree storage algorithms.
|
|
58
58
|
|
59
59
|
Note that closure_tree only supports ActiveRecord 4.1 and later, and has test coverage for MySQL, PostgreSQL, and SQLite.
|
60
60
|
|
61
|
-
1. Add `gem 'closure_tree'` to your Gemfile
|
61
|
+
1. Add `gem 'closure_tree'` to your Gemfile
|
62
62
|
|
63
63
|
2. Run `bundle install`
|
64
64
|
|
@@ -75,7 +75,7 @@ Note that closure_tree only supports ActiveRecord 4.1 and later, and has test co
|
|
75
75
|
```
|
76
76
|
|
77
77
|
Make sure you check out the [large number options](#available-options) that `has_closure_tree` accepts.
|
78
|
-
|
78
|
+
|
79
79
|
**IMPORTANT: Make sure you add `has_closure_tree` _after_ `attr_accessible` and
|
80
80
|
`self.table_name =` lines in your model.**
|
81
81
|
|
@@ -115,9 +115,9 @@ NOTE: Run `rails g closure_tree:config` to create an initializer with extra
|
|
115
115
|
|
116
116
|
## Warning
|
117
117
|
|
118
|
-
As stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model
|
118
|
+
As stated above, using multiple hierarchy gems (like `ancestry` or `nested set`) on the same model
|
119
119
|
will most likely result in pain, suffering, hair loss, tooth decay, heel-related ailments, and gingivitis.
|
120
|
-
Assume things will break.
|
120
|
+
Assume things will break.
|
121
121
|
|
122
122
|
## Usage
|
123
123
|
|
@@ -169,7 +169,7 @@ child1.ancestry_path
|
|
169
169
|
|
170
170
|
You can `find` as well as `find_or_create` by "ancestry paths".
|
171
171
|
|
172
|
-
If you provide an array of strings to these methods, they reference the `name` column in your
|
172
|
+
If you provide an array of strings to these methods, they reference the `name` column in your
|
173
173
|
model, which can be overridden with the `:name_column` option provided to `has_closure_tree`.
|
174
174
|
|
175
175
|
```ruby
|
@@ -256,6 +256,45 @@ server may not be happy trying to do this.
|
|
256
256
|
|
257
257
|
HT: [ancestry](https://github.com/stefankroes/ancestry#arrangement) and [elhoyos](https://github.com/mceachen/closure_tree/issues/11)
|
258
258
|
|
259
|
+
### Eager loading
|
260
|
+
|
261
|
+
Since most of closure_tree's methods (e.g. `children`) return regular `ActiveRecord` scopes, you can use the `includes` method for eager loading, e.g.
|
262
|
+
|
263
|
+
```ruby
|
264
|
+
comment.children.includes(:author)
|
265
|
+
```
|
266
|
+
|
267
|
+
However, note that the above approach only eager loads the requested associations for the immediate children of `comment`. If you want to walk through the entire tree, you may still end up making many queries and loading duplicate copies of objects.
|
268
|
+
|
269
|
+
In some cases, a viable alternative is the following:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
comment.self_and_descendants.includes(:author)
|
273
|
+
```
|
274
|
+
|
275
|
+
This would load authors for `comment` and all its descendants in a constant number of queries. However, the return value is an array of `Comment`s, and the tree structure is thus lost, which makes it difficult to walk the tree using elegant recursive algorithms.
|
276
|
+
|
277
|
+
A third option is to use `has_closure_tree_root` on the model that is composed by the closure_tree model (e.g. a `Post` may be composed by a tree of `Comment`s). So in `post.rb`, you would do:
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
# app/models/post.rb
|
281
|
+
has_closure_tree_root :root_comment
|
282
|
+
```
|
283
|
+
|
284
|
+
This gives you a plain `has_one` association (`root_comment`) to the root `Comment` (i.e. that with null `parent_id`).
|
285
|
+
|
286
|
+
It also gives you a method called `root_comment_including_tree`, which you can invoke as follows:
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
a_post.root_comment_including_tree(:author)
|
290
|
+
```
|
291
|
+
|
292
|
+
The result of this call will be the root `Comment` with all descendants _and_ associations loaded in a constant number of queries. Inverse associations are also setup on all nodes, so as you walk the tree, calling `children` or `parent` on any node will _not_ trigger any further queries and no duplicate copies of objects are loaded into memory.
|
293
|
+
|
294
|
+
The class and foreign key of `root_comment` are assumed to be `Comment` and `post_id`, respectively. These can be overridden in the usual way.
|
295
|
+
|
296
|
+
The same caveat stated above with `hash_tree` also applies here: this method will load the entire tree into memory. If the tree is very large, this may be a bad idea, in which case using the eager loading methods above may be preferred.
|
297
|
+
|
259
298
|
### Graph visualization
|
260
299
|
|
261
300
|
```to_dot_digraph``` is suitable for passing into [Graphviz](http://www.graphviz.org/).
|
@@ -473,7 +512,7 @@ Yup! [Ilya Bodrov](https://github.com/bodrovis) wrote [Nested Comments with Rail
|
|
473
512
|
|
474
513
|
### Can I update parentage with `update_attribute`?
|
475
514
|
|
476
|
-
**No.** `update_attribute` skips the validation hook that is required for maintaining the
|
515
|
+
**No.** `update_attribute` skips the validation hook that is required for maintaining the
|
477
516
|
hierarchy table.
|
478
517
|
|
479
518
|
### Can I assign a parent to multiple children with ```#update_all```?
|
@@ -15,18 +15,16 @@ module ClosureTree
|
|
15
15
|
has_one assoc_name, -> { where(parent: nil) }, options
|
16
16
|
|
17
17
|
# Fetches the association, eager loading all children and given associations
|
18
|
-
define_method("#{assoc_name}_including_tree") do |
|
18
|
+
define_method("#{assoc_name}_including_tree") do |*args|
|
19
19
|
reload = false
|
20
|
-
if
|
21
|
-
|
22
|
-
|
23
|
-
reload = assoc_map_or_reload
|
24
|
-
end
|
20
|
+
reload = args.shift if args && (args.first == true || args.first == false)
|
21
|
+
assoc_map = args
|
22
|
+
assoc_map = [nil] if assoc_map.blank?
|
25
23
|
|
24
|
+
# Memoize
|
25
|
+
@closure_tree_roots ||= {}
|
26
|
+
@closure_tree_roots[assoc_name] ||= {}
|
26
27
|
unless reload
|
27
|
-
# Memoize
|
28
|
-
@closure_tree_roots ||= {}
|
29
|
-
@closure_tree_roots[assoc_name] ||= {}
|
30
28
|
if @closure_tree_roots[assoc_name].has_key?(assoc_map)
|
31
29
|
return @closure_tree_roots[assoc_name][assoc_map]
|
32
30
|
end
|
@@ -52,7 +50,7 @@ module ClosureTree
|
|
52
50
|
|
53
51
|
# Fetch all descendants in constant number of queries.
|
54
52
|
# This is the last query-triggering statement in the method.
|
55
|
-
temp_root.self_and_descendants.includes(assoc_map).each do |node|
|
53
|
+
temp_root.self_and_descendants.includes(*assoc_map).each do |node|
|
56
54
|
id_hash[node.id] = node
|
57
55
|
parent_node = id_hash[node[parent_col_id]]
|
58
56
|
|
data/lib/closure_tree/version.rb
CHANGED
data/spec/db/schema.rb
CHANGED
@@ -26,18 +26,36 @@ describe "has_closure_tree_root" do
|
|
26
26
|
user3.children << user5
|
27
27
|
user3.children << user6
|
28
28
|
|
29
|
-
user1.contracts.create!(contract_type: ct1)
|
30
|
-
user2.contracts.create!(contract_type: ct1)
|
31
|
-
user3.contracts.create!(contract_type: ct1)
|
32
|
-
user3.contracts.create!(contract_type: ct2)
|
33
|
-
user4.contracts.create!(contract_type: ct2)
|
34
|
-
user5.contracts.create!(contract_type: ct1)
|
35
|
-
user6.contracts.create!(contract_type: ct2)
|
29
|
+
user1.contracts.create!(title: "Contract 1", contract_type: ct1)
|
30
|
+
user2.contracts.create!(title: "Contract 2", contract_type: ct1)
|
31
|
+
user3.contracts.create!(title: "Contract 3", contract_type: ct1)
|
32
|
+
user3.contracts.create!(title: "Contract 4", contract_type: ct2)
|
33
|
+
user4.contracts.create!(title: "Contract 5", contract_type: ct2)
|
34
|
+
user5.contracts.create!(title: "Contract 6", contract_type: ct1)
|
35
|
+
user6.contracts.create!(title: "Contract 7", contract_type: ct2)
|
36
36
|
end
|
37
37
|
|
38
38
|
context "with basic config" do
|
39
39
|
let!(:group) { Group.create!(name: "TheGroup") }
|
40
40
|
|
41
|
+
it "loads all nodes in a constant number of queries" do
|
42
|
+
expect do
|
43
|
+
root = group_reloaded.root_user_including_tree
|
44
|
+
expect(root.children[0].email).to eq "2@example.com"
|
45
|
+
expect(root.children[0].parent.children[1].email).to eq "3@example.com"
|
46
|
+
end.to_not exceed_query_limit(2)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "loads all nodes plus single association in a constant number of queries" do
|
50
|
+
expect do
|
51
|
+
root = group_reloaded.root_user_including_tree(:contracts)
|
52
|
+
expect(root.children[0].email).to eq "2@example.com"
|
53
|
+
expect(root.children[0].parent.children[1].email).to eq "3@example.com"
|
54
|
+
expect(root.children[0].children[0].contracts[0].user.
|
55
|
+
parent.parent.children[1].children[1].contracts[0].title).to eq "Contract 7"
|
56
|
+
end.to_not exceed_query_limit(3)
|
57
|
+
end
|
58
|
+
|
41
59
|
it "loads all nodes and associations in a constant number of queries" do
|
42
60
|
expect do
|
43
61
|
root = group_reloaded.root_user_including_tree(contracts: :contract_type)
|
@@ -66,6 +84,10 @@ describe "has_closure_tree_root" do
|
|
66
84
|
to eq "1@example.com"
|
67
85
|
end
|
68
86
|
|
87
|
+
it "works if true passed on first call" do
|
88
|
+
expect(group_reloaded.root_user_including_tree(true).email).to eq "1@example.com"
|
89
|
+
end
|
90
|
+
|
69
91
|
it "eager loads inverse association to group" do
|
70
92
|
expect do
|
71
93
|
root = group_reloaded.root_user_including_tree
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: closure_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew McEachen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -140,7 +140,6 @@ files:
|
|
140
140
|
- README.md
|
141
141
|
- Rakefile
|
142
142
|
- closure_tree.gemspec
|
143
|
-
- gemfiles/activerecord_4.1.gemfile
|
144
143
|
- gemfiles/activerecord_4.2.gemfile
|
145
144
|
- gemfiles/activerecord_5.0.gemfile
|
146
145
|
- gemfiles/activerecord_5.0_foreigner.gemfile
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# This file was generated by Appraisal
|
2
|
-
|
3
|
-
source "https://rubygems.org"
|
4
|
-
|
5
|
-
gem "activerecord", "~> 4.1.0"
|
6
|
-
gem "foreigner"
|
7
|
-
|
8
|
-
platforms :ruby, :rbx do
|
9
|
-
gem "mysql2", "~> 0.3.20"
|
10
|
-
gem "pg"
|
11
|
-
gem "sqlite3"
|
12
|
-
end
|
13
|
-
|
14
|
-
platforms :jruby do
|
15
|
-
gem "activerecord-jdbcmysql-adapter"
|
16
|
-
gem "activerecord-jdbcpostgresql-adapter"
|
17
|
-
gem "activerecord-jdbcsqlite3-adapter"
|
18
|
-
end
|
19
|
-
|
20
|
-
gemspec :path => "../"
|