lib/keycard/db.rb in keycard-0.1.2 vs lib/keycard/db.rb in keycard-0.2.0

- old
+ new

@@ -2,161 +2,159 @@ require 'ostruct' require 'logger' require 'yaml' -module Keycard - # Module for database interactions for Keycard. - module DB - # Any error with the database that Keycard itself detects but cannot handle. - class DatabaseError < StandardError; end +# Module for database interactions for Keycard. +module Keycard::DB + # Any error with the database that Keycard itself detects but cannot handle. + class DatabaseError < StandardError; end - CONNECTION_ERROR = 'The Keycard database is not initialized. Call initialize! first.' + CONNECTION_ERROR = 'The Keycard database is not initialized. Call initialize! first.' - ALREADY_CONNECTED = 'Already connected; refusing to connect to another database.' + 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 + 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 + LOAD_ERROR = <<~MSG + Error loading Keycard database models. + Verify connection information and that the database is migrated. + MSG - SCHEMA_HEADER = "# Keycard Database Version\n" + SCHEMA_HEADER = "# Keycard Database Version\n" - class << self - # 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. - 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 + class << self + # 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. + def initialize! + connect! unless connected? + begin + model_files.each do |file| + require_relative file end - db + rescue Sequel::DatabaseError, NoMethodError => e + raise DatabaseError, LOAD_ERROR + "\n" + e.message end + db + end - # 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. - # - # @see {Sequel.connect} - # @param [Hash] config Optional connection config - # @option config [String] :url A Sequel database URL - # @option config [Hash] :opts A set of connection options - # @option config [Sequel::Database] :db An already-connected database; - # @return [Sequel::Database] The initialized database connection - def connect!(config = {}) - raise DatabaseError, ALREADY_CONNECTED if connected? - merge_config!(config) - raise DatabaseError, MISSING_CONFIG if self.config.db.nil? && conn_opts.empty? + # 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. + # + # @see {Sequel.connect} + # @param [Hash] config Optional connection config + # @option config [String] :url A Sequel database URL + # @option config [Hash] :opts A set of connection options + # @option config [Sequel::Database] :db An already-connected database; + # @return [Sequel::Database] The initialized database connection + 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 + # 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 - # Run any pending migrations. - # This will connect with the current config if not already conencted. - def migrate! - connect! unless connected? - unless config.readonly - Sequel.extension :migration - Sequel::Migrator.run(db, File.join(__dir__, '../../db/migrations'), table: schema_table) - end - end + # Run any pending migrations. + # This will connect with the current config if not already conencted. + def migrate! + connect! unless connected? + return if config.readonly - def schema_table - :keycard_schema - end + Sequel.extension :migration + Sequel::Migrator.run(db, File.join(__dir__, '../../db/migrations'), table: schema_table) + end - def schema_file - 'db/keycard.yml' - end + def schema_table + :keycard_schema + end - def dump_schema! - connect! unless connected? - version = db[schema_table].first.to_yaml - File.write(schema_file, SCHEMA_HEADER + version) - end + def schema_file + 'db/keycard.yml' + end - def load_schema! - connect! unless connected? - version = YAML.load_file(schema_file)[:version] - db[schema_table].delete - db[schema_table].insert(version: version) - end + def dump_schema! + connect! unless connected? + version = db[schema_table].first.to_yaml + File.write(schema_file, SCHEMA_HEADER + version) + end - def model_files - [] - end + 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 url, opts, or db settings from a hash into our config - 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 + def model_files + [] + end - 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 + # Merge url, opts, or db settings from a hash into our config + 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 - def config - @config ||= OpenStruct.new( - url: ENV['KEYCARD_DATABASE_URL'] || ENV['DATABASE_URL'], - readonly: false - ) + 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 - def connected? - !@db.nil? - end + def config + @config ||= OpenStruct.new( + url: ENV['KEYCARD_DATABASE_URL'] || ENV['DATABASE_URL'], + readonly: false + ) + end - # The Keycard database - # @return [Sequel::Database] The connected database; be sure to call initialize! first. - def db - raise DatabaseError, CONNECTION_ERROR unless connected? - @db - end + def connected? + !@db.nil? + end - # Forward the Sequel::Database []-syntax down to db for convenience. - # Everything else must be called on db directly, but this is nice sugar. - def [](*args) - db[*args] - end + # The Keycard database + # @return [Sequel::Database] The connected database; be sure to call initialize! first. + def db + raise DatabaseError, CONNECTION_ERROR unless connected? + @db + end + + # Forward the Sequel::Database []-syntax down to db for convenience. + # Everything else must be called on db directly, but this is nice sugar. + def [](*args) + db[*args] end end end