Skip to content

Commit

Permalink
feat: add multi database support
Browse files Browse the repository at this point in the history
  • Loading branch information
seuros committed Jan 17, 2024
1 parent b362c22 commit 3af1cf8
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 47 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
- activerecord_6.1
- activerecord_edge
adapter:
- 'sqlite3:///:memory:'
- '' # SQLite3
- mysql2://root:root@0/closure_tree_test
- postgres://closure_tree:closure_tree@0/closure_tree_test
exclude:
Expand All @@ -59,7 +59,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand All @@ -74,7 +74,8 @@ jobs:
- name: RSpec
env:
RAILS_VERSION: ${{ matrix.rails }}
DB_ADAPTER: ${{ matrix.adapter }}
DATABASE_URL: ${{ matrix.adapter }}
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
run: bin/rake
7 changes: 4 additions & 3 deletions .github/workflows/ci_jruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ jobs:
- activerecord_7.0
- activerecord_6.1
adapter:
- 'sqlite3:///:memory:'
- '' # SQLite3
- mysql2://root:root@0/closure_tree_test
- postgres://closure_tree:closure_tree@0/closure_tree_test
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand All @@ -63,7 +63,8 @@ jobs:
- name: RSpec
env:
RAILS_VERSION: ${{ matrix.rails }}
DB_ADAPTER: ${{ matrix.adapter }}
DATABASE_URL: ${{ matrix.adapter }}
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
run: bin/rake
7 changes: 4 additions & 3 deletions .github/workflows/ci_truffleruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ jobs:
- activerecord_7.0
- activerecord_6.1
adapter:
- 'sqlite3:///:memory:'
- '' # SQLite3
- mysql2://root:root@0/closure_tree_test
- postgres://closure_tree:closure_tree@0/closure_tree_test

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
Expand All @@ -66,7 +66,8 @@ jobs:
- name: RSpec
env:
RAILS_VERSION: ${{ matrix.rails }}
DB_ADAPTER: ${{ matrix.adapter }}
DATABASE_URL: ${{ matrix.adapter }}
SECONDARY_DATABASE_URL: ${{ matrix.adapter }}
BUNDLE_GEMFILE: gemfiles/${{ matrix.rails }}.gemfile
WITH_ADVISORY_LOCK_PREFIX: ${{ github.run_id }}
run: bin/rake
2 changes: 1 addition & 1 deletion lib/closure_tree/support_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def quoted_order_column(include_table_name = true)

