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 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 => "../"