Module: SmartBrain::DB
- Defined in:
- lib/smart_brain/db.rb
Overview
SmartBrain::DB owns the Sequel/Postgres connection and schema migration.
Backed by config/brain.yml storage.database (overridable via
SMARTBRAIN_DB_HOST,PORT,NAME,USER,PASSWORD env). Connections are memoized
per connection signature so the runtime, server, and CLI share one pool.
Constant Summary collapse
- SCHEMA_SQL_PATH =
File.('../../db/migrate/001_init.sql', __dir__)
- MIGRATIONS_DIR =
File.('../../db/migrate', __dir__)
- DEFAULT_ADAPTER =
'postgres'
Class Method Summary collapse
-
.connect(config) ⇒ Object
Returns a memoized Sequel::Database for the given config (or config hash).
-
.disconnect! ⇒ Object
Drop all memoized connections (used by tests / reconfigure).
-
.migrate(db) ⇒ Object
Run all bundled migration SQL files in order (each is idempotent: CREATE TABLE/INDEX IF NOT EXISTS, ADD COLUMN IF NOT EXISTS).
-
.reachable?(config) ⇒ Boolean
True if a Postgres connection can be established for the given config.
-
.stringify_keys(obj) ⇒ Object
Stringify keys for storage into a JSONB column (Sequel pg_json stores string keys; symbol keys round-trip fine but strings are canonical).
-
.symbolize_jsonb(obj) ⇒ Object
Recursively symbolize a JSONB value read from Postgres.
Class Method Details
.connect(config) ⇒ Object
Returns a memoized Sequel::Database for the given config (or config hash).
20 21 22 23 24 25 26 27 28 29 |
# File 'lib/smart_brain/db.rb', line 20 def connect(config) opts = connection_opts(config) signature = opts.hash return pools[signature] if pools.key?(signature) db = Sequel.connect(opts) db.sql_log_level = :error if db.respond_to?(:sql_log_level=) db.extension(:pg_json) rescue nil pools[signature] = db end |
.disconnect! ⇒ Object
Drop all memoized connections (used by tests / reconfigure).
55 56 57 58 |
# File 'lib/smart_brain/db.rb', line 55 def disconnect! pools.each_value(&:disconnect) pools.clear end |
.migrate(db) ⇒ Object
Run all bundled migration SQL files in order (each is idempotent: CREATE TABLE/INDEX IF NOT EXISTS, ADD COLUMN IF NOT EXISTS).
33 34 35 36 37 38 |
# File 'lib/smart_brain/db.rb', line 33 def migrate(db) Dir.glob(File.join(MIGRATIONS_DIR, '*.sql')).sort.each do |path| db.run(File.read(path)) end true end |
.reachable?(config) ⇒ Boolean
True if a Postgres connection can be established for the given config.
41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/smart_brain/db.rb', line 41 def reachable?(config) db = Sequel.connect(connection_opts(config)) begin db.test_connection rescue StandardError false else true ensure db.disconnect if db end end |
.stringify_keys(obj) ⇒ Object
Stringify keys for storage into a JSONB column (Sequel pg_json stores string keys; symbol keys round-trip fine but strings are canonical).
83 84 85 86 87 |
# File 'lib/smart_brain/db.rb', line 83 def stringify_keys(obj) return obj unless obj.is_a?(Hash) obj.transform_keys(&:to_s) end |
.symbolize_jsonb(obj) ⇒ Object
Recursively symbolize a JSONB value read from Postgres. Robust whether the adapter returns a parsed Hash/Array, a Sequel JSONBHash/JSONBArray wrapper (NOT a Hash subclass in recent Sequel), or a raw JSON String.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/smart_brain/db.rb', line 63 def symbolize_jsonb(obj) case obj when String begin parsed = JSON.parse(obj) parsed.is_a?(Hash) || parsed.is_a?(Array) ? symbolize_jsonb(parsed) : obj rescue JSON::ParserError, TypeError obj end when ->(o) { o.respond_to?(:to_hash) } obj.to_hash.each_with_object({}) { |(k, v), h| h[k.to_sym] = symbolize_jsonb(v) } when ->(o) { !o.nil? && !o.is_a?(String) && o.respond_to?(:to_a) } obj.to_a.map { |v| symbolize_jsonb(v) } else obj end end |