Skip to content

Commit 3af1cf8

Browse files
committed
feat: add multi database support
1 parent b362c22 commit 3af1cf8

File tree

12 files changed

+83
-47
lines changed

12 files changed

+83
-47
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- activerecord_6.1
5151
- activerecord_edge
5252
adapter:
53-
- 'sqlite3:///:memory:'
53+
- '' # SQLite3
5454
- mysql2://root:root@0/closure_tree_test
5555
- postgres://closure_tree:closure_tree@0/closure_tree_test
5656
exclude:
@@ -59,7 +59,7 @@ jobs:
5959

6060
steps:
6161
- name: Checkout
62-
uses: actions/checkout@v3
62+
uses: actions/checkout@v4
6363

6464
- name: Setup Ruby
6565
uses: ruby/setup-ruby@v1
@@ -74,7 +74,8 @@ jobs:
7474
- name: RSpec
7575
env:
7676
RAILS_VERSION: ${{ matrix.rails }}
77-
DB_ADAPTER: ${{ matrix.adapter }}
77+
DATABASE_URL: ${{ matrix.adapter }}
78+
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
7879
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
7980
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
8081
run: bin/rake

.github/workflows/ci_jruby.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ jobs:
4343
- activerecord_7.0
4444
- activerecord_6.1
4545
adapter:
46-
- 'sqlite3:///:memory:'
46+
- '' # SQLite3
4747
- mysql2://root:root@0/closure_tree_test
4848
- postgres://closure_tree:closure_tree@0/closure_tree_test
4949
steps:
5050
- name: Checkout
51-
uses: actions/checkout@v3
51+
uses: actions/checkout@v4
5252

5353
- name: Setup Ruby
5454
uses: ruby/setup-ruby@v1
@@ -63,7 +63,8 @@ jobs:
6363
- name: RSpec
6464
env:
6565
RAILS_VERSION: ${{ matrix.rails }}
66-
DB_ADAPTER: ${{ matrix.adapter }}
66+
DATABASE_URL: ${{ matrix.adapter }}
67+
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
6768
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
6869
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
6970
run: bin/rake

.github/workflows/ci_truffleruby.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ jobs:
4545
- activerecord_7.0
4646
- activerecord_6.1
4747
adapter:
48-
- 'sqlite3:///:memory:'
48+
- '' # SQLite3
4949
- mysql2://root:root@0/closure_tree_test
5050
- postgres://closure_tree:closure_tree@0/closure_tree_test
5151

5252
steps:
5353
- name: Checkout
54-
uses: actions/checkout@v3
54+
uses: actions/checkout@v4
5555

5656
- name: Setup Ruby
5757
uses: ruby/setup-ruby@v1
@@ -66,7 +66,8 @@ jobs:
6666
- name: RSpec
6767
env:
6868
RAILS_VERSION: ${{ matrix.rails }}
69-
DB_ADAPTER: ${{ matrix.adapter }}
69+
DATABASE_URL: ${{ matrix.adapter }}
70+
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
7071
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
7172
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
7273
run: bin/rake

lib/closure_tree/support_attributes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def quoted_order_column(include_table_name = true)
117117

