Skip to content

Commit cd0fb68

Browse files
committed
Move database path handling from BaseConfig into the SQLite adapter
Closes #214
1 parent 1f87853 commit cd0fb68

File tree

7 files changed

+211
-248
lines changed

7 files changed

+211
-248
lines changed

lib/active_record/tenanted/database_adapters/sqlite.rb

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,44 @@
22

33
module ActiveRecord
44
module Tenanted
5-
module DatabaseAdapters
6-
class SQLite # :nodoc:
5+
module DatabaseAdapters # :nodoc:
6+
#
7+
# TODO: This still feels to me like it's not _quite_ right. I think we could further refactor this by:
8+
#
9+
# 1. Moving tenant_databases and validate_tenant_name to BaseConfig, and subclassing it for
10+
# each database
11+
# 2. Moving create_database, drop_database, database_exist?, database_ready?,
12+
# acquire_ready_lock, ensure_database_directory_exists, and database_path to the SQLite
13+
# connection adapter, possibly into Rails
14+
# 3. Moving test_workerize and path_for to be SQLite connection adapter class methods,
15+
# possibly into Rails
16+
#
17+
class SQLite
18+
attr_reader :db_config
19+
720
def initialize(db_config)
821
@db_config = db_config
922
end
1023

24+
def tenant_databases
25+
glob = path_for(db_config.database_for("*"))
26+
scanner = Regexp.new(path_for(db_config.database_for("(.+)")))
27+
28+
Dir.glob(glob).filter_map do |path|
29+
result = path.scan(scanner).flatten.first
30+
if result.nil?
31+
Rails.logger.warn "ActiveRecord::Tenanted: Cannot parse tenant name from filename #{path.inspect}"
32+
end
33+
result
34+
end
35+
end
36+
37+
def validate_tenant_name(tenant_name)
38+
if tenant_name.match?(%r{[/'"`]})
39+
raise BadTenantNameError, "Tenant name contains an invalid character: #{tenant_name.inspect}"
40+
end
41+
end
42+
1143
def create_database
1244
ensure_database_directory_exists
1345
FileUtils.touch(database_path)
@@ -28,29 +60,10 @@ def database_ready?
2860
File.exist?(database_path) && !ActiveRecord::Tenanted::Mutex::Ready.locked?(database_path)
2961
end
3062

31-
def tenant_databases
32-
glob = db_config.database_path_for("*")
33-
scanner = Regexp.new(db_config.database_path_for("(.+)"))
34-
35-
Dir.glob(glob).filter_map do |path|
36-
result = path.scan(scanner).flatten.first
37-
if result.nil?
38-
Rails.logger.warn "ActiveRecord::Tenanted: Cannot parse tenant name from filename #{path.inspect}"
39-
end
40-
result
41-
end
42-
end
43-
4463
def acquire_ready_lock(&block)
4564
ActiveRecord::Tenanted::Mutex::Ready.lock(database_path, &block)
4665
end
4766

48-
def validate_tenant_name(tenant_name)
49-
if tenant_name.match?(%r{[/'"`]})
50-
raise BadTenantNameError, "Tenant name contains an invalid character: #{tenant_name.inspect}"
51-
end
52-
end
53-
5467
def ensure_database_directory_exists
5568
return unless database_path
5669

@@ -60,25 +73,32 @@ def ensure_database_directory_exists
6073
end
6174
end
6275

63-
private
64-
attr_reader :db_config
76+
def database_path
77+
path_for(db_config.database)
78+
end
79+
80+
def test_workerize(db, test_worker_id)
81+
test_worker_suffix = "_#{test_worker_id}"
6582

66-
def database_path
67-
coerce_path(db_config.database)
83+
if db.start_with?("file:") && db.include?("?")
84+
db.sub(/(\?.*)$/, "#{test_worker_suffix}\\1")
85+
else
86+
db + test_worker_suffix
6887
end
88+
end
6989

70-
# A sqlite database path can be a file path or a URI (either relative or absolute). We
71-
# can't parse it as a standard URI in all circumstances, though, see
72-
# https://sqlite.org/uri.html
73-
def coerce_path(path)
74-
if path.start_with?("file:/")
75-
URI.parse(path).path
76-
elsif path.start_with?("file:")
77-
URI.parse(path.sub(/\?.*$/, "")).opaque
78-
else
79-
path
80-
end
90+
# A sqlite database path can be a file path or a URI (either relative or absolute). We
91+
# can't parse it as a standard URI in all circumstances, though, see
92+
# https://sqlite.org/uri.html
93+
def path_for(database)
94+
if database.start_with?("file:/")
95+
URI.parse(database).path
96+
elsif database.start_with?("file:")
97+
URI.parse(database.sub(/\?.*$/, "")).opaque
98+
else
99+
database
81100
end
101+
end
82102
end
83103
end
84104
end

lib/active_record/tenanted/database_configurations/base_config.rb

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,15 @@ def database_tasks?
2525
def database_for(tenant_name)
2626
tenant_name = tenant_name.to_s
2727

28-
validate_tenant_name(tenant_name)
28+
config_adapter.validate_tenant_name(tenant_name)
2929

30-
path = sprintf(database, tenant: tenant_name)
30+
db = sprintf(database, tenant: tenant_name)
3131

3232
if test_worker_id
33-
test_worker_path(path)
34-
else
35-
path
33+
db = config_adapter.test_workerize(db, test_worker_id)
3634
end
37-
end
3835

39-
def database_path_for(tenant_name)
40-
coerce_path(database_for(tenant_name))
36+
db
4137
end
4238

4339
def tenants
@@ -64,33 +60,6 @@ def new_connection
6460
def max_connection_pools
6561
(configuration_hash[:max_connection_pools] || DEFAULT_MAX_CONNECTION_POOLS).to_i
6662
end
67-
68-
private
69-
# A sqlite database path can be a file path or a URI (either relative or absolute).
70-
# We can't parse it as a standard URI in all circumstances, though, see https://sqlite.org/uri.html
71-
def coerce_path(path)
72-
if path.start_with?("file:/")
73-
URI.parse(path).path
74-
elsif path.start_with?("file:")
75-
URI.parse(path.sub(/\?.*$/, "")).opaque
76-
else
77-
path
78-
end
79-
end
80-
81-
def validate_tenant_name(tenant_name)
82-
config_adapter.validate_tenant_name(tenant_name)
83-
end
84-
85-
def test_worker_path(path)
86-
test_worker_suffix = "_#{test_worker_id}"
87-
88-
if path.start_with?("file:") && path.include?("?")
89-
path.sub(/(\?.*)$/, "#{test_worker_suffix}\\1")
90-
else
91-
path + test_worker_suffix
92-
end
93-
end
9463
end
9564
end
9665
end

lib/active_record/tenanted/database_configurations/tenant_config.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ def default_schema_cache_path(db_dir = "db")
4848
File.join(db_dir, "#{tenanted_config_name}_schema_cache.yml")
4949
end
5050
end
51-
52-
def database_path
53-
configuration_hash[:database_path]
54-
end
5551
end
5652
end
5753
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
describe ActiveRecord::Tenanted::DatabaseAdapters::SQLite do
6+
let(:adapter) { ActiveRecord::Tenanted::DatabaseAdapters::SQLite.new(Object.new) }
7+
let(:dir) { Dir.mktmpdir }
8+
9+
describe "path_for" do
10+
test "file path" do
11+
database = "storage/db/tenanted/foo/main.sqlite3"
12+
expected = "storage/db/tenanted/foo/main.sqlite3"
13+
assert_equal(expected, adapter.path_for(database))
14+
end
15+
16+
test "absolute URI" do
17+
database = "file:#{dir}/storage/db/tenanted/foo/main.sqlite3"
18+
expected = "#{dir}/storage/db/tenanted/foo/main.sqlite3"
19+
assert_equal(expected, adapter.path_for(database))
20+
end
21+
22+
test "absolute URI with query params" do
23+
database = "file:#{dir}/storage/db/tenanted/foo/main.sqlite3?vfs=unix-dotfile"
24+
expected = "#{dir}/storage/db/tenanted/foo/main.sqlite3"
25+
assert_equal(expected, adapter.path_for(database))
26+
end
27+
28+
test "relative URI" do
29+
database = "file:storage/db/tenanted/foo/main.sqlite3"
30+
expected = "storage/db/tenanted/foo/main.sqlite3"
31+
assert_equal(expected, adapter.path_for(database))
32+
end
33+
34+
test "relative URI with query params" do
35+
database = "file:storage/db/tenanted/foo/main.sqlite3?vfs=unix-dotfile"
36+
expected = "storage/db/tenanted/foo/main.sqlite3"
37+
assert_equal(expected, adapter.path_for(database))
38+
end
39+
end
40+
end

0 commit comments

Comments
 (0)