Module: Keycard::DB

Defined in:
lib/keycard/db.rb

Overview

Module for database interactions for Keycard.

Defined Under Namespace

Classes: DatabaseError

Constant Summary collapse

CONNECTION_ERROR =
'The Keycard database is not initialized. Call initialize! first.'
ALREADY_CONNECTED =
'Already connected; refusing to connect to another database.'
MISSING_CONFIG =
<<~MSG
  KEYCARD_DATABASE_URL and DATABASE_URL are both missing and a connection
  has not been configured. Cannot connect to the Keycard database.
  See Keycard::DB.connect! for help.
MSG
LOAD_ERROR =
<<~MSG
  Error loading Keycard database models.
  Verify connection information and that the database is migrated.
MSG
SCHEMA_HEADER =
"# Keycard Database Version\n"

Class Method Summary collapse

Class Method Details

.[](*args) ⇒ Object

Forward the Sequel::Database []-syntax down to db for convenience. Everything else must be called on db directly, but this is nice sugar.



156
157
158
# File 'lib/keycard/db.rb', line 156

def [](*args)
  db[*args]
end

.configObject



136
137
138
139
140
141
# File 'lib/keycard/db.rb', line 136

def config
  @config ||= OpenStruct.new(
    url: ENV['KEYCARD_DATABASE_URL'] || ENV['DATABASE_URL'],
    readonly: false
  )
end

.conn_optsObject



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/keycard/db.rb', line 123

def conn_opts
  log = { logger: Logger.new('db/keycard.log') }
  url = config.url
  opts = config.opts
  if url
    [url, log]
  elsif opts
    [log.merge(opts)]
  else
    []
  end
end

.connect!(config = {}) ⇒ Sequel::Database

Connect to the Keycard database.

The default is to use the settings under config, but can be supplied here (and they will be merged into config as a side effect). The keys that will be used from either source are documented here as the options.

Only one “mode” will be used; the first of these supplied will take precedence:

  1. An already-connected Sequel::Database object

  2. A connection string

  3. A connection options hash

While Keycard serves as a singleton, this will raise a DatabaseError if already connected. Check ‘connected?` if you are unsure.

Parameters:

  • config (Hash) (defaults to: {})

    Optional connection config

Options Hash (config):

  • :url (String)

    A Sequel database URL

  • :opts (Hash)

    A set of connection options

  • :db (Sequel::Database)

    An already-connected database;

Returns:

  • (Sequel::Database)

    The initialized database connection

Raises:

See Also:

  • {Sequel{Sequel.connect}


71
72
73
74
75
76
77
78
79
# File 'lib/keycard/db.rb', line 71

def connect!(config = {})
  raise DatabaseError, ALREADY_CONNECTED if connected?
  merge_config!(config)
  raise DatabaseError, MISSING_CONFIG if self.config.db.nil? && conn_opts.empty?

  # We splat here because we might give one or two arguments depending
  # on whether we have a string or not; to add our logger regardless.
  @db = self.config.db || Sequel.connect(*conn_opts)
end

.connected?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/keycard/db.rb', line 143

def connected?
  !@db.nil?
end

.dbSequel::Database

The Keycard database

Returns:

  • (Sequel::Database)

    The connected database; be sure to call initialize! first.

Raises:



149
150
151
152
# File 'lib/keycard/db.rb', line 149

def db
  raise DatabaseError, CONNECTION_ERROR unless connected?
  @db
end

.dump_schema!Object



99
100
101
102
103
# File 'lib/keycard/db.rb', line 99

def dump_schema!
  connect! unless connected?
  version = db[schema_table].first.to_yaml
  File.write(schema_file, SCHEMA_HEADER + version)
end

.initialize!Object

Initialize Keycard

This connects to the database if it has not already happened and requires all of the Keycard model classes. It is required to do the connection setup first because of the design decision in Sequel that the schema is examined at the time of extending Sequel::Model.



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/keycard/db.rb', line 36

def initialize!
  connect! unless connected?
  begin
    model_files.each do |file|
      require_relative file
    end
  rescue Sequel::DatabaseError, NoMethodError => e
    raise DatabaseError, LOAD_ERROR + "\n" + e.message
  end
  db
end

.load_schema!Object



105
106
107
108
109
110
# File 'lib/keycard/db.rb', line 105

def load_schema!
  connect! unless connected?
  version = YAML.load_file(schema_file)[:version]
  db[schema_table].delete
  db[schema_table].insert(version: version)
end

.merge_config!(config = {}) ⇒ Object

Merge url, opts, or db settings from a hash into our config



117
118
119
120
121
# File 'lib/keycard/db.rb', line 117

def merge_config!(config = {})
  self.config.url  = config[:url]  if config.key?(:url)
  self.config.opts = config[:opts] if config.key?(:opts)
  self.config.db   = config[:db]   if config.key?(:db)
end

.migrate!Object

Run any pending migrations. This will connect with the current config if not already conencted.



83
84
85
86
87
88
89
# File 'lib/keycard/db.rb', line 83

def migrate!
  connect! unless connected?
  return if config.readonly

  Sequel.extension :migration
  Sequel::Migrator.run(db, File.join(__dir__, '../../db/migrations'), table: schema_table)
end

.model_filesObject



112
113
114
# File 'lib/keycard/db.rb', line 112

def model_files
  []
end

.schema_fileObject



95
96
97
# File 'lib/keycard/db.rb', line 95

def schema_file
  'db/keycard.yml'
end

.schema_tableObject



91
92
93
# File 'lib/keycard/db.rb', line 91

def schema_table
  :keycard_schema
end