You can define your own settings for your app through a Settings class, defined in config/settings.rb.

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. # Define your app settings here, for example:
  5. #
  6. # setting :my_flag, default: false, constructor: Types::Params::Bool
  7. end
  8. end

Using this class, you can specify what settings exist within your application, what types and defaults they have, and whether or not they are required for your application to boot.

These “app settings” are unrelated to “app configs”, which configure framework behaviours. App settings are your own to define and use.

Each app setting is read from an environment variable matching its name. For example, the Redis URL and Sentry DSN settings below are sourced from the REDIS_URL and SENTRY_DSN environment variables respectively.

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. setting :redis_url, constructor: Types::String
  5. setting :sentry_dsn, constructor: Types::String
  6. end
  7. end

Types

Environment variables are strings, but it’s convenient for settings like analytics_enabled to be a boolean value, or max_cart_items to be an integer.

You can coerce settings to these types by specifying the relevant constructor. For example, Types::Params::Bool and Types::Params::Integer will coerce values to boolean and integer values respectively:

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. setting :analytics_enabled, constructor: Types::Params::Bool
  5. setting :max_cart_items, constructor: Types::Params::Integer
  6. end
  7. end

We can see this in action in the Hanami console:

  1. ANALYTICS_ENABLED=true MAX_CART_ITEMS=100 bundle exec hanami console
  2. bookshelf[development]> Hanami.app["settings"].analytics_enabled
  3. => true
  4. bookshelf[development]> Hanami.app["settings"].max_cart_items
  5. => 100

Common types that are useful in settings constructors include:

  1. Types::String
  2. Types::Params::Bool
  3. Types::Params::Integer
  4. Types::Params::Float
  5. Types::Params::Date
  6. Types::Params::Time

These types are provided by dry-types, and the Types module is generated for you automatically when you subclass Hanami::Settings.

Required and optional settings

Whether or not each setting is required for your application to boot is determined by its constructor.

If a setting uses a constructor like Types::String or Types::Params::Bool, then Hanami will raise an exception if the setting is missing, rather than let your application boot in a potentially invalid state.

The below settings will result in a Hanami::Settings::InvalidSettingsError when DATABASE_URL and ANALYTICS_ENABLED environment variables are not set:

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. setting :analytics_enabled, constructor: Types::Params::Bool
  5. setting :max_cart_items, constructor: Types::Params::Integer
  6. end
  7. end
  1. bundle exec hanami server
  2. ! Unable to load application: Hanami::Settings::InvalidSettingsError: Could not initialize settings. The following settings were invalid:
  3. analytics_enabled: cannot be coerced to false
  4. max_cart_items: cant convert nil into Integer

The same exception will be raised if a setting can’t be correctly coerced:

  1. ANALYTICS_ENABLED=true MAX_CART_ITEMS="not coerceable to integer" bundle exec hanami server
  2. ! Unable to load application: Hanami::Settings::InvalidSettingsError: Could not initialize settings. The following settings were invalid:
  3. max_cart_items: invalid value for Integer(): "not coerceable to integer"

To make a setting optional, use optional to allow nil values:

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. setting :analytics_enabled, constructor: Types::Params::Bool.optional
  5. setting :max_cart_items, constructor: Types::Params::Integer.optional
  6. end
  7. end

Default values

Settings can also specify defaults to be used in the absence of the relevant environment variable.

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. setting :redis_url, default: "redis://localhost:6379", constructor: Types::String
  5. setting :analytics_enabled, default: false, constructor: Types::Params::Bool
  6. setting :max_cart_items, default: 100, constructor: Types::Params::Integer
  7. end
  8. end

Constraints

To enforce additional contraints on settings, you can use a constraint in your constructor type.

Here, the value of the session_secret must be at least 32 characters, while the value of the from_email setting must satisfy the EMAIL_FORMAT regular expression:

  1. # config/settings.rb
  2. module Bookshelf
  3. class Settings < Hanami::Settings
  4. EMAIL_FORMAT = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
  5. setting :session_secret, constructor: Types::String.constrained(min_size: 32)
  6. setting :from_email, constructor: Types::String.constrained(format: EMAIL_FORMAT)
  7. end
  8. end

Using settings within your app

