class Hanami::Settings

Provides user-defined settings for an Hanami app or slice.

Define your own settings by inheriting from this class in ‘config/settings.rb` within an app or slice. Your settings will be loaded from matching ENV vars (with upper-cased names) and be registered as a component as part of the Hanami app {Hanami::Slice::ClassMethods#prepare prepare} step.

The settings instance is registered in your app and slice containers as a ‘“settings”` component. You can use the `Deps` mixin to inject this dependency and make settings available to your other components as required.

@example

# config/settings.rb
# frozen_string_literal: true

module MyApp
  class Settings < Hanami::Settings
    Secret = Types::String.constrained(min_size: 20)

    setting :database_url, constructor: Types::String
    setting :session_secret, constructor: Secret
    setting :some_flag, default: false, constructor: Types::Params::Bool
  end
end

Settings are defined with [dry-configurable]‘s `setting` method. You may likely want to provide `default:` and `constructor:` options for your settings.

If you have [dry-types] bundled, then a nested ‘Types` module will be available for type checking your setting values. Pass type objects to the setting `constructor:` options to ensure their values meet your type expectations. You can use dry-types’ default type objects or define your own.

When the settings are initialized, all type errors will be collected and presented together for correction. Settings are loaded early, as part of the Hanami app’s {Hanami::Slice::ClassMethods#prepare prepare} step, to ensure that the app boots only when valid settings are present.

Setting values are loaded from a configurable store, which defaults to {Hanami::Settings::EnvStore}, which fetches the values from equivalent upper-cased keys in ‘ENV`. You can configure an alternative store via {Hanami::Config#settings_store}. Setting stores must implement a `#fetch` method with the same signature as `Hash#fetch`.

[dry-c]: dry-rb.org/gems/dry-configurable/ [dry-t]: dry-rb.org/gems/dry-types/

@see Hanami::Settings::DotenvStore

@api public @since 2.0.0

Constants

EMPTY_STORE

@api private

Undefined

@api private

Public Class Methods

inherited(subclass) click to toggle source

Defines a nested ‘Types` constant in `Settings` subclasses if dry-types is bundled.

@see dry-rb.org/gems/dry-types

@api private

Calls superclass method
# File lib/hanami/settings.rb, line 93
def inherited(subclass)
  super

  if Hanami.bundled?("dry-types")
    require "dry/types"
    subclass.const_set(:Types, Dry.Types())
  end
end
load_for_slice(slice) click to toggle source

Loads the settings for a slice.

Returns nil if no settings class is defined.

@return [Settings, nil]

@api private

# File lib/hanami/settings.rb, line 109
def load_for_slice(slice)
  return unless settings_defined?(slice)

  require_slice_settings(slice) unless slice_settings_class?(slice)

  slice_settings_class(slice).new(slice.config.settings_store)
end
new(store = EMPTY_STORE) click to toggle source

@api private

# File lib/hanami/settings.rb, line 158
def initialize(store = EMPTY_STORE)
  errors = config._settings.map(&:name).each_with_object({}) do |name, errs|
    value = store.fetch(name, Undefined)

    if value.eql?(Undefined)
      # When a key is missing entirely from the store, _read_ its value from the config instead.
      # This ensures its setting constructor runs (with a `nil` argument given) and raises any
      # necessary errors.
      public_send(name)
    else
      public_send("#{name}=", value)
    end
  rescue => e # rubocop:disable Style/RescueStandardError
    errs[name] = e
  end

  raise InvalidSettingsError, errors if errors.any?

  config.finalize!
end

Private Class Methods

require_slice_settings(slice) click to toggle source
# File lib/hanami/settings.rb, line 136
def require_slice_settings(slice)
  require "hanami/settings"

  slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)

  begin
    require slice_settings_require_path
  rescue LoadError => e
    raise e unless e.path == slice_settings_require_path
  end
end
settings_defined?(slice) click to toggle source

Returns true if settings are defined for the slice.

Settings are considered defined if a ‘Settings` class is already defined in the slice namespace, or a `config/settings.rb` exists under the slice root.

# File lib/hanami/settings.rb, line 123
def settings_defined?(slice)
  slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
    slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
end
slice_settings_class(slice) click to toggle source
# File lib/hanami/settings.rb, line 132
def slice_settings_class(slice)
  slice.namespace.const_get(SETTINGS_CLASS_NAME)
end
slice_settings_class?(slice) click to toggle source
# File lib/hanami/settings.rb, line 128
def slice_settings_class?(slice)
  slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
end

Public Instance Methods

inspect() click to toggle source

Returns a string containing a human-readable representation of the settings.

This includes setting names only, not any values, to ensure that sensitive values do not inadvertently leak.

Use {#inspect_values} to inspect settings with their values.

@example

settings.inspect
# => #<MyApp::Settings [database_url, session_secret, some_flag]>

@return [String]

@see inspect_values

@api public @since 2.0.0

# File lib/hanami/settings.rb, line 196
def inspect
  "#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
end
inspect_values() click to toggle source

Returns a string containing a human-readable representation of the settings and their values.

@example

settings.inspect_values
# => #<MyApp::Settings database_url="postgres://localhost/my_db", session_secret="xxx", some_flag=true]>

@return [String]

@see inspect

@api public @since 2.0.0

# File lib/hanami/settings.rb, line 214
def inspect_values
  "#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
end

Private Instance Methods

method_missing(name, *args, &block) click to toggle source

rubocop:enable Layout/LineLength

Calls superclass method
# File lib/hanami/settings.rb, line 222
def method_missing(name, *args, &block)
  if config.respond_to?(name)
    config.send(name, *args, &block)
  else
    super
  end
end
respond_to_missing?(name, _include_all = false) click to toggle source
Calls superclass method
# File lib/hanami/settings.rb, line 230
def respond_to_missing?(name, _include_all = false)
  config.respond_to?(name) || super
end