closure_tree 6.5.0 → 7.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +98 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -1
  5. data/Appraisals +90 -7
  6. data/CHANGELOG.md +100 -42
  7. data/Gemfile +3 -11
  8. data/README.md +68 -24
  9. data/Rakefile +16 -10
  10. data/_config.yml +1 -0
  11. data/bin/appraisal +29 -0
  12. data/bin/rake +29 -0
  13. data/bin/rspec +29 -0
  14. data/closure_tree.gemspec +16 -9
  15. data/lib/closure_tree/finders.rb +32 -9
  16. data/lib/closure_tree/has_closure_tree.rb +4 -0
  17. data/lib/closure_tree/has_closure_tree_root.rb +5 -7
  18. data/lib/closure_tree/hash_tree_support.rb +4 -4
  19. data/lib/closure_tree/hierarchy_maintenance.rb +28 -8
  20. data/lib/closure_tree/model.rb +42 -16
  21. data/lib/closure_tree/numeric_deterministic_ordering.rb +20 -6
  22. data/lib/closure_tree/numeric_order_support.rb +7 -3
  23. data/lib/closure_tree/support.rb +18 -12
  24. data/lib/closure_tree/support_attributes.rb +10 -1
  25. data/lib/closure_tree/support_flags.rb +1 -4
  26. data/lib/closure_tree/version.rb +1 -1
  27. data/lib/generators/closure_tree/migration_generator.rb +8 -0
  28. data/lib/generators/closure_tree/templates/create_hierarchies_table.rb.erb +1 -1
  29. metadata +78 -79
  30. data/.travis.yml +0 -29
  31. data/gemfiles/activerecord_4.2.gemfile +0 -19
  32. data/gemfiles/activerecord_5.0.gemfile +0 -19
  33. data/gemfiles/activerecord_5.0_foreigner.gemfile +0 -20
  34. data/gemfiles/activerecord_edge.gemfile +0 -20
  35. data/img/example.png +0 -0
  36. data/img/preorder.png +0 -0
  37. data/spec/cache_invalidation_spec.rb +0 -39
  38. data/spec/cuisine_type_spec.rb +0 -38
  39. data/spec/db/database.yml +0 -21
  40. data/spec/db/models.rb +0 -128
  41. data/spec/db/schema.rb +0 -166
  42. data/spec/fixtures/tags.yml +0 -98
  43. data/spec/generators/migration_generator_spec.rb +0 -48
  44. data/spec/has_closure_tree_root_spec.rb +0 -154
  45. data/spec/hierarchy_maintenance_spec.rb +0 -16
  46. data/spec/label_spec.rb +0 -554
  47. data/spec/matcher_spec.rb +0 -34
  48. data/spec/metal_spec.rb +0 -55
  49. data/spec/model_spec.rb +0 -9
  50. data/spec/namespace_type_spec.rb +0 -13
  51. data/spec/parallel_spec.rb +0 -159
  52. data/spec/spec_helper.rb +0 -24
  53. data/spec/support/database.rb +0 -52
  54. data/spec/support/database_cleaner.rb +0 -14
  55. data/spec/support/exceed_query_limit.rb +0 -18
  56. data/spec/support/hash_monkey_patch.rb +0 -13
  57. data/spec/support/query_counter.rb +0 -18
  58. data/spec/support/sqlite3_with_advisory_lock.rb +0 -10
  59. data/spec/support_spec.rb +0 -14
  60. data/spec/tag_examples.rb +0 -665
  61. data/spec/tag_spec.rb +0 -6
  62. data/spec/user_spec.rb +0 -174
  63. data/spec/uuid_tag_spec.rb +0 -6