Hanami makes your settings available within your app as a "settings" component.

  1. Hanami.app["settings"]

Accessing settings within components

To access settings within one of your components, use the Deps mixin:

  1. # app/analytics/send_event.rb
  2. module Bookshelf
  3. module Analytics
  4. class SendEvent
  5. include Deps["settings"]
  6. def call(event_type, payload)
  7. return unless settings.analytics_enabled
  8. # ...send event to analytics service here ...
  9. end
  10. end
  11. end
  12. end

For more information on components and the Deps mixin, see the architecture guide.

Accessing settings within providers

When registering a provider, you can access the app’s settings via the target, which returns the app’s container:

  1. # config/providers/redis.rb
  2. Hanami.app.register_provider :redis do
  3. start do
  4. require "redis"
  5. redis = Redis.new(url: target["settings"].redis_url)
  6. register "redis", redis
  7. end
  8. end

See the guides for providers and containers for more information.

Accessing settings within your app class

In some cases, you may want to use a setting to inform an application configuration inside your App class. Within the App class, settings are exposed at the class level for you, via a settings method:

  1. # config/app.rb
  2. module Bookshelf
  3. class App < Hanami::App
  4. config.actions.sessions = :cookie, {
  5. key: "bookshelf.session",
  6. secret: settings.session_secret,
  7. expire_after: 60*60*24*365
  8. }
  9. end
  10. end

Because settings can be accessed at this early point in the app’s boot process, it’s important to ensure that the `Settings` class remains self contained, with no dependencies to other code within your app.

Adding custom methods

One benefit of using a concrete Settings class is that you can add methods to the settings class. This allows you to encapsulate settings-related logic and provides you with a way to a design the best interface into your settings.

  1. module Bookshelf
  2. class Settings < Hanami::Settings
  3. setting :analytics_enabled, default: false, constructor: Types::Params::Bool
  4. setting :analytics_api_key, constructor: Types::String.optional
  5. def send_analytics?
  6. analytics_enabled && !analytics_api_key.nil?
  7. end
  8. end
  9. end

Using dotenv to manage environment variables

Hanami uses the dotenv gem to load environment variables from .env files.

This allows you to maintain specific sets of per-environment variables for your app settings. Which set is used depends on the current environment, which is determined by HANAMI_ENV.

.env.development is used if HANAMI_ENV is “development”:

  1. # .env.development
  2. DATABASE_URL=postgres://localhost:5432/bookshelf_development
  3. ANALYTICS_ENABLED=true
  1. HANAMI_ENV=development bundle exec hanami console
  2. bookshelf[development]> Hanami.app["settings"].database_url
  3. => "postgres://localhost:5432/bookshelf_development"
  4. bookshelf[development]> Hanami.app["settings"].analytics_enabled
  5. => true

.env.test is used if HANAMI_ENV is “test”:

  1. # .env.test
  2. DATABASE_URL=postgres://localhost:5432/bookshelf_test
  3. ANALYTICS_ENABLED=false
  1. HANAMI_ENV=test bundle exec hanami console
  2. bookshelf[test]> Hanami.app["settings"].database_url
  3. => "postgres://localhost:5432/bookshelf_test"
  4. bookshelf[test]> Hanami.app["settings"].analytics_enabled
  5. => false

For a given HANAMI_ENV environment, the .env files are looked up in the following order, which adheres to the recommendations made by the dotenv gem:

  • .env.{environment}.local
  • .env.local (unless the environment is test)
  • .env.{environment}
  • .env

This means a variable in .env.development.local will override a variable declared in .env.development.

Exactly which .env files to create and manage is up to you. But we recommend the following as a reasonable set up for development and test environments in shared projects:

FilenameEnvironmentChecked into gitPurpose
.env.development.localdevelopmentnoLocal overrides of development-specific settings.
.env.developmentdevelopmentyesShared development-specific settings.
.env.test.localtestnoLocal overrides of test-specific settings.
.env.testtestyesShared test-specific settings.
.envdevelopment and testyesShared settings applicable in test and development.

We do not recommend using dotenv in production environments.

Hanami will only use the dotenv gem if it is included in your Gemfile. Applications generated using “hanami new” include the gem in development and test by default.