closure_tree 8.0.0 → 9.0.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/CHANGELOG.md +13 -0
- data/README.md +111 -38
- data/bin/rails +15 -0
- data/bin/rake +7 -7
- data/closure_tree.gemspec +11 -17
- data/lib/closure_tree/active_record_support.rb +4 -1
- data/lib/closure_tree/adapter_support.rb +11 -0
- data/lib/closure_tree/arel_helpers.rb +83 -0
- data/lib/closure_tree/configuration.rb +2 -0
- data/lib/closure_tree/deterministic_ordering.rb +2 -0
- data/lib/closure_tree/digraphs.rb +6 -4
- data/lib/closure_tree/finders.rb +103 -54
- data/lib/closure_tree/has_closure_tree.rb +5 -2
- data/lib/closure_tree/has_closure_tree_root.rb +12 -17
- data/lib/closure_tree/hash_tree.rb +2 -1
- data/lib/closure_tree/hash_tree_support.rb +38 -13
- data/lib/closure_tree/hierarchy_maintenance.rb +19 -26
- data/lib/closure_tree/model.rb +29 -29
- data/lib/closure_tree/numeric_deterministic_ordering.rb +90 -55
- data/lib/closure_tree/numeric_order_support.rb +20 -18
- data/lib/closure_tree/support.rb +29 -32
- data/lib/closure_tree/support_attributes.rb +31 -5
- data/lib/closure_tree/support_flags.rb +2 -12
- data/lib/closure_tree/test/matcher.rb +10 -12
- data/lib/closure_tree/version.rb +3 -1
- data/lib/closure_tree.rb +22 -2
- data/lib/generators/closure_tree/config_generator.rb +3 -1
- data/lib/generators/closure_tree/migration_generator.rb +6 -4
- data/lib/generators/closure_tree/templates/config.rb +2 -0
- metadata +12 -104
- data/.github/workflows/ci.yml +0 -72
- data/.github/workflows/ci_jruby.yml +0 -68
- data/.github/workflows/ci_truffleruby.yml +0 -71
- data/.github/workflows/release.yml +0 -17
- data/.gitignore +0 -17
- data/.release-please-manifest.json +0 -1
- data/.rspec +0 -1
- data/.tool-versions +0 -1
- data/.yardopts +0 -3
- data/Appraisals +0 -61
- data/Gemfile +0 -6
- data/Rakefile +0 -32
- data/bin/appraisal +0 -29
- data/bin/rspec +0 -29
- data/mktree.rb +0 -38
- data/release-please-config.json +0 -4
- data/test/closure_tree/cache_invalidation_test.rb +0 -36
- data/test/closure_tree/cuisine_type_test.rb +0 -42
- data/test/closure_tree/generator_test.rb +0 -49
- data/test/closure_tree/has_closure_tree_root_test.rb +0 -80
- data/test/closure_tree/hierarchy_maintenance_test.rb +0 -56
- data/test/closure_tree/label_test.rb +0 -674
- data/test/closure_tree/metal_test.rb +0 -59
- data/test/closure_tree/model_test.rb +0 -9
- data/test/closure_tree/namespace_type_test.rb +0 -13
- data/test/closure_tree/parallel_test.rb +0 -162
- data/test/closure_tree/pool_test.rb +0 -33
- data/test/closure_tree/support_test.rb +0 -18
- data/test/closure_tree/tag_test.rb +0 -8
- data/test/closure_tree/user_test.rb +0 -175
- data/test/closure_tree/uuid_tag_test.rb +0 -8
- data/test/support/query_counter.rb +0 -25
- data/test/support/tag_examples.rb +0 -923
- data/test/test_helper.rb +0 -99
@@ -1,36 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class CacheInvalidationTest < ActiveSupport::TestCase
|
4
|
-
def setup
|
5
|
-
Timecop.travel(10.seconds.ago) do
|
6
|
-
#create a long tree with 2 branch
|
7
|
-
@root = MenuItem.create(
|
8
|
-
name: SecureRandom.hex(10)
|
9
|
-
)
|
10
|
-
2.times do
|
11
|
-
parent = @root
|
12
|
-
10.times do
|
13
|
-
parent = parent.children.create(
|
14
|
-
name: SecureRandom.hex(10)
|
15
|
-
)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
@first_leaf = MenuItem.leaves.first
|
19
|
-
@second_leaf = MenuItem.leaves.last
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
test "touch option should invalidate cache for all it ancestors" do
|
24
|
-
old_time_stamp = @first_leaf.ancestors.pluck(:updated_at)
|
25
|
-
@first_leaf.touch
|
26
|
-
new_time_stamp = @first_leaf.ancestors.pluck(:updated_at)
|
27
|
-
assert_not_equal old_time_stamp, new_time_stamp, 'Cache not invalidated for all ancestors'
|
28
|
-
end
|
29
|
-
|
30
|
-
test "touch option should not invalidate cache for another branch" do
|
31
|
-
old_time_stamp = @second_leaf.updated_at
|
32
|
-
@first_leaf.touch
|
33
|
-
new_time_stamp = @second_leaf.updated_at
|
34
|
-
assert_equal old_time_stamp, new_time_stamp, 'Cache incorrectly invalidated for another branch'
|
35
|
-
end
|
36
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'test_helper'
|
4
|
-
|
5
|
-
def assert_lineage(e, m)
|
6
|
-
assert_equal e, m.parent
|
7
|
-
assert_equal [m, e], m.self_and_ancestors
|
8
|
-
|
9
|
-
# make sure reloading doesn't affect the self_and_ancestors:
|
10
|
-
m.reload
|
11
|
-
assert_equal [m, e], m.self_and_ancestors
|
12
|
-
end
|
13
|
-
|
14
|
-
describe CuisineType do
|
15
|
-
it 'finds self and parents when children << is used' do
|
16
|
-
e = CuisineType.new(name: 'e')
|
17
|
-
m = CuisineType.new(name: 'm')
|
18
|
-
e.children << m
|
19
|
-
e.save
|
20
|
-
assert_lineage(e, m)
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'finds self and parents properly if the constructor is used' do
|
24
|
-
e = CuisineType.create(name: 'e')
|
25
|
-
m = CuisineType.create(name: 'm', parent: e)
|
26
|
-
assert_lineage(e, m)
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'sets the table_name of the hierarchy class properly' do
|
30
|
-
assert_equal(
|
31
|
-
"#{ActiveRecord::Base.table_name_prefix}cuisine_type_hierarchies#{ActiveRecord::Base.table_name_suffix}", CuisineTypeHierarchy.table_name
|
32
|
-
)
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'fixes self_and_ancestors properly on reparenting' do
|
36
|
-
a = CuisineType.create! name: 'a'
|
37
|
-
b = CuisineType.create! name: 'b'
|
38
|
-
assert_equal([b], b.self_and_ancestors.to_a)
|
39
|
-
a.children << b
|
40
|
-
assert_equal([b, a], b.self_and_ancestors.to_a)
|
41
|
-
end
|
42
|
-
end
|
@@ -1,49 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'test_helper'
|
4
|
-
require 'generators/closure_tree/migration_generator'
|
5
|
-
|
6
|
-
module ClosureTree
|
7
|
-
module Generators
|
8
|
-
class MigrationGeneratorTest < Rails::Generators::TestCase
|
9
|
-
tests MigrationGenerator
|
10
|
-
destination File.expand_path('../tmp', __dir__)
|
11
|
-
setup :prepare_destination
|
12
|
-
|
13
|
-
def test_generator_output
|
14
|
-
run_generator %w[tag]
|
15
|
-
migration_file = migration_file_name('db/migrate/create_tag_hierarchies.rb')
|
16
|
-
content = File.read(migration_file)
|
17
|
-
assert_match(/t.integer :ancestor_id, null: false/, content)
|
18
|
-
assert_match(/t.integer :descendant_id, null: false/, content)
|
19
|
-
assert_match(/t.integer :generations, null: false/, content)
|
20
|
-
assert_match(/add_index :tag_hierarchies/, content)
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_generator_output_with_namespaced_model
|
24
|
-
run_generator %w[Namespace::Type]
|
25
|
-
migration_file = migration_file_name('db/migrate/create_namespace_type_hierarchies.rb')
|
26
|
-
content = File.read(migration_file)
|
27
|
-
assert_match(/t.integer :ancestor_id, null: false/, content)
|
28
|
-
assert_match(/t.integer :descendant_id, null: false/, content)
|
29
|
-
assert_match(/t.integer :generations, null: false/, content)
|
30
|
-
assert_match(/add_index :namespace_type_hierarchies/, content)
|
31
|
-
end
|
32
|
-
|
33
|
-
def test_generator_output_with_namespaced_model_with_slash
|
34
|
-
run_generator %w[namespace/type]
|
35
|
-
migration_file = migration_file_name('db/migrate/create_namespace_type_hierarchies.rb')
|
36
|
-
content = File.read(migration_file)
|
37
|
-
assert_match(/t.integer :ancestor_id, null: false/, content)
|
38
|
-
assert_match(/t.integer :descendant_id, null: false/, content)
|
39
|
-
assert_match(/t.integer :generations, null: false/, content)
|
40
|
-
assert_match(/add_index :namespace_type_hierarchies/, content)
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_should_run_all_tasks_in_generator_without_errors
|
44
|
-
gen = generator %w[tag]
|
45
|
-
capture_io { gen.invoke_all }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class HasClosureTreeRootTest < ActiveSupport::TestCase
|
4
|
-
setup do
|
5
|
-
ENV['FLOCK_DIR'] = Dir.mktmpdir
|
6
|
-
end
|
7
|
-
|
8
|
-
teardown do
|
9
|
-
FileUtils.remove_entry_secure ENV['FLOCK_DIR']
|
10
|
-
end
|
11
|
-
def create_tree(group)
|
12
|
-
@ct1 = ContractType.create!(name: "Type1")
|
13
|
-
@ct2 = ContractType.create!(name: "Type2")
|
14
|
-
@user1 = User.create!(email: "1@example.com", group_id: group.id)
|
15
|
-
@user2 = User.create!(email: "2@example.com", group_id: group.id)
|
16
|
-
@user3 = User.create!(email: "3@example.com", group_id: group.id)
|
17
|
-
@user4 = User.create!(email: "4@example.com", group_id: group.id)
|
18
|
-
@user5 = User.create!(email: "5@example.com", group_id: group.id)
|
19
|
-
@user6 = User.create!(email: "6@example.com", group_id: group.id)
|
20
|
-
|
21
|
-
# The tree (contract types in parens)
|
22
|
-
#
|
23
|
-
# U1(1)
|
24
|
-
# / \
|
25
|
-
# U2(1) U3(1&2)
|
26
|
-
# / / \
|
27
|
-
# U4(2) U5(1) U6(2)
|
28
|
-
|
29
|
-
@user1.children << @user2
|
30
|
-
@user1.children << @user3
|
31
|
-
@user2.children << @user4
|
32
|
-
@user3.children << @user5
|
33
|
-
@user3.children << @user6
|
34
|
-
|
35
|
-
@user1.contracts.create!(title: "Contract 1", contract_type: @ct1)
|
36
|
-
@user2.contracts.create!(title: "Contract 2", contract_type: @ct1)
|
37
|
-
@user3.contracts.create!(title: "Contract 3", contract_type: @ct1)
|
38
|
-
@user3.contracts.create!(title: "Contract 4", contract_type: @ct2)
|
39
|
-
@user4.contracts.create!(title: "Contract 5", contract_type: @ct2)
|
40
|
-
@user5.contracts.create!(title: "Contract 6", contract_type: @ct1)
|
41
|
-
@user6.contracts.create!(title: "Contract 7", contract_type: @ct2)
|
42
|
-
end
|
43
|
-
|
44
|
-
test "loads all nodes in a constant number of queries" do
|
45
|
-
group = Group.create!(name: "TheGrouping")
|
46
|
-
create_tree(group)
|
47
|
-
reloaded_group = group.reload
|
48
|
-
exceed_query_limit(2) do
|
49
|
-
root = reloaded_group.root_user_including_tree
|
50
|
-
assert_equal "2@example.com", root.children[0].email
|
51
|
-
assert_equal "3@example.com", root.children[0].parent.children[1].email
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
test "loads all nodes plus single association in a constant number of queries" do
|
56
|
-
group = Group.create!(name: "TheGrouping")
|
57
|
-
create_tree(group)
|
58
|
-
reloaded_group = group.reload
|
59
|
-
exceed_query_limit(3) do
|
60
|
-
root = reloaded_group.root_user_including_tree(:contracts)
|
61
|
-
assert_equal "2@example.com", root.children[0].email
|
62
|
-
assert_equal "3@example.com", root.children[0].parent.children[1].email
|
63
|
-
assert_equal "Contract 7", root.children[0].children[0].contracts[0].user.parent.parent.children[1].children[1].contracts[0].title
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
test "loads all nodes and associations in a constant number of queries" do
|
68
|
-
group = Group.create!(name: "TheGrouping")
|
69
|
-
create_tree(group)
|
70
|
-
reloaded_group = group.reload
|
71
|
-
exceed_query_limit(4) do
|
72
|
-
root = reloaded_group.root_user_including_tree(contracts: :contract_type)
|
73
|
-
assert_equal "2@example.com", root.children[0].email
|
74
|
-
assert_equal "3@example.com", root.children[0].parent.children[1].email
|
75
|
-
assert_equal %w[Type1 Type2], root.children[1].contracts.map(&:contract_type).map(&:name)
|
76
|
-
assert_equal "Type1", root.children[1].children[0].contracts[0].contract_type.name
|
77
|
-
assert_equal "Type2", root.children[0].children[0].contracts[0].user.parent.parent.children[1].children[1].contracts[0].contract_type.name
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'test_helper'
|
4
|
-
|
5
|
-
describe ClosureTree::HierarchyMaintenance do
|
6
|
-
describe '.rebuild!' do
|
7
|
-
it 'rebuild tree' do
|
8
|
-
20.times do |counter|
|
9
|
-
Metal.create(value: "Nitro-#{counter}", parent: Metal.all.sample)
|
10
|
-
end
|
11
|
-
hierarchy_count = MetalHierarchy.count
|
12
|
-
assert_operator hierarchy_count, :>, (20 * 2) - 1 # shallowest-possible case, where all children use the first root
|
13
|
-
MetalHierarchy.delete_all
|
14
|
-
Metal.rebuild!
|
15
|
-
assert_equal MetalHierarchy.count, hierarchy_count
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
describe '.cleanup!' do
|
20
|
-
before do
|
21
|
-
@parent = Metal.create(value: 'parent metal')
|
22
|
-
@child = Metal.create(value: 'child metal', parent: @parent)
|
23
|
-
MetalHierarchy.delete_all
|
24
|
-
Metal.rebuild!
|
25
|
-
end
|
26
|
-
|
27
|
-
describe 'when an element is deleted' do
|
28
|
-
it 'should delete the child hierarchies' do
|
29
|
-
@child.delete
|
30
|
-
|
31
|
-
Metal.cleanup!
|
32
|
-
|
33
|
-
assert_empty MetalHierarchy.where(descendant_id: @child.id)
|
34
|
-
assert_empty MetalHierarchy.where(ancestor_id: @child.id)
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'should not delete the parent hierarchies' do
|
38
|
-
@child.delete
|
39
|
-
Metal.cleanup!
|
40
|
-
assert_equal 1, MetalHierarchy.where(ancestor_id: @parent.id).size
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'should not delete other hierarchies' do
|
44
|
-
other_parent = Metal.create(value: 'other parent metal')
|
45
|
-
other_child = Metal.create(value: 'other child metal', parent: other_parent)
|
46
|
-
Metal.rebuild!
|
47
|
-
|
48
|
-
@child.delete
|
49
|
-
Metal.cleanup!
|
50
|
-
|
51
|
-
assert_equal 2, MetalHierarchy.where(ancestor_id: other_parent.id).size
|
52
|
-
assert_equal 2, MetalHierarchy.where(descendant_id: other_child.id).size
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|