lib/hanami/settings.rb in hanami-2.0.0.beta4 vs lib/hanami/settings.rb in hanami-2.0.0.rc1
- old
+ new
@@ -1,45 +1,97 @@
# frozen_string_literal: true
-require "dry/core"
+require "dry/core/constants"
require "dry/configurable"
+require_relative "errors"
module Hanami
- # App settings
+ # Provides user-defined settings for an Hanami app or slice.
#
- # Users are expected to inherit from this class to define their app settings.
+ # 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 made
+ # 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
#
- # require "hanami/settings"
- # require "my_app/types"
- #
# module MyApp
# class Settings < Hanami::Settings
- # setting :database_url
- # setting :feature_flag, default: false, constructor: Types::Params::Bool
+ # 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](https://dry-rb.org/gems/dry-configurable/), so you
- # can take a look there to see the supported syntax.
+ # Settings are defined with [dry-configurable][dry-c]'s `setting` method. You may likely want to
+ # provide `default:` and `constructor:` options for your settings.
#
- # Users work with an instance of this class made available within the `settings` key in the
- # container. The instance gets its settings populated from a configurable store, which defaults to
- # {Hanami::Settings::EnvStore}.
+ # If you have [dry-types][dry-t] 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.
#
- # A different store can be set through the `settings_store` Hanami configuration option. All it
- # needs to do is implementing a `#fetch` method with the same signature as `Hash#fetch`.
+ # 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 configue an alternative store via {Hanami::Config#settings_store}. Setting stores
+ # must implement a `#fetch` method with the same signature as `Hash#fetch`.
+ #
+ # [dry-c]: https://dry-rb.org/gems/dry-configurable/
+ # [dry-t]: https://dry-rb.org/gems/dry-types/
+ #
# @see Hanami::Settings::DotenvStore
#
# @api public
# @since 2.0.0
class Settings
+ # Error raised when setting values do not meet their type expectations.
+ #
+ # Its message collects all the individual errors that can be raised for each setting.
+ #
+ # @api public
+ # @since 2.0.0
+ class InvalidSettingsError < Hanami::Error
+ # @api private
+ def initialize(errors)
+ super()
+ @errors = errors
+ end
+
+ # Returns the exception's message.
+ #
+ # @return [String]
+ #
+ # @api public
+ # @since 2.0.0
+ def to_s
+ <<~STR.strip
+ Could not initialize settings. The following settings were invalid:
+
+ #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
+ STR
+ end
+ end
+
class << self
+ # Defines a nested `Types` constant in `Settings` subclasses if dry-types is bundled.
+ #
+ # @see https://dry-rb.org/gems/dry-types
+ #
+ # @api private
def inherited(subclass)
super
if Hanami.bundled?("dry-types")
require "dry/types"
@@ -92,29 +144,10 @@
raise e unless e.path == slice_settings_require_path
end
end
end
- # Exception for errors in the definition of settings.
- #
- # Its message collects all the individual errors that can be raised for each setting.
- #
- # @api public
- InvalidSettingsError = Class.new(StandardError) do
- def initialize(errors)
- @errors = errors
- end
-
- def to_s
- <<~STR.strip
- Could not initialize settings. The following settings were invalid:
-
- #{@errors.map { |setting, message| "#{setting}: #{message}" }.join("\n")}
- STR
- end
- end
-
# @api private
Undefined = Dry::Core::Constants::Undefined
# @api private
EMPTY_STORE = Dry::Core::Constants::EMPTY_HASH
@@ -141,16 +174,49 @@
raise InvalidSettingsError, errors if errors.any?
config.finalize!
end
+ # 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
def inspect
- "#<#{self.class.to_s} [#{config._settings.map(&:name).join(", ")}]>"
+ "#<#{self.class} [#{config._settings.map(&:name).join(", ")}]>"
end
+ # rubocop:disable Layout/LineLength
+
+ # 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
def inspect_values
- "#<#{self.class.to_s} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
+ "#<#{self.class} #{config._settings.map { |setting| "#{setting.name}=#{config[setting.name].inspect}" }.join(" ")}>"
end
+
+ # rubocop:enable Layout/LineLength
private
def method_missing(name, *args, &block)
if config.respond_to?(name)