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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 06cbb37da1e1592760ad47995f099a8f69be5d81
4
- data.tar.gz: a9e2ece8d7e792b0cb97378c9885b9201e654bbb
3
+ metadata.gz: c6312f8ac4880ea36a31ef47a6c1d851307ee5b9
4
+ data.tar.gz: 431fe393f396890c009da5f4a74e402db077a4a8
5
5
  SHA512:
6
- metadata.gz: 1ca685fbfaf26efad13f7633039553940d7655fe73d83e2c5e0d2b3b00f2905de07b648026e8d093b59c43082f14e7e3bbc87c963c3933dff46b12c54311673b
7
- data.tar.gz: 0bed535b6c550b3b6fa09aa40de17d82c022e8997914e98cf4ba1ba436a9d6f453a41e549831a10e50a8836365acccf9e5201105b44a8cbfdf297445e267f5a5
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.3.1
6
- - 2.2.5
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
@@ -1,10 +1,3 @@
1
- appraise 'activerecord-4.1' do
2
- gem 'activerecord', '~> 4.1.0'
3
- gem 'foreigner'
4
- platforms :ruby, :rbx do
5
- gem 'mysql2', '~> 0.3.20'
6
- end
7
- end
8
1
 
9
2
  appraise 'activerecord-4.2' do
10
3
  gem 'activerecord', '~> 4.2.0'
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.1, 4.2 and 5.0__
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```?
@@ -5,7 +5,7 @@ source "https://rubygems.org"
5
5
  gem "activerecord", "~> 4.2.0"
6
6
 
7
7
  platforms :ruby, :rbx do
8
- gem "mysql2", "~> 0.3.20"
8
+ gem "mysql2"
9
9
  gem "pg"
10
10
  gem "sqlite3"
11
11
  end
@@ -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 |assoc_map_or_reload = nil, assoc_map = nil|
18
+ define_method("#{assoc_name}_including_tree") do |*args|
19
19
  reload = false
20
- if assoc_map_or_reload.is_a?(::Hash)
21
- assoc_map = assoc_map_or_reload
22
- else
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
 
@@ -1,3 +1,3 @@
1
1
  module ClosureTree
2
- VERSION = Gem::Version.new('6.3.0')
2
+ VERSION = Gem::Version.new('6.4.0')
3
3
  end
data/spec/db/schema.rb CHANGED
@@ -71,6 +71,7 @@ ActiveRecord::Schema.define(:version => 0) do
71
71
  create_table "contracts" do |t|
72
72
  t.integer "user_id", :null => false
73
73
  t.integer "contract_type_id"
74
+ t.string "title"
74
75
  end
75
76
 
76
77
  create_table "contract_types" do |t|
@@ -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.3.0
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-08 00:00:00.000000000 Z
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 => "../"