closure_tree 6.1.0 → 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -0
- data/.rspec +0 -0
- data/.travis.yml +0 -0
- data/.yardopts +0 -0
- data/Appraisals +0 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +0 -0
- data/MIT-LICENSE +0 -0
- data/README.md +6 -1
- data/Rakefile +0 -0
- data/closure_tree.gemspec +0 -0
- data/gemfiles/activerecord_4.1.gemfile +0 -0
- data/gemfiles/activerecord_4.2.gemfile +0 -0
- data/gemfiles/activerecord_5.0.gemfile +0 -0
- data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -0
- data/gemfiles/activerecord_edge.gemfile +0 -0
- data/img/example.png +0 -0
- data/img/preorder.png +0 -0
- data/lib/closure_tree.rb +2 -0
- data/lib/closure_tree/active_record_support.rb +0 -0
- data/lib/closure_tree/configuration.rb +0 -0
- data/lib/closure_tree/deterministic_ordering.rb +0 -0
- data/lib/closure_tree/digraphs.rb +0 -0
- data/lib/closure_tree/finders.rb +3 -3
- data/lib/closure_tree/has_closure_tree.rb +0 -0
- data/lib/closure_tree/has_closure_tree_root.rb +88 -0
- data/lib/closure_tree/hash_tree.rb +0 -0
- data/lib/closure_tree/hash_tree_support.rb +0 -0
- data/lib/closure_tree/hierarchy_maintenance.rb +0 -0
- data/lib/closure_tree/model.rb +0 -0
- data/lib/closure_tree/numeric_deterministic_ordering.rb +1 -1
- data/lib/closure_tree/numeric_order_support.rb +0 -0
- data/lib/closure_tree/support.rb +0 -0
- data/lib/closure_tree/support_attributes.rb +1 -2
- data/lib/closure_tree/support_flags.rb +0 -0
- data/lib/closure_tree/test/matcher.rb +0 -0
- data/lib/closure_tree/version.rb +1 -1
- data/lib/generators/closure_tree/config_generator.rb +0 -0
- data/lib/generators/closure_tree/migration_generator.rb +0 -0
- data/lib/generators/closure_tree/templates/config.rb +0 -0
- data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +0 -0
- data/spec/cache_invalidation_spec.rb +0 -0
- data/spec/cuisine_type_spec.rb +0 -0
- data/spec/db/database.yml +0 -0
- data/spec/db/models.rb +24 -2
- data/spec/db/schema.rb +22 -0
- data/spec/fixtures/tags.yml +0 -0
- data/spec/generators/migration_generator_spec.rb +0 -0
- data/spec/has_closure_tree_root_spec.rb +132 -0
- data/spec/hierarchy_maintenance_spec.rb +0 -0
- data/spec/label_spec.rb +0 -0
- data/spec/matcher_spec.rb +0 -0
- data/spec/metal_spec.rb +0 -0
- data/spec/model_spec.rb +0 -0
- data/spec/namespace_type_spec.rb +0 -0
- data/spec/parallel_spec.rb +0 -0
- data/spec/spec_helper.rb +0 -0
- data/spec/support/database.rb +0 -0
- data/spec/support/database_cleaner.rb +0 -0
- data/spec/support/exceed_query_limit.rb +18 -0
- data/spec/support/hash_monkey_patch.rb +0 -0
- data/spec/support/query_counter.rb +18 -0
- data/spec/support/sqlite3_with_advisory_lock.rb +0 -0
- data/spec/support_spec.rb +0 -0
- data/spec/tag_examples.rb +0 -0
- data/spec/tag_spec.rb +0 -0
- data/spec/user_spec.rb +0 -0
- data/spec/uuid_tag_spec.rb +0 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3b96ba78aea59298da3138316379a458d28c713
|
4
|
+
data.tar.gz: f1c786fb41309ea4460a06ae296b7393ea058b63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5850fadca83ecdc88e53f4fac48a19faaafda82c7b6bd93f8051e264b632a94b56edbc3b22d9d060d658cf76dca02268fda625835c74e9e6cfb6ef75e9430d6
|
7
|
+
data.tar.gz: cae0ca262a5f9f76d1a01b24aac119a5307a49637a77e9287305630d2e97c2b3265a767180b5e76eedff9a4a9ada44ff07aebe648d0b109949c7ed2e012b9b93
|
data/.gitignore
CHANGED
File without changes
|
data/.rspec
CHANGED
File without changes
|
data/.travis.yml
CHANGED
File without changes
|
data/.yardopts
CHANGED
File without changes
|
data/Appraisals
CHANGED
File without changes
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### 6.2.0
|
4
|
+
|
5
|
+
* Fix for [MySQL lock lengths](https://github.com/mceachen/closure_tree/issues/231).
|
6
|
+
Thanks to [Liam](https://github.com/hut8)!
|
7
|
+
* [Tom Smyth](https://github.com/hooverlunch) added [eager tree loading](https://github.com/mceachen/closure_tree/pull/232)
|
8
|
+
* Merged [PR 200](https://github.com/mceachen/closure_tree/pull/200) which may or may not add support to SQLServer 2008 (but this is not a supported RDBMS).
|
9
|
+
|
3
10
|
### 6.1.0
|
4
11
|
|
5
12
|
* Added official support for ActiveRecord 5.0! Thanks to [Abdelkader Boudih](https://github.com/seuros),
|
data/Gemfile
CHANGED
File without changes
|
data/MIT-LICENSE
CHANGED
File without changes
|
data/README.md
CHANGED
@@ -468,7 +468,12 @@ Yup! [Ilya Bodrov](https://github.com/bodrovis) wrote [Nested Comments with Rail
|
|
468
468
|
|
469
469
|
### Does this work well with ```#default_scope```?
|
470
470
|
|
471
|
-
No
|
471
|
+
**No.** Please see [issue 86](https://github.com/mceachen/closure_tree/issues/86) for details.
|
472
|
+
|
473
|
+
### Can I update parentage with `update_attribute`?
|
474
|
+
|
475
|
+
**No.** `update_attribute` skips the validation hook that is required for maintaining the
|
476
|
+
hierarchy table.
|
472
477
|
|
473
478
|
### Does this gem support multiple parents?
|
474
479
|
|
data/Rakefile
CHANGED
File without changes
|
data/closure_tree.gemspec
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/img/example.png
CHANGED
File without changes
|
data/img/preorder.png
CHANGED
File without changes
|
data/lib/closure_tree.rb
CHANGED
@@ -4,6 +4,7 @@ module ClosureTree
|
|
4
4
|
extend ActiveSupport::Autoload
|
5
5
|
|
6
6
|
autoload :HasClosureTree
|
7
|
+
autoload :HasClosureTreeRoot
|
7
8
|
autoload :Support
|
8
9
|
autoload :HierarchyMaintenance
|
9
10
|
autoload :Model
|
@@ -25,4 +26,5 @@ end
|
|
25
26
|
|
26
27
|
ActiveSupport.on_load :active_record do
|
27
28
|
ActiveRecord::Base.send :extend, ClosureTree::HasClosureTree
|
29
|
+
ActiveRecord::Base.send :extend, ClosureTree::HasClosureTreeRoot
|
28
30
|
end
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/closure_tree/finders.rb
CHANGED
@@ -39,7 +39,7 @@ module ClosureTree
|
|
39
39
|
SELECT descendant_id
|
40
40
|
FROM #{_ct.quoted_hierarchy_table_name}
|
41
41
|
WHERE ancestor_id = #{_ct.quote(self.id)}
|
42
|
-
GROUP BY
|
42
|
+
GROUP BY descendant_id
|
43
43
|
HAVING MAX(#{_ct.quoted_hierarchy_table_name}.generations) = #{generation_level.to_i}
|
44
44
|
) AS descendants ON (#{_ct.quoted_table_name}.#{_ct.base_class.primary_key} = descendants.descendant_id)
|
45
45
|
SQL
|
@@ -74,7 +74,7 @@ module ClosureTree
|
|
74
74
|
INNER JOIN (
|
75
75
|
SELECT ancestor_id
|
76
76
|
FROM #{_ct.quoted_hierarchy_table_name}
|
77
|
-
GROUP BY
|
77
|
+
GROUP BY ancestor_id
|
78
78
|
HAVING MAX(#{_ct.quoted_hierarchy_table_name}.generations) = 0
|
79
79
|
) AS leaves ON (#{_ct.quoted_table_name}.#{primary_key} = leaves.ancestor_id)
|
80
80
|
SQL
|
@@ -100,7 +100,7 @@ module ClosureTree
|
|
100
100
|
INNER JOIN (
|
101
101
|
SELECT ancestor_id, descendant_id
|
102
102
|
FROM #{_ct.quoted_hierarchy_table_name}
|
103
|
-
GROUP BY
|
103
|
+
GROUP BY ancestor_id, descendant_id
|
104
104
|
HAVING MAX(generations) = #{generation_level.to_i}
|
105
105
|
) AS descendants ON (
|
106
106
|
#{_ct.quoted_table_name}.#{primary_key} = descendants.descendant_id
|
File without changes
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ClosureTree
|
2
|
+
class MultipleRootError < StandardError; end
|
3
|
+
|
4
|
+
module HasClosureTreeRoot
|
5
|
+
|
6
|
+
def has_closure_tree_root(assoc_name, options = {})
|
7
|
+
options.assert_valid_keys(
|
8
|
+
:class_name,
|
9
|
+
:foreign_key
|
10
|
+
)
|
11
|
+
|
12
|
+
options[:class_name] ||= assoc_name.to_s.sub(/\Aroot_/, "").classify
|
13
|
+
options[:foreign_key] ||= self.name.underscore << "_id"
|
14
|
+
|
15
|
+
has_one assoc_name, -> { where(parent: nil) }, options
|
16
|
+
|
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|
|
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
|
25
|
+
|
26
|
+
unless reload
|
27
|
+
# Memoize
|
28
|
+
@closure_tree_roots ||= {}
|
29
|
+
@closure_tree_roots[assoc_name] ||= {}
|
30
|
+
if @closure_tree_roots[assoc_name].has_key?(assoc_map)
|
31
|
+
return @closure_tree_roots[assoc_name][assoc_map]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
roots = options[:class_name].constantize.where(parent: nil, options[:foreign_key] => id).to_a
|
36
|
+
|
37
|
+
return nil if roots.empty?
|
38
|
+
|
39
|
+
if roots.size > 1
|
40
|
+
raise MultipleRootError.new("#{self.class.name}: has_closure_tree_root requires a single root")
|
41
|
+
end
|
42
|
+
|
43
|
+
temp_root = roots.first
|
44
|
+
root = nil
|
45
|
+
id_hash = {}
|
46
|
+
parent_col_id = temp_root.class._ct.options[:parent_column_name]
|
47
|
+
|
48
|
+
# Lookup inverse belongs_to association reflection on target class.
|
49
|
+
inverse = temp_root.class.reflections.values.detect do |r|
|
50
|
+
r.macro == :belongs_to && r.klass == self.class
|
51
|
+
end
|
52
|
+
|
53
|
+
# Fetch all descendants in constant number of queries.
|
54
|
+
# This is the last query-triggering statement in the method.
|
55
|
+
temp_root.self_and_descendants.includes(assoc_map).each do |node|
|
56
|
+
id_hash[node.id] = node
|
57
|
+
parent_node = id_hash[node[parent_col_id]]
|
58
|
+
|
59
|
+
# Pre-assign parent association
|
60
|
+
parent_assoc = node.association(:parent)
|
61
|
+
parent_assoc.loaded!
|
62
|
+
parent_assoc.target = parent_node
|
63
|
+
|
64
|
+
# Pre-assign children association as empty for now,
|
65
|
+
# children will be added in subsequent loop iterations
|
66
|
+
children_assoc = node.association(:children)
|
67
|
+
children_assoc.loaded!
|
68
|
+
|
69
|
+
if parent_node
|
70
|
+
parent_node.association(:children).target << node
|
71
|
+
else
|
72
|
+
# Capture the root we're going to use
|
73
|
+
root = node
|
74
|
+
end
|
75
|
+
|
76
|
+
# Pre-assign inverse association back to this class, if it exists on target class.
|
77
|
+
if inverse
|
78
|
+
inverse_assoc = node.association(inverse.name)
|
79
|
+
inverse_assoc.loaded!
|
80
|
+
inverse_assoc.target = self
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
@closure_tree_roots[assoc_name][assoc_map] = root
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
File without changes
|
File without changes
|
File without changes
|
data/lib/closure_tree/model.rb
CHANGED
File without changes
|
@@ -72,7 +72,7 @@ module ClosureTree
|
|
72
72
|
JOIN (
|
73
73
|
SELECT descendant_id, max(generations) AS max_depth
|
74
74
|
FROM #{_ct.quoted_hierarchy_table_name}
|
75
|
-
GROUP BY
|
75
|
+
GROUP BY descendant_id
|
76
76
|
) AS depths ON depths.descendant_id = anc.#{_ct.quoted_id_column_name}
|
77
77
|
SQL
|
78
78
|
joins(join_sql)
|
File without changes
|
data/lib/closure_tree/support.rb
CHANGED
File without changes
|
@@ -1,12 +1,11 @@
|
|
1
1
|
require 'forwardable'
|
2
2
|
module ClosureTree
|
3
3
|
module SupportAttributes
|
4
|
-
|
5
4
|
extend Forwardable
|
6
5
|
def_delegators :model_class, :connection, :transaction, :table_name, :base_class, :inheritance_column, :column_names
|
7
6
|
|
8
7
|
def advisory_lock_name
|
9
|
-
"ClosureTree::#{base_class.name}"
|
8
|
+
Digest::SHA1.hexdigest("ClosureTree::#{base_class.name}")[0..32]
|
10
9
|
end
|
11
10
|
|
12
11
|
def quoted_table_name
|
File without changes
|
File without changes
|
data/lib/closure_tree/version.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/spec/cuisine_type_spec.rb
CHANGED
File without changes
|
data/spec/db/database.yml
CHANGED
File without changes
|
data/spec/db/models.rb
CHANGED
@@ -35,13 +35,30 @@ end
|
|
35
35
|
class DestroyedTag < ActiveRecord::Base
|
36
36
|
end
|
37
37
|
|
38
|
+
class Group < ActiveRecord::Base
|
39
|
+
has_closure_tree_root :root_user
|
40
|
+
end
|
41
|
+
|
42
|
+
class Grouping < ActiveRecord::Base
|
43
|
+
has_closure_tree_root :root_person, class_name: "User", foreign_key: :group_id
|
44
|
+
end
|
45
|
+
|
46
|
+
class UserSet < ActiveRecord::Base
|
47
|
+
has_closure_tree_root :root_user, class_name: "Useur"
|
48
|
+
end
|
49
|
+
|
50
|
+
class Team < ActiveRecord::Base
|
51
|
+
has_closure_tree_root :root_user, class_name: "User", foreign_key: :grp_id
|
52
|
+
end
|
53
|
+
|
38
54
|
class User < ActiveRecord::Base
|
39
55
|
acts_as_tree :parent_column_name => "referrer_id",
|
40
56
|
:name_column => 'email',
|
41
57
|
:hierarchy_class_name => 'ReferralHierarchy',
|
42
58
|
:hierarchy_table_name => 'referral_hierarchies'
|
43
59
|
|
44
|
-
has_many :contracts
|
60
|
+
has_many :contracts, inverse_of: :user
|
61
|
+
belongs_to :group # Can't use and don't need inverse_of here when using has_closure_tree_root.
|
45
62
|
|
46
63
|
def indirect_contracts
|
47
64
|
Contract.where(:user_id => descendant_ids)
|
@@ -53,7 +70,12 @@ class User < ActiveRecord::Base
|
|
53
70
|
end
|
54
71
|
|
55
72
|
class Contract < ActiveRecord::Base
|
56
|
-
belongs_to :user
|
73
|
+
belongs_to :user, inverse_of: :contracts
|
74
|
+
belongs_to :contract_type, inverse_of: :contracts
|
75
|
+
end
|
76
|
+
|
77
|
+
class ContractType < ActiveRecord::Base
|
78
|
+
has_many :contracts, inverse_of: :contract_type
|
57
79
|
end
|
58
80
|
|
59
81
|
class Label < ActiveRecord::Base
|
data/spec/db/schema.rb
CHANGED
@@ -43,9 +43,26 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
43
43
|
add_index "tag_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "tag_anc_desc_idx"
|
44
44
|
add_index "tag_hierarchies", [:descendant_id], :name => "tag_desc_idx"
|
45
45
|
|
46
|
+
create_table "groups" do |t|
|
47
|
+
t.string "name", null: false
|
48
|
+
end
|
49
|
+
|
50
|
+
create_table "groupings" do |t|
|
51
|
+
t.string "name", null: false
|
52
|
+
end
|
53
|
+
|
54
|
+
create_table "user_sets" do |t|
|
55
|
+
t.string "name", null: false
|
56
|
+
end
|
57
|
+
|
58
|
+
create_table "teams" do |t|
|
59
|
+
t.string "name", null: false
|
60
|
+
end
|
61
|
+
|
46
62
|
create_table "users" do |t|
|
47
63
|
t.string "email"
|
48
64
|
t.integer "referrer_id"
|
65
|
+
t.integer "group_id"
|
49
66
|
t.timestamps null: false
|
50
67
|
end
|
51
68
|
|
@@ -53,6 +70,11 @@ ActiveRecord::Schema.define(:version => 0) do
|
|
53
70
|
|
54
71
|
create_table "contracts" do |t|
|
55
72
|
t.integer "user_id", :null => false
|
73
|
+
t.integer "contract_type_id"
|
74
|
+
end
|
75
|
+
|
76
|
+
create_table "contract_types" do |t|
|
77
|
+
t.string "name", :null => false
|
56
78
|
end
|
57
79
|
|
58
80
|
create_table "referral_hierarchies", :id => false do |t|
|
data/spec/fixtures/tags.yml
CHANGED
File without changes
|
File without changes
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "has_closure_tree_root" do
|
4
|
+
let!(:ct1) { ContractType.create!(name: "Type1") }
|
5
|
+
let!(:ct2) { ContractType.create!(name: "Type2") }
|
6
|
+
let!(:user1) { User.create!(email: "1@example.com", group_id: group.id) }
|
7
|
+
let!(:user2) { User.create!(email: "2@example.com", group_id: group.id) }
|
8
|
+
let!(:user3) { User.create!(email: "3@example.com", group_id: group.id) }
|
9
|
+
let!(:user4) { User.create!(email: "4@example.com", group_id: group.id) }
|
10
|
+
let!(:user5) { User.create!(email: "5@example.com", group_id: group.id) }
|
11
|
+
let!(:user6) { User.create!(email: "6@example.com", group_id: group.id) }
|
12
|
+
let!(:group_reloaded) { group.class.find(group.id) } # Ensures were starting fresh.
|
13
|
+
|
14
|
+
before do
|
15
|
+
# The tree (contract types in parens)
|
16
|
+
#
|
17
|
+
# U1(1)
|
18
|
+
# / \
|
19
|
+
# U2(1) U3(1&2)
|
20
|
+
# / / \
|
21
|
+
# U4(2) U5(1) U6(2)
|
22
|
+
|
23
|
+
user1.children << user2
|
24
|
+
user1.children << user3
|
25
|
+
user2.children << user4
|
26
|
+
user3.children << user5
|
27
|
+
user3.children << user6
|
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)
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with basic config" do
|
39
|
+
let!(:group) { Group.create!(name: "TheGroup") }
|
40
|
+
|
41
|
+
it "loads all nodes and associations in a constant number of queries" do
|
42
|
+
expect do
|
43
|
+
root = group_reloaded.root_user_including_tree(contracts: :contract_type)
|
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
|
+
expect(root.children[1].contracts.map(&:contract_type).map(&:name)).to eq %w(Type1 Type2)
|
47
|
+
expect(root.children[1].children[0].contracts[0].contract_type.name).to eq "Type1"
|
48
|
+
expect(root.children[0].children[0].contracts[0].user.
|
49
|
+
parent.parent.children[1].children[1].contracts[0].contract_type.name).to eq "Type2"
|
50
|
+
end.to_not exceed_query_limit(4) # Without this feature, this is 15, and scales with number of nodes.
|
51
|
+
end
|
52
|
+
|
53
|
+
it "memoizes by assoc_map" do
|
54
|
+
group_reloaded.root_user_including_tree.email = "x"
|
55
|
+
expect(group_reloaded.root_user_including_tree.email).to eq "x"
|
56
|
+
group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
|
57
|
+
expect(group_reloaded.root_user_including_tree(contracts: :contract_type).email).to eq "y"
|
58
|
+
expect(group_reloaded.root_user_including_tree.email).to eq "x"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "doesn't memoize if true argument passed" do
|
62
|
+
group_reloaded.root_user_including_tree.email = "x"
|
63
|
+
expect(group_reloaded.root_user_including_tree(true).email).to eq "1@example.com"
|
64
|
+
group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
|
65
|
+
expect(group_reloaded.root_user_including_tree(true, contracts: :contract_type).email).
|
66
|
+
to eq "1@example.com"
|
67
|
+
end
|
68
|
+
|
69
|
+
it "eager loads inverse association to group" do
|
70
|
+
expect do
|
71
|
+
root = group_reloaded.root_user_including_tree
|
72
|
+
expect(root.group).to eq group
|
73
|
+
expect(root.children[0].group).to eq group
|
74
|
+
end.to_not exceed_query_limit(2)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "works if eager load association map is not given" do
|
78
|
+
expect do
|
79
|
+
root = group_reloaded.root_user_including_tree
|
80
|
+
expect(root.children[0].email).to eq "2@example.com"
|
81
|
+
expect(root.children[0].parent.children[1].children[0].email).to eq "5@example.com"
|
82
|
+
end.to_not exceed_query_limit(2)
|
83
|
+
end
|
84
|
+
|
85
|
+
context "with no tree root" do
|
86
|
+
let(:group2) { Group.create!(name: "OtherGroup") }
|
87
|
+
|
88
|
+
it "should return nil" do
|
89
|
+
expect(group2.root_user_including_tree(contracts: :contract_type)).to be_nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with multiple tree roots" do
|
94
|
+
let!(:other_root) { User.create!(email: "10@example.com", group_id: group.id) }
|
95
|
+
|
96
|
+
it "should error" do
|
97
|
+
expect do
|
98
|
+
root = group_reloaded.root_user_including_tree(contracts: :contract_type)
|
99
|
+
end.to raise_error(ClosureTree::MultipleRootError)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "with explicit class_name and foreign_key" do
|
105
|
+
let(:group) { Grouping.create!(name: "TheGrouping") }
|
106
|
+
|
107
|
+
it "should still work" do
|
108
|
+
root = group_reloaded.root_person_including_tree(contracts: :contract_type)
|
109
|
+
expect(root.children[0].email).to eq "2@example.com"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "with bad class_name" do
|
114
|
+
let(:group) { UserSet.create!(name: "TheUserSet") }
|
115
|
+
|
116
|
+
it "should error" do
|
117
|
+
expect do
|
118
|
+
root = group_reloaded.root_user_including_tree(contracts: :contract_type)
|
119
|
+
end.to raise_error(NameError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context "with bad foreign_key" do
|
124
|
+
let(:group) { Team.create!(name: "TheTeam") }
|
125
|
+
|
126
|
+
it "should error" do
|
127
|
+
expect do
|
128
|
+
root = group_reloaded.root_user_including_tree(contracts: :contract_type)
|
129
|
+
end.to raise_error(ActiveRecord::StatementInvalid)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
File without changes
|
data/spec/label_spec.rb
CHANGED
File without changes
|
data/spec/matcher_spec.rb
CHANGED
File without changes
|
data/spec/metal_spec.rb
CHANGED
File without changes
|
data/spec/model_spec.rb
CHANGED
File without changes
|
data/spec/namespace_type_spec.rb
CHANGED
File without changes
|
data/spec/parallel_spec.rb
CHANGED
File without changes
|
data/spec/spec_helper.rb
CHANGED
File without changes
|
data/spec/support/database.rb
CHANGED
File without changes
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Derived from http://stackoverflow.com/a/13423584/153896. Updated for RSpec 3.
|
2
|
+
RSpec::Matchers.define :exceed_query_limit do |expected|
|
3
|
+
supports_block_expectations
|
4
|
+
|
5
|
+
match do |block|
|
6
|
+
query_count(&block) > expected
|
7
|
+
end
|
8
|
+
|
9
|
+
failure_message_when_negated do |actual|
|
10
|
+
"Expected to run maximum #{expected} queries, got #{@counter.query_count}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def query_count(&block)
|
14
|
+
@counter = ActiveRecord::QueryCounter.new
|
15
|
+
ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
|
16
|
+
@counter.query_count
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# From http://stackoverflow.com/a/13423584/153896
|
2
|
+
module ActiveRecord
|
3
|
+
class QueryCounter
|
4
|
+
attr_reader :query_count
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@query_count = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_proc
|
11
|
+
lambda(&method(:callback))
|
12
|
+
end
|
13
|
+
|
14
|
+
def callback(name, start, finish, message_id, values)
|
15
|
+
@query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
data/spec/support_spec.rb
CHANGED
File without changes
|
data/spec/tag_examples.rb
CHANGED
File without changes
|
data/spec/tag_spec.rb
CHANGED
File without changes
|
data/spec/user_spec.rb
CHANGED
File without changes
|
data/spec/uuid_tag_spec.rb
CHANGED
File without changes
|
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.2.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: 2016-
|
11
|
+
date: 2016-10-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -154,6 +154,7 @@ files:
|
|
154
154
|
- lib/closure_tree/digraphs.rb
|
155
155
|
- lib/closure_tree/finders.rb
|
156
156
|
- lib/closure_tree/has_closure_tree.rb
|
157
|
+
- lib/closure_tree/has_closure_tree_root.rb
|
157
158
|
- lib/closure_tree/hash_tree.rb
|
158
159
|
- lib/closure_tree/hash_tree_support.rb
|
159
160
|
- lib/closure_tree/hierarchy_maintenance.rb
|
@@ -177,6 +178,7 @@ files:
|
|
177
178
|
- spec/db/schema.rb
|
178
179
|
- spec/fixtures/tags.yml
|
179
180
|
- spec/generators/migration_generator_spec.rb
|
181
|
+
- spec/has_closure_tree_root_spec.rb
|
180
182
|
- spec/hierarchy_maintenance_spec.rb
|
181
183
|
- spec/label_spec.rb
|
182
184
|
- spec/matcher_spec.rb
|
@@ -187,7 +189,9 @@ files:
|
|
187
189
|
- spec/spec_helper.rb
|
188
190
|
- spec/support/database.rb
|
189
191
|
- spec/support/database_cleaner.rb
|
192
|
+
- spec/support/exceed_query_limit.rb
|
190
193
|
- spec/support/hash_monkey_patch.rb
|
194
|
+
- spec/support/query_counter.rb
|
191
195
|
- spec/support/sqlite3_with_advisory_lock.rb
|
192
196
|
- spec/support_spec.rb
|
193
197
|
- spec/tag_examples.rb
|
@@ -227,6 +231,7 @@ test_files:
|
|
227
231
|
- spec/db/schema.rb
|
228
232
|
- spec/fixtures/tags.yml
|
229
233
|
- spec/generators/migration_generator_spec.rb
|
234
|
+
- spec/has_closure_tree_root_spec.rb
|
230
235
|
- spec/hierarchy_maintenance_spec.rb
|
231
236
|
- spec/label_spec.rb
|
232
237
|
- spec/matcher_spec.rb
|
@@ -237,7 +242,9 @@ test_files:
|
|
237
242
|
- spec/spec_helper.rb
|
238
243
|
- spec/support/database.rb
|
239
244
|
- spec/support/database_cleaner.rb
|
245
|
+
- spec/support/exceed_query_limit.rb
|
240
246
|
- spec/support/hash_monkey_patch.rb
|
247
|
+
- spec/support/query_counter.rb
|
241
248
|
- spec/support/sqlite3_with_advisory_lock.rb
|
242
249
|
- spec/support_spec.rb
|
243
250
|
- spec/tag_examples.rb
|