Module: Kaal::Config::BackendFactory

Defined in:
lib/kaal/config/backend_factory.rb

Overview

Builds backend adapter instances from symbolic runtime configuration.

Constant Summary collapse

SUPPORTED_BACKENDS =
%w[memory redis sqlite postgres mysql].freeze

Class Method Summary collapse

Class Method Details

.adapter_name_for_error(adapter) ⇒ Object



157
158
159
# File 'lib/kaal/config/backend_factory.rb', line 157

def adapter_name_for_error(adapter)
  adapter == 'postgresql' ? 'postgres' : 'mysql'
end

.build(name, backend_config:, namespace:, runtime_context: nil) ⇒ Object



30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/kaal/config/backend_factory.rb', line 30

def build(name, backend_config:, namespace:, runtime_context: nil)
  backend_name = normalize_name(name)
  config = normalize_backend_config(backend_config)

  case backend_name
  when 'memory'
    Kaal::Backend::MemoryAdapter.new
  when 'redis'
    build_redis_backend(config, namespace)
  when 'sqlite'
    build_sqlite_backend(config, namespace, runtime_context)
  when 'postgres'
    build_postgres_backend(config, namespace)
  when 'mysql'
    build_mysql_backend(config, namespace)
  end
end

.build_mysql_backend(config, namespace) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/kaal/config/backend_factory.rb', line 90

def build_mysql_backend(config, namespace)
  use_skip_locked = config[:use_skip_locked]
  skip_locked_configured = config.key?(:use_skip_locked)

  if config.key?(:connection)
    connection = normalize_connection_hash(config[:connection], 'mysql2', nil)
    return Kaal::Backend::MySQL.new(connection:, namespace:) unless skip_locked_configured

    return Kaal::Backend::MySQL.new(connection:, namespace:, use_skip_locked:)
  end

  url = string_value(config[:url])
  raise Kaal::ConfigurationError, 'mysql backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?

  require_sequel!
  database = ::Sequel.connect(url)
  return Kaal::Backend::MySQL.new(database:, namespace:) unless skip_locked_configured

  Kaal::Backend::MySQL.new(database:, namespace:, use_skip_locked:)
end

.build_postgres_backend(config, namespace) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/kaal/config/backend_factory.rb', line 77

def build_postgres_backend(config, namespace)
  if config.key?(:connection)
    return Kaal::Backend::Postgres.new(connection: normalize_connection_hash(config[:connection], 'postgresql', nil),
                                       namespace:)
  end

  url = string_value(config[:url])
  raise Kaal::ConfigurationError, 'postgres backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?

  require_sequel!
  Kaal::Backend::Postgres.new(database: ::Sequel.connect(url), namespace:)
end

.build_redis_backend(config, namespace) ⇒ Object



53
54
55
56
57
58
59
60
# File 'lib/kaal/config/backend_factory.rb', line 53

def build_redis_backend(config, namespace)
  require_redis!

  url = string_value(config[:url])
  raise Kaal::ConfigurationError, 'redis backend requires backend_config.url or KAAL_BACKEND_URL' if url.empty?

  Kaal::Backend::RedisAdapter.new(::Redis.new(url: url), namespace:)
end

.build_sqlite_backend(config, namespace, runtime_context) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/kaal/config/backend_factory.rb', line 62

def build_sqlite_backend(config, namespace, runtime_context)
  return Kaal::Backend::SQLite.new(connection: build_sqlite_connection(config[:connection], runtime_context), namespace:) if config.key?(:connection)

  url = string_value(config[:url])
  database = string_value(config[:database])
  target = url.empty? ? database : url
  raise Kaal::ConfigurationError, 'sqlite backend requires backend_config.url, backend_config.database, or KAAL_BACKEND_URL' if target.empty?

  require_sequel!
  Kaal::Backend::SQLite.new(
    database: sequel_sqlite_database(target, runtime_context),
    namespace:
  )
end

.build_sqlite_connection(connection, runtime_context) ⇒ Object



111
112
113
# File 'lib/kaal/config/backend_factory.rb', line 111

def build_sqlite_connection(connection, runtime_context)
  normalize_connection_hash(connection, 'sqlite3', runtime_context)
