You can define your own settings for your app through a Settings
class, defined in config/settings.rb
.
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
# Define your app settings here, for example:
#
# setting :my_flag, default: false, constructor: Types::Params::Bool
end
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.
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
setting :redis_url, constructor: Types::String
setting :sentry_dsn, constructor: Types::String
end
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:
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
setting :analytics_enabled, constructor: Types::Params::Bool
setting :max_cart_items, constructor: Types::Params::Integer
end
end
We can see this in action in the Hanami console:
ANALYTICS_ENABLED=true MAX_CART_ITEMS=100 bundle exec hanami console
bookshelf[development]> Hanami.app["settings"].analytics_enabled
=> true
bookshelf[development]> Hanami.app["settings"].max_cart_items
=> 100
Common types that are useful in settings constructors include:
Types::String
Types::Params::Bool
Types::Params::Integer
Types::Params::Float
Types::Params::Date
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:
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
setting :analytics_enabled, constructor: Types::Params::Bool
setting :max_cart_items, constructor: Types::Params::Integer
end
end
bundle exec hanami server
! Unable to load application: Hanami::Settings::InvalidSettingsError: Could not initialize settings. The following settings were invalid:
analytics_enabled: cannot be coerced to false
max_cart_items: can’t convert nil into Integer
The same exception will be raised if a setting can’t be correctly coerced:
ANALYTICS_ENABLED=true MAX_CART_ITEMS="not coerceable to integer" bundle exec hanami server
! Unable to load application: Hanami::Settings::InvalidSettingsError: Could not initialize settings. The following settings were invalid:
max_cart_items: invalid value for Integer(): "not coerceable to integer"
To make a setting optional, use optional
to allow nil
values:
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
setting :analytics_enabled, constructor: Types::Params::Bool.optional
setting :max_cart_items, constructor: Types::Params::Integer.optional
end
end
Default values
Settings can also specify defaults to be used in the absence of the relevant environment variable.
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
setting :redis_url, default: "redis://localhost:6379", constructor: Types::String
setting :analytics_enabled, default: false, constructor: Types::Params::Bool
setting :max_cart_items, default: 100, constructor: Types::Params::Integer
end
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:
# config/settings.rb
module Bookshelf
class Settings < Hanami::Settings
EMAIL_FORMAT = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
setting :session_secret, constructor: Types::String.constrained(min_size: 32)
setting :from_email, constructor: Types::String.constrained(format: EMAIL_FORMAT)
end
end
Using settings within your app
Hanami makes your settings available within your app as a "settings"
component.
Hanami.app["settings"]
Accessing settings within components
To access settings within one of your components, use the Deps mixin:
# app/analytics/send_event.rb
module Bookshelf
module Analytics
class SendEvent
include Deps["settings"]
def call(event_type, payload)
return unless settings.analytics_enabled
# ...send event to analytics service here ...
end
end
end
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:
# config/providers/redis.rb
Hanami.app.register_provider :redis do
start do
require "redis"
redis = Redis.new(url: target["settings"].redis_url)
register "redis", redis
end
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:
# config/app.rb
module Bookshelf
class App < Hanami::App
config.actions.sessions = :cookie, {
key: "bookshelf.session",
secret: settings.session_secret,
expire_after: 60*60*24*365
}
end
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.
module Bookshelf
class Settings < Hanami::Settings
setting :analytics_enabled, default: false, constructor: Types::Params::Bool
setting :analytics_api_key, constructor: Types::String.optional
def send_analytics?
analytics_enabled && !analytics_api_key.nil?
end
end
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”:
# .env.development
DATABASE_URL=postgres://localhost:5432/bookshelf_development
ANALYTICS_ENABLED=true
HANAMI_ENV=development bundle exec hanami console
bookshelf[development]> Hanami.app["settings"].database_url
=> "postgres://localhost:5432/bookshelf_development"
bookshelf[development]> Hanami.app["settings"].analytics_enabled
=> true
.env.test
is used if HANAMI_ENV
is “test”:
# .env.test
DATABASE_URL=postgres://localhost:5432/bookshelf_test
ANALYTICS_ENABLED=false
HANAMI_ENV=test bundle exec hanami console
bookshelf[test]> Hanami.app["settings"].database_url
=> "postgres://localhost:5432/bookshelf_test"
bookshelf[test]> Hanami.app["settings"].analytics_enabled
=> 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:
Filename | Environment | Checked into git | Purpose |
---|---|---|---|
.env.development.local | development | no | Local overrides of development-specific settings. |
.env.development | development | yes | Shared development-specific settings. |
.env.test.local | test | no | Local overrides of test-specific settings. |
.env.test | test | yes | Shared test-specific settings. |
.env | development and test | yes | Shared 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.