closure_tree 6.3.0 → 6.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 => "../"
|