end

.ensure_sqlite_directory!(database_path) ⇒ Object



151
152
153
154
155
# File 'lib/kaal/config/backend_factory.rb', line 151

def ensure_sqlite_directory!(database_path)
  directory = File.dirname(database_path)
  FileUtils.mkdir_p(directory) unless directory == '.' || directory.empty?
  database_path
end

.normalize_backend_config(backend_config) ⇒ Object



48
49
50
51
# File 'lib/kaal/config/backend_factory.rb', line 48

def normalize_backend_config(backend_config)
  hash = backend_config.is_a?(Hash) ? backend_config : {}
  Kaal::Support::HashTools.symbolize_keys(Kaal::Support::HashTools.deep_dup(hash))
end

.normalize_connection_hash(connection, default_adapter, runtime_context) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/kaal/config/backend_factory.rb', line 115

def normalize_connection_hash(connection, default_adapter, runtime_context)
  case connection
  when String
    connection
  when Hash
    normalized = Kaal::Support::HashTools.symbolize_keys(Kaal::Support::HashTools.deep_dup(connection))
    adapter = string_value(normalized[:adapter])
    normalized_adapter = adapter.empty? ? default_adapter : adapter
    normalized[:adapter] = normalized_adapter
    normalized[:database] = resolve_sqlite_database_path(normalized[:database], runtime_context) if normalized_adapter == 'sqlite3'
    normalized
  else
    raise Kaal::ConfigurationError, 'backend_config.connection must be a URL string or hash'
  end
end

.normalize_name(name) ⇒ Object



19
20
21
22
23
24
25
26
27
28
# File 'lib/kaal/config/backend_factory.rb', line 19

def normalize_name(name)
  normalized = name.to_s.strip.downcase
  return nil if normalized.empty?

  normalized = 'postgres' if normalized == 'postgresql'
  normalized = 'mysql' if normalized == 'trilogy'
  return normalized if SUPPORTED_BACKENDS.include?(normalized)

  raise Kaal::ConfigurationError, "Unsupported backend #{name.inspect}; use memory, redis, sqlite, postgres, or mysql"
end

.require_redis!Object



165
166
167
168
169
# File 'lib/kaal/config/backend_factory.rb', line 165

def require_redis!
  require 'redis'
rescue LoadError => e
  raise LoadError, "#{e.message}. Add `gem 'redis'` to your Gemfile to use the Redis-backed Kaal adapter.", cause: e
end

.require_sequel!Object



171
172
173
174
175
# File 'lib/kaal/config/backend_factory.rb', line 171

def require_sequel!
  require 'sequel'
rescue LoadError => e
  raise LoadError, "#{e.message}. Add `gem 'sequel'` to your Gemfile to use Sequel-backed Kaal SQL support.", cause: e
end

.resolve_sqlite_database_path(database, runtime_context) ⇒ Object



131
132
133
134
135
136
137
138
139
# File 'lib/kaal/config/backend_factory.rb', line 131

def resolve_sqlite_database_path(database, runtime_context)
  value = string_value(database)
  return value if value.empty?
  return value if sqlite_uri?(value)
  return ensure_sqlite_directory!(value) if Pathname.new(value).absolute?

  resolved = runtime_context ? runtime_context.resolve_path(value) : value
  ensure_sqlite_directory!(resolved)
end

.sequel_sqlite_database(target, runtime_context) ⇒ Object



145
146
147
148
149
# File 'lib/kaal/config/backend_factory.rb', line 145

def sequel_sqlite_database(target, runtime_context)
  return ::Sequel.connect(target) if sqlite_uri?(target)

  ::Sequel.connect(adapter: 'sqlite', database: resolve_sqlite_database_path(target, runtime_context))
end

.sqlite_uri?(value) ⇒ Boolean

Returns:

  • (Boolean)


141
142
143
# File 'lib/kaal/config/backend_factory.rb', line 141

def sqlite_uri?(value)
  value.start_with?('sqlite:', 'file:')
end

.string_value(value) ⇒ Object



161
162
163
# File 'lib/kaal/config/backend_factory.rb', line 161

def string_value(value)
  value.to_s.strip
end