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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +13 -0
  3. data/README.md +111 -38
  4. data/bin/rails +15 -0
  5. data/bin/rake +7 -7
  6. data/closure_tree.gemspec +11 -17
  7. data/lib/closure_tree/active_record_support.rb +4 -1
  8. data/lib/closure_tree/adapter_support.rb +11 -0
  9. data/lib/closure_tree/arel_helpers.rb +83 -0
  10. data/lib/closure_tree/configuration.rb +2 -0
  11. data/lib/closure_tree/deterministic_ordering.rb +2 -0
  12. data/lib/closure_tree/digraphs.rb +6 -4
  13. data/lib/closure_tree/finders.rb +103 -54
  14. data/lib/closure_tree/has_closure_tree.rb +5 -2
  15. data/lib/closure_tree/has_closure_tree_root.rb +12 -17
  16. data/lib/closure_tree/hash_tree.rb +2 -1
  17. data/lib/closure_tree/hash_tree_support.rb +38 -13
  18. data/lib/closure_tree/hierarchy_maintenance.rb +19 -26
  19. data/lib/closure_tree/model.rb +29 -29
  20. data/lib/closure_tree/numeric_deterministic_ordering.rb +90 -55
  21. data/lib/closure_tree/numeric_order_support.rb +20 -18
  22. data/lib/closure_tree/support.rb +29 -32
  23. data/lib/closure_tree/support_attributes.rb +31 -5
  24. data/lib/closure_tree/support_flags.rb +2 -12
  25. data/lib/closure_tree/test/matcher.rb +10 -12
  26. data/lib/closure_tree/version.rb +3 -1
  27. data/lib/closure_tree.rb +22 -2
  28. data/lib/generators/closure_tree/config_generator.rb +3 -1
  29. data/lib/generators/closure_tree/migration_generator.rb +6 -4
  30. data/lib/generators/closure_tree/templates/config.rb +2 -0
  31. metadata +12 -104
  32. data/.github/workflows/ci.yml +0 -72
  33. data/.github/workflows/ci_jruby.yml +0 -68
  34. data/.github/workflows/ci_truffleruby.yml +0 -71
  35. data/.github/workflows/release.yml +0 -17
  36. data/.gitignore +0 -17
  37. data/.release-please-manifest.json +0 -1
  38. data/.rspec +0 -1
  39. data/.tool-versions +0 -1
  40. data/.yardopts +0 -3
  41. data/Appraisals +0 -61
  42. data/Gemfile +0 -6
  43. data/Rakefile +0 -32
  44. data/bin/appraisal +0 -29
  45. data/bin/rspec +0 -29
  46. data/mktree.rb +0 -38
  47. data/release-please-config.json +0 -4
  48. data/test/closure_tree/cache_invalidation_test.rb +0 -36
  49. data/test/closure_tree/cuisine_type_test.rb +0 -42
  50. data/test/closure_tree/generator_test.rb +0 -49
  51. data/test/closure_tree/has_closure_tree_root_test.rb +0 -80
  52. data/test/closure_tree/hierarchy_maintenance_test.rb +0 -56
  53. data/test/closure_tree/label_test.rb +0 -674
  54. data/test/closure_tree/metal_test.rb +0 -59
  55. data/test/closure_tree/model_test.rb +0 -9
  56. data/test/closure_tree/namespace_type_test.rb +0 -13
  57. data/test/closure_tree/parallel_test.rb +0 -162
  58. data/test/closure_tree/pool_test.rb +0 -33
  59. data/test/closure_tree/support_test.rb +0 -18
  60. data/test/closure_tree/tag_test.rb +0 -8
  61. data/test/closure_tree/user_test.rb +0 -175
  62. data/test/closure_tree/uuid_tag_test.rb +0 -8
  63. data/test/support/query_counter.rb +0 -25
  64. data/test/support/tag_examples.rb +0 -923
  65. 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