Configuration

Put

require "jennifer"
require "jennifer/adapter/mysql" # for mysql
require "jennifer/adapter/postgres" # for postgres

Be attentive - adapter should be required after Jennifer. From 0.5.0 several adapters could be required at the same time.

SQLite3 adapter is in a separate shard.

This should be done before you load your application configurations (or at least models). Now configuration could be loaded from yaml file:

Jennifer::Config.read("./spec/fixtures/database.yml", :development)

Second argument presents environment and just use it as namespace key grepping values from yml.

defaults : &defaults
  host: localhost
  adapter: postgres
  user: developer
  password: 1qazxsw2
  migration_files_path: ./any/path/migrations # ./db/migrations by default

development:
  db: jennifer_develop
  <<: *defaults

test:
  db: jennifer_test
  <<: *defaults

You cam also use .ecr extension to leverage environmet variables in your configuration file. To do this use:

config_file = YAML.parse(ECR.render("config/database.yml.ecr"))
Jennifer::Config.configure do |conf|
  conf.from_yaml(config_file[ENV["APP_ENV"]])
end

All configurations also can be set using DSL:

Jennifer::Config.configure do |conf|
  conf.host = "localhost"
  conf.user = "root"
  conf.password = ""
  conf.adapter = "mysql"
  conf.db = "crystal"
  conf.migration_files_path = "./any/path/migrations"
end

If your configurations aren’t stored on the top level - you can manipulate which document subpart will be used to parse parameters:

Jennifer::Config.read("./spec/fixtures/database.yml", &.["database"]["development"])

Also configuration can be parsed directly from URI:

db_uri = "mysql://root@somehost/some_database?max_pool_size=111&initial_pool_size=222&max_idle_pool_size=333&retry_attempts=444&checkout_timeout=555&retry_delay=666"
Jennifer::Config.from_uri(db)

Take into account - some configs can’t be initialized using URI or yaml file but all of them always can be initialized using Jennifer::Config.configure. Here is the list of such configs:

Config YAML URI
logger
migration_files_path
verbose_migrations
model_files_path
local_time_zone_name
schema
structure_folder
skip_dumping_schema_sql
docker_container
docker_source_location
command_shell_sudo
migration_failure_handler_method
allow_outdated_pending_migration
max_bind_vars_count
time_zone_aware_attributes

Supported configuration options

  • host - database host; default: "localhost"
  • port - database port; default: -1 (-1 value makes adapter to skip port in building connection URL, specify required port number)
  • logger - logger instance; default: Log.for("db", :debug)
  • schema - PostgreSQL database schema name; default: "public"
  • user - database user name used to connect to the database
  • password - database user password used to connect to the database (if not specified - connection URL will specify only user name)
  • db - database name
  • adapter - adapter name to be used to connect to the database (e.g. "postgres")
  • pool_size - count of simultaneously alive database connection; default: 1
  • retry_attempts - count of attempts to connect to the database before raising an exception; default: 1
  • retry_delay - amount of seconds to wait between connection retries; default: 1.0
  • checkout_timeout - amount of seconds to be wait for connection; default: 5.0
  • local_time_zone_name - local time zone name; automatically taken from Time::Location.local.name
  • skip_dumping_schema_sql - skip dumping database structure if set to true; default: false
  • allow_outdated_pending_migration - allows outdated pending migrations (which version is below the latest run migration) to be invoked without exception; default: false
  • command_shell - the name of system command interface to be used for some operations that require system calls; default: "bash"; "docker" value makes commands to be invoked inside of specified docker container
  • docker_container - container name with database instance (is used when command_shell set to "docker"); default: ""
  • docker_source_location - default source location prefix for the executables inside of docker container (is used when command_shell set to "docker"); default: ""
  • command_shell_sudo - marks whether system commands should be invoked with sudo; default: false
  • migration_failure_handler_method - strategy used on migration file failure; default: "none"; supported:
    • "none" - do nothing
    • "reverse_direction" - invokes an opposite method to migration direction (#down for an up-migration)
    • "callback" - invokes #after_up_failure on a failed up-migration and #after_down_failure on a failed down-migration
  • migration_files_path - path to the location with migration files; default: "./db/migrations"
  • verbose_migrations - outputs basic invoked migration information if set to true; default: true
  • model_files_path - path to the models locations; is used by model and migration generators; default: "./src/models"
  • structure_folder - path to the database structure file location; if set to empty string - parent folder of migration_files_path is used; default: ""
  • max_bind_vars_count - maximum allowed count of bind variables; if nothing specified - used adapter’s default value; default: nil
  • time_zone_aware_attributes - whether Jennifer should convert time objects to UTC and back to application time zone when store/load them from a database; default: true

Logging

Jennifer uses standard Crystal logging mechanism so you could specify your own logger:

# This is default logger configuration
Jennifer::Config.configure do |conf|
  conf.logger = Log.for("db", :debug)
end

As a default formatter Jennifer::Adapter::DBFormatter could be used:

Log.setup "db", :debug, Log::IOBackend.new(formatter: Jennifer::Adapter::DBFormatter)

More about logging could be found in the crystal doc.

Command Shell

Some database related operations need to be performed by invoking bash command (like creating or dropping database). By default bash shell is used for such purposes under user invoking this operation, but this may be specified.

To specify another command shell set command_shell configuration to another registered one. One more onboard command shell is "docker" but you mau also define your own. To do this you should inherit from Jennifer::Adapter::ICommandShell abstract class and register it:

class MySimpleDocker < Jennifer::Adapter::ICommandShell
  def execute(command)
    command_string = String.build do |io|
      io << "sudo " if config.command_shell_sudo
      io << "docker exec -i "
      io << config.docker_container
      io << " "
      io << command.executable
      io << " "
      io << OPTIONS_PLACEHOLDER
    end
    invoke(command_string, command.options)
  end
end

Jennifer::Adapter::DBCommandInterface.register_shell("my_docker", MySimpleDocker)