118118
# table_name alias keyword , like "AS". When used on table name alias, Oracle Database don't support used 'AS'
119119
def t_alias_keyword
120-
(ActiveRecord::Base.connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
120+
(connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
121121
end
122122
end
123123
end

spec/spec_helper.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@
2020
end
2121
end
2222

23+
database_file = SecureRandom.hex
2324
ActiveRecord::Base.configurations = {
2425
default_env: {
25-
url: ENV.fetch('DATABASE_URL', "sqlite3::memory:"),
26+
url: ENV['DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}.sqlite3",
27+
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
28+
},
29+
secondary_env: {
30+
url: ENV['SECONDARY_DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}-s.sqlite3",
2631
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
2732
}
2833
}

spec/support/exceed_query_limit.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
# Derived from http://stackoverflow.com/a/13423584/153896. Updated for RSpec 3.
24
RSpec::Matchers.define :exceed_query_limit do |expected|
35
supports_block_expectations
@@ -6,7 +8,7 @@
68
query_count(&block) > expected
79
end
810

9-
failure_message_when_negated do |actual|
11+
failure_message_when_negated do |_actual|
1012
"Expected to run maximum #{expected} queries, got #{@counter.query_count}"
1113
end
1214

spec/support/helpers.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
# frozen_string_literal: true
2+
13
# See http://stackoverflow.com/a/22388177/1268016
24
def count_queries(&block)
35
count = 0
4-
counter_fn = ->(name, started, finished, unique_id, payload) do
6+
counter_fn = lambda do |_name, _started, _finished, _unique_id, payload|
57
count += 1 unless %w[CACHE SCHEMA].include? payload[:name]
68
end
79
ActiveSupport::Notifications.subscribed(counter_fn, 'sql.active_record', &block)

spec/support/models.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
# frozen_string_literal: true
22

3-
class ApplicationRecord < ActiveRecord::Base
4-
self.abstract_class = true
5-
end
6-
73
class Tag < ApplicationRecord
84
has_closure_tree dependent: :destroy, order: :name
95
before_destroy :add_destroyed_tag
@@ -146,6 +142,6 @@ class Adamantium < Metal
146142
class Unobtanium < Metal
147143
end
148144

149-
class MenuItem < ApplicationRecord
145+
class MenuItem < SecondDatabaseRecord
150146
has_closure_tree touch: true, with_advisory_lock: false
151147
end

spec/support/query_counter.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# frozen_string_literal: true
2+
13
# From http://stackoverflow.com/a/13423584/153896
24
module ActiveRecord
35
class QueryCounter
@@ -11,8 +13,8 @@ def to_proc
1113
lambda(&method(:callback))
1214
end
1315

14-
def callback(name, start, finish, message_id, values)
15-
@query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
16+
def callback(_name, _start, _finish, _message_id, values)
17+
@query_count += 1 unless %w[CACHE SCHEMA].include?(values[:name])
1618
end
1719
end
1820
end

spec/support/schema.rb

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
# frozen_string_literal: true
22

3+
class ApplicationRecord < ActiveRecord::Base
4+
self.abstract_class = true
5+
end
6+
7+
class SecondDatabaseRecord < ActiveRecord::Base
8+
self.abstract_class = true
9+
10+
establish_connection :secondary_env
11+
end
12+
313
ActiveRecord::Schema.define(version: 0) do
4-
create_table 'tags', force: :cascade do |t|
14+
connection.create_table 'tags', force: :cascade do |t|
515
t.string 'name'
616
t.string 'title'
717
t.references 'parent'
@@ -126,18 +136,6 @@
126136
t.integer 'generations', null: false
127137
end
128138

129-
create_table 'menu_items', force: :cascade do |t|
130-
t.string 'name'
131-
t.references 'parent'
132-
t.timestamps null: false
133-
end
134-
135-
create_table 'menu_item_hierarchies', id: false, force: :cascade do |t|
136-
t.references 'ancestor', null: false
137-
t.references 'descendant', null: false
138-
t.integer 'generations', null: false
139-
end
140-
141139
add_index 'label_hierarchies', %i[ancestor_id descendant_id generations], unique: true,
142140
name: 'lh_anc_desc_idx'
143141
add_index 'label_hierarchies', [:descendant_id], name: 'lh_desc_idx'
@@ -149,9 +147,25 @@
149147
add_foreign_key(:users, :users, column: 'referrer_id', on_delete: :cascade)
150148
add_foreign_key(:labels, :labels, column: 'mother_id', on_delete: :cascade)
151149
add_foreign_key(:metal, :metal, column: 'parent_id', on_delete: :cascade)
152-
add_foreign_key(:menu_items, :menu_items, column: 'parent_id', on_delete: :cascade)
153-
add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'ancestor_id', on_delete: :cascade)
154-
add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'descendant_id', on_delete: :cascade)
155150
add_foreign_key(:tag_hierarchies, :tags, column: 'ancestor_id', on_delete: :cascade)
156151
add_foreign_key(:tag_hierarchies, :tags, column: 'descendant_id', on_delete: :cascade)
157152
end
153+
154+
SecondDatabaseRecord.connection_pool.with_connection do |connection|
155+
ActiveRecord::Schema.define(version: 0) do
156+
connection.create_table 'menu_items', force: :cascade do |t|
157+
t.string 'name'
158+
t.references 'parent'
159+
t.timestamps null: false
160+
end
161+
162+
connection.create_table 'menu_item_hierarchies', id: false, force: :cascade do |t|
163+
t.references 'ancestor', null: false
164+
t.references 'descendant', null: false
165+
t.integer 'generations', null: false
166+
end
167+
connection.add_foreign_key(:menu_items, :menu_items, column: 'parent_id', on_delete: :cascade)
168+
connection.add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'ancestor_id', on_delete: :cascade)
169+
connection.add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'descendant_id', on_delete: :cascade)
170+
end
171+
end

test/closure_tree/model_test.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,18 @@
77
assert_equal Tag._ct, Tag.new._ct
88
end
99
end
10+
11+
describe "multi database support" do
12+
it 'should have a different connection for menu items' do
13+
# These 2 models are in the same database
14+
assert_equal Tag.connection, Metal.connection
15+
# The hierarchy table is in the same database
16+
assert_equal Tag.connection, TagHierarchy.connection
17+
18+
# However, these 2 models are in different databases
19+
refute_equal MenuItem.connection, Tag.connection
20+
# The hierarchy table is in the same database
21+
assert_equal MenuItem.connection, MenuItemHierarchy.connection
22+
end
23+
24+
end

test/test_helper.rb

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
11
# frozen_string_literal: true
22

3-
require 'erb'
43
require 'active_record'
5-
require 'with_advisory_lock'
6-
require 'tmpdir'
7-
require 'securerandom'
84
require 'minitest'
95
require 'minitest/autorun'
106
require 'database_cleaner'
117
require 'support/query_counter'
128
require 'parallel'
139

10+
database_file = SecureRandom.hex
1411
ActiveRecord::Base.configurations = {
1512
default_env: {
16-
url: ENV.fetch('DATABASE_URL', "sqlite3://#{Dir.tmpdir}/#{SecureRandom.hex}.sqlite3"),
13+
url: ENV['DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}.sqlite3",
14+
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
15+
},
16+
secondary_env: {
17+
url: ENV['SECONDARY_DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}-s.sqlite3",
1718
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
1819
}
1920
}
@@ -23,11 +24,7 @@
2324
ActiveRecord::Base.establish_connection
2425

2526
def env_db
26-
@env_db ||= if ActiveRecord::Base.respond_to?(:connection_db_config)
27-
ActiveRecord::Base.connection_db_config.adapter
28-
else
29-
ActiveRecord::Base.connection_config[:adapter]
30-
end.to_sym
27+
@env_db ||= ActiveRecord::Base.connection_db_config.adapter.to_sym
3128
end
3229

3330
ActiveRecord::Migration.verbose = false
@@ -40,7 +37,6 @@ def sqlite?
4037
env_db == :sqlite3
4138
end
4239

43-
ActiveRecord::Base.connection.recreate_database('closure_tree_test') unless sqlite?
4440
puts "Testing with #{env_db} database, ActiveRecord #{ActiveRecord.gem_version} and #{RUBY_ENGINE} #{RUBY_ENGINE_VERSION} as #{RUBY_VERSION}"
4541

4642
DatabaseCleaner.strategy = :truncation
@@ -67,3 +63,4 @@ class Spec
6763
require 'closure_tree'
6864
require_relative '../spec/support/schema'
6965
require_relative '../spec/support/models'
66+
ActiveRecord::Base.connection.recreate_database('closure_tree_test') unless sqlite?

0 commit comments

Comments
 (0)