# table_name alias keyword , like "AS". When used on table name alias, Oracle Database don't support used 'AS'
def t_alias_keyword
(ActiveRecord::Base.connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
(connection.adapter_name.to_sym == :OracleEnhanced) ? "" : "AS"
end
end
end
7 changes: 6 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@
end
end

database_file = SecureRandom.hex
ActiveRecord::Base.configurations = {
default_env: {
url: ENV.fetch('DATABASE_URL', "sqlite3::memory:"),
url: ENV['DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}.sqlite3",
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
},
secondary_env: {
url: ENV['SECONDARY_DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}-s.sqlite3",
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
}
}
Expand Down
4 changes: 3 additions & 1 deletion spec/support/exceed_query_limit.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# Derived from http://stackoverflow.com/a/13423584/153896. Updated for RSpec 3.
RSpec::Matchers.define :exceed_query_limit do |expected|
supports_block_expectations
Expand All @@ -6,7 +8,7 @@
query_count(&block) > expected
end

failure_message_when_negated do |actual|
failure_message_when_negated do |_actual|
"Expected to run maximum #{expected} queries, got #{@counter.query_count}"
end

Expand Down
4 changes: 3 additions & 1 deletion spec/support/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# frozen_string_literal: true

# See http://stackoverflow.com/a/22388177/1268016
def count_queries(&block)
count = 0
counter_fn = ->(name, started, finished, unique_id, payload) do
counter_fn = lambda do |_name, _started, _finished, _unique_id, payload|
count += 1 unless %w[CACHE SCHEMA].include? payload[:name]
end
ActiveSupport::Notifications.subscribed(counter_fn, 'sql.active_record', &block)
Expand Down
6 changes: 1 addition & 5 deletions spec/support/models.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# frozen_string_literal: true

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

class Tag < ApplicationRecord
has_closure_tree dependent: :destroy, order: :name
before_destroy :add_destroyed_tag
Expand Down Expand Up @@ -146,6 +142,6 @@ class Adamantium < Metal
class Unobtanium < Metal
end

class MenuItem < ApplicationRecord
class MenuItem < SecondDatabaseRecord
has_closure_tree touch: true, with_advisory_lock: false
end
6 changes: 4 additions & 2 deletions spec/support/query_counter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# From http://stackoverflow.com/a/13423584/153896
module ActiveRecord
class QueryCounter
Expand All @@ -11,8 +13,8 @@ def to_proc
lambda(&method(:callback))
end

def callback(name, start, finish, message_id, values)
@query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
def callback(_name, _start, _finish, _message_id, values)
@query_count += 1 unless %w[CACHE SCHEMA].include?(values[:name])
end
end
end
46 changes: 30 additions & 16 deletions spec/support/schema.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
# frozen_string_literal: true

class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

class SecondDatabaseRecord < ActiveRecord::Base
self.abstract_class = true

establish_connection :secondary_env
end

ActiveRecord::Schema.define(version: 0) do
create_table 'tags', force: :cascade do |t|
connection.create_table 'tags', force: :cascade do |t|
t.string 'name'
t.string 'title'
t.references 'parent'
Expand Down Expand Up @@ -126,18 +136,6 @@
t.integer 'generations', null: false
end

create_table 'menu_items', force: :cascade do |t|
t.string 'name'
t.references 'parent'
t.timestamps null: false
end

create_table 'menu_item_hierarchies', id: false, force: :cascade do |t|
t.references 'ancestor', null: false
t.references 'descendant', null: false
t.integer 'generations', null: false
end

add_index 'label_hierarchies', %i[ancestor_id descendant_id generations], unique: true,
name: 'lh_anc_desc_idx'
add_index 'label_hierarchies', [:descendant_id], name: 'lh_desc_idx'
Expand All @@ -149,9 +147,25 @@
add_foreign_key(:users, :users, column: 'referrer_id', on_delete: :cascade)
add_foreign_key(:labels, :labels, column: 'mother_id', on_delete: :cascade)
add_foreign_key(:metal, :metal, column: 'parent_id', on_delete: :cascade)
add_foreign_key(:menu_items, :menu_items, column: 'parent_id', on_delete: :cascade)
add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'ancestor_id', on_delete: :cascade)
add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'descendant_id', on_delete: :cascade)
add_foreign_key(:tag_hierarchies, :tags, column: 'ancestor_id', on_delete: :cascade)
add_foreign_key(:tag_hierarchies, :tags, column: 'descendant_id', on_delete: :cascade)
end

SecondDatabaseRecord.connection_pool.with_connection do |connection|
ActiveRecord::Schema.define(version: 0) do
connection.create_table 'menu_items', force: :cascade do |t|
t.string 'name'
t.references 'parent'
t.timestamps null: false
end

connection.create_table 'menu_item_hierarchies', id: false, force: :cascade do |t|
t.references 'ancestor', null: false
t.references 'descendant', null: false
t.integer 'generations', null: false
end
connection.add_foreign_key(:menu_items, :menu_items, column: 'parent_id', on_delete: :cascade)
connection.add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'ancestor_id', on_delete: :cascade)
connection.add_foreign_key(:menu_item_hierarchies, :menu_items, column: 'descendant_id', on_delete: :cascade)
end
end
15 changes: 15 additions & 0 deletions test/closure_tree/model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,18 @@
assert_equal Tag._ct, Tag.new._ct
end
end

describe "multi database support" do
it 'should have a different connection for menu items' do
# These 2 models are in the same database
assert_equal Tag.connection, Metal.connection
# The hierarchy table is in the same database
assert_equal Tag.connection, TagHierarchy.connection

# However, these 2 models are in different databases
refute_equal MenuItem.connection, Tag.connection
# The hierarchy table is in the same database
assert_equal MenuItem.connection, MenuItemHierarchy.connection
end

end
19 changes: 8 additions & 11 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
# frozen_string_literal: true

require 'erb'
require 'active_record'
require 'with_advisory_lock'
require 'tmpdir'
require 'securerandom'
require 'minitest'
require 'minitest/autorun'
require 'database_cleaner'
require 'support/query_counter'
require 'parallel'

database_file = SecureRandom.hex
ActiveRecord::Base.configurations = {
default_env: {
url: ENV.fetch('DATABASE_URL', "sqlite3://#{Dir.tmpdir}/#{SecureRandom.hex}.sqlite3"),
url: ENV['DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}.sqlite3",
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
},
secondary_env: {
url: ENV['SECONDARY_DATABASE_URL'].presence || "sqlite3://#{Dir.tmpdir}/#{database_file}-s.sqlite3",
properties: { allowPublicKeyRetrieval: true } # for JRuby madness
}
}
Expand All @@ -23,11 +24,7 @@
ActiveRecord::Base.establish_connection

def env_db
@env_db ||= if ActiveRecord::Base.respond_to?(:connection_db_config)
ActiveRecord::Base.connection_db_config.adapter
else
ActiveRecord::Base.connection_config[:adapter]
end.to_sym
@env_db ||= ActiveRecord::Base.connection_db_config.adapter.to_sym
end

ActiveRecord::Migration.verbose = false
Expand All @@ -40,7 +37,6 @@ def sqlite?
env_db == :sqlite3
end

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

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

0 comments on commit 3af1cf8

Please sign in to comment.