data/spec/db/schema.rb DELETED
@@ -1,166 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- ActiveRecord::Schema.define(:version => 0) do
4
-
5
- create_table "tags" do |t|
6
- t.string "name"
7
- t.string "title"
8
- t.integer "parent_id"
9
- t.integer "sort_order"
10
- t.timestamps null: false
11
- end
12
-
13
- add_foreign_key(:tags, :tags, :column => 'parent_id')
14
-
15
- create_table "tag_hierarchies", :id => false do |t|
16
- t.integer "ancestor_id", :null => false
17
- t.integer "descendant_id", :null => false
18
- t.integer "generations", :null => false
19
- end
20
-
21
- add_foreign_key(:tag_hierarchies, :tags, :column => 'ancestor_id')
22
- add_foreign_key(:tag_hierarchies, :tags, :column => 'descendant_id')
23
-
24
- create_table "uuid_tags", :id => false do |t|
25
- t.string "uuid", :unique => true
26
- t.string "name"
27
- t.string "title"
28
- t.string "parent_uuid"
29
- t.integer "sort_order"
30
- t.timestamps null: false
31
- end
32
-
33
- create_table "uuid_tag_hierarchies", :id => false do |t|
34
- t.string "ancestor_id", :null => false
35
- t.string "descendant_id", :null => false
36
- t.integer "generations", :null => false
37
- end
38
-
39
- create_table "destroyed_tags" do |t|
40
- t.string "name"
41
- end
42
-
43
- add_index "tag_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "tag_anc_desc_idx"
44
- add_index "tag_hierarchies", [:descendant_id], :name => "tag_desc_idx"
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
-
62
- create_table "users" do |t|
63
- t.string "email"
64
- t.integer "referrer_id"
65
- t.integer "group_id"
66
- t.timestamps null: false
67
- end
68
-
69
- add_foreign_key(:users, :users, :column => 'referrer_id')
70
-
71
- create_table "contracts" do |t|
72
- t.integer "user_id", :null => false
73
- t.integer "contract_type_id"
74
- t.string "title"
75
- end
76
-
77
- create_table "contract_types" do |t|
78
- t.string "name", :null => false
79
- end
80
-
81
- create_table "referral_hierarchies", :id => false do |t|
82
- t.integer "ancestor_id", :null => false
83
- t.integer "descendant_id", :null => false
84
- t.integer "generations", :null => false
85
- end
86
-
87
- add_index "referral_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "ref_anc_desc_idx"
88
- add_index "referral_hierarchies", [:descendant_id], :name => "ref_desc_idx"
89
-
90
- create_table "labels" do |t|
91
- t.string "name"
92
- t.string "type"
93
- t.integer "column_whereby_ordering_is_inferred"
94
- t.integer "mother_id"
95
- end
96
-
97
- add_foreign_key(:labels, :labels, :column => 'mother_id')
98
-
99
- create_table "label_hierarchies", :id => false do |t|
100
- t.integer "ancestor_id", :null => false
101
- t.integer "descendant_id", :null => false
102
- t.integer "generations", :null => false
103
- end
104
-
105
- add_index "label_hierarchies", [:ancestor_id, :descendant_id, :generations], :unique => true, :name => "lh_anc_desc_idx"
106
- add_index "label_hierarchies", [:descendant_id], :name => "lh_desc_idx"
107
-
108
- create_table "cuisine_types" do |t|
109
- t.string "name"
110
- t.integer "parent_id"
111
- end
112
-
113
- create_table "cuisine_type_hierarchies", :id => false do |t|
114
- t.integer "ancestor_id", :null => false
115
- t.integer "descendant_id", :null => false
116
- t.integer "generations", :null => false
117
- end
118
-
119
- create_table "namespace_types" do |t|
120
- t.string "name"
121
- t.integer "parent_id"
122
- end
123
-
124
- create_table "namespace_type_hierarchies", :id => false do |t|
125
- t.integer "ancestor_id", :null => false
126
- t.integer "descendant_id", :null => false
127
- t.integer "generations", :null => false
128
- end
129
-
130
- create_table "metal" do |t|
131
- t.integer "parent_id"
132
- t.string "metal_type"
133
- t.string "value"
134
- t.string "description"
135
- t.integer "sort_order"
136
- end
137
-
138
- add_foreign_key(:metal, :metal, :column => 'parent_id')
139
-
140
- create_table "metal_hierarchies", :id => false do |t|
141
- t.integer "ancestor_id", :null => false
142
- t.integer "descendant_id", :null => false
143
- t.integer "generations", :null => false
144
- end
145
-
146
- add_foreign_key(:metal_hierarchies, :metal, :column => 'ancestor_id')
147
- add_foreign_key(:metal_hierarchies, :metal, :column => 'descendant_id')
148
-
149
- create_table 'menu_items' do |t|
150
- t.string 'name'
151
- t.integer 'parent_id'
152
- t.timestamps null: false
153
- end
154
-
155
- add_foreign_key(:menu_items, :menu_items, :column => 'parent_id')
156
-
157
- create_table 'menu_item_hierarchies', :id => false do |t|
158
- t.integer 'ancestor_id', :null => false
159
- t.integer 'descendant_id', :null => false
160
- t.integer 'generations', :null => false
161
- end
162
-
163
- add_foreign_key(:menu_item_hierarchies, :menu_items, :column => 'ancestor_id')
164
- add_foreign_key(:menu_item_hierarchies, :menu_items, :column => 'descendant_id')
165
-
166
- end
@@ -1,98 +0,0 @@
1
- # Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html
2
-
3
- grandparent:
4
- name: grandparent
5
- title: Nonnie
6
-
7
- parent:
8
- name: parent
9
- parent: grandparent
10
- title: Mom
11
-
12
- child:
13
- name: child
14
- parent: parent
15
- title: Kid
16
-
17
- people:
18
- name: people
19
-
20
- # people has no children
21
-
22
- events:
23
- name: events
24
-
25
- # events has only one child
26
-
27
- birthday:
28
- name: birthday
29
- parent: events
30
-
31
- places:
32
- name: places
33
-
34
- # places has many children, with many depths
35
-
36
- home:
37
- name: home
38
- parent: places
39
-
40
- indoor:
41
- name: indoor
42
- parent: places
43
-
44
- outdoor:
45
- name: outdoor
46
- parent: places
47
-
48
- museum:
49
- name: museum
50
- parent: places
51
-
52
- united_states:
53
- name: united_states
54
- parent: places
55
-
56
- california:
57
- name: california
58
- parent: united_states
59
-
60
- san_francisco:
61
- name: san_francisco
62
- parent: california
63
-
64
-
65
- # Move and deletion test tree
66
-
67
- a1:
68
- name: a1
69
-
70
- b1:
71
- name: b1
72
- parent: a1
73
-
74
- b2:
75
- name: b2
76
- parent: a1
77
-
78
- c1a:
79
- name: c1a
80
- parent: b1
81
- sort_order: 2
82
-
83
- c1b:
84
- name: c1b
85
- parent: b1
86
- sort_order: 1
87
-
88
- c2:
89
- name: c2
90
- parent: b2
91
-
92
- d2:
93
- name: d2
94
- parent: c2
95
-
96
- e2:
97
- name: e2
98
- parent: d2
@@ -1,48 +0,0 @@
1
- # require 'spec_helper'
2
- # require 'ammeter/init'
3
- #
4
- # # Generators are not automatically loaded by Rails
5
- # require 'generators/closure_tree/migration_generator'
6
- #
7
- # RSpec.describe ClosureTree::Generators::MigrationGenerator, type: :generator do
8
- # TMPDIR = Dir.mktmpdir
9
- # # Tell generator where to put its output
10
- # destination TMPDIR
11
- # before { prepare_destination }
12
- #
13
- # describe 'generator output' do
14
- # before { run_generator %w(tag) }
15
- # subject { migration_file('db/migrate/create_tag_hierarchies.rb') }
16
- # it { is_expected.to be_a_migration }
17
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
18
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
19
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
20
- # it { is_expected.to contain(/add_index :tag_hierarchies/) }
21
- # end
22
- #
23
- # describe 'generator output with namespaced model' do
24
- # before { run_generator %w(Namespace::Type) }
25
- # subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
26
- # it { is_expected.to be_a_migration }
27
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
28
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
29
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
30
- # it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
31
- # end
32
- #
33
- # describe 'generator output with namespaced model with /' do
34
- # before { run_generator %w(namespace/type) }
35
- # subject { migration_file('db/migrate/create_namespace_type_hierarchies.rb') }
36
- # it { is_expected.to be_a_migration }
37
- # it { is_expected.to contain(/t.integer :ancestor_id, null: false/) }
38
- # it { is_expected.to contain(/t.integer :descendant_id, null: false/) }
39
- # it { is_expected.to contain(/t.integer :generations, null: false/) }
40
- # it { is_expected.to contain(/add_index :namespace_type_hierarchies/) }
41
- # end
42
- #
43
- # it 'should run all tasks in generator without errors' do
44
- # gen = generator %w(tag)
45
- # expect(gen).to receive :create_migration_file
46
- # capture(:stdout) { gen.invoke_all }
47
- # end
48
- # end
@@ -1,154 +0,0 @@
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!(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
- end
37
-
38
- context "with basic config" do
39
- let!(:group) { Group.create!(name: "TheGroup") }
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
-
59
- it "loads all nodes and associations in a constant number of queries" do
60
- expect do
61
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
62
- expect(root.children[0].email).to eq "2@example.com"
63
- expect(root.children[0].parent.children[1].email).to eq "3@example.com"
64
- expect(root.children[1].contracts.map(&:contract_type).map(&:name)).to eq %w(Type1 Type2)
65
- expect(root.children[1].children[0].contracts[0].contract_type.name).to eq "Type1"
66
- expect(root.children[0].children[0].contracts[0].user.
67
- parent.parent.children[1].children[1].contracts[0].contract_type.name).to eq "Type2"
68
- end.to_not exceed_query_limit(4) # Without this feature, this is 15, and scales with number of nodes.
69
- end
70
-
71
- it "memoizes by assoc_map" do
72
- group_reloaded.root_user_including_tree.email = "x"
73
- expect(group_reloaded.root_user_including_tree.email).to eq "x"
74
- group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
75
- expect(group_reloaded.root_user_including_tree(contracts: :contract_type).email).to eq "y"
76
- expect(group_reloaded.root_user_including_tree.email).to eq "x"
77
- end
78
-
79
- it "doesn't memoize if true argument passed" do
80
- group_reloaded.root_user_including_tree.email = "x"
81
- expect(group_reloaded.root_user_including_tree(true).email).to eq "1@example.com"
82
- group_reloaded.root_user_including_tree(contracts: :contract_type).email = "y"
83
- expect(group_reloaded.root_user_including_tree(true, contracts: :contract_type).email).
84
- to eq "1@example.com"
85
- end
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
-
91
- it "eager loads inverse association to group" do
92
- expect do
93
- root = group_reloaded.root_user_including_tree
94
- expect(root.group).to eq group
95
- expect(root.children[0].group).to eq group
96
- end.to_not exceed_query_limit(2)
97
- end
98
-
99
- it "works if eager load association map is not given" do
100
- expect do
101
- root = group_reloaded.root_user_including_tree
102
- expect(root.children[0].email).to eq "2@example.com"
103
- expect(root.children[0].parent.children[1].children[0].email).to eq "5@example.com"
104
- end.to_not exceed_query_limit(2)
105
- end
106
-
107
- context "with no tree root" do
108
- let(:group2) { Group.create!(name: "OtherGroup") }
109
-
110
- it "should return nil" do
111
- expect(group2.root_user_including_tree(contracts: :contract_type)).to be_nil
112
- end
113
- end
114
-
115
- context "with multiple tree roots" do
116
- let!(:other_root) { User.create!(email: "10@example.com", group_id: group.id) }
117
-
118
- it "should error" do
119
- expect do
120
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
121
- end.to raise_error(ClosureTree::MultipleRootError)
122
- end
123
- end
124
- end
125
-
126
- context "with explicit class_name and foreign_key" do
127
- let(:group) { Grouping.create!(name: "TheGrouping") }
128
-
129
- it "should still work" do
130
- root = group_reloaded.root_person_including_tree(contracts: :contract_type)
131
- expect(root.children[0].email).to eq "2@example.com"
132
- end
133
- end
134
-
135
- context "with bad class_name" do
136
- let(:group) { UserSet.create!(name: "TheUserSet") }
137
-
138
- it "should error" do
139
- expect do
140
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
141
- end.to raise_error(NameError)
142
- end
143
- end
144
-
145
- context "with bad foreign_key" do
146
- let(:group) { Team.create!(name: "TheTeam") }
147
-
148
- it "should error" do
149
- expect do
150
- root = group_reloaded.root_user_including_tree(contracts: :contract_type)
151
- end.to raise_error(ActiveRecord::StatementInvalid)
152
- end
153
- end
154
- end
@@ -1,16 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ClosureTree::HierarchyMaintenance do
4
- describe '.rebuild!' do
5
- it 'rebuild tree' do
6
- 20.times do |counter|
7
- Metal.create(:value => "Nitro-#{counter}", parent: Metal.all.sample)
8
- end
9
- hierarchy_count = MetalHierarchy.count
10
- expect(hierarchy_count).to be > (20*2)-1 # shallowest-possible case, where all children use the first root
11
- MetalHierarchy.delete_all
12
- Metal.rebuild!
13
- expect(MetalHierarchy.count).to eq(hierarchy_count)
14
- end
15
- end
16
- end