lib/hanami/configuration.rb in hanami-2.0.0.alpha2 vs lib/hanami/configuration.rb in hanami-2.0.0.alpha3

- old
+ new

@@ -1,270 +1,171 @@ # frozen_string_literal: true require "uri" require "concurrent/hash" require "concurrent/array" +require "dry/configurable" require "dry/inflector" require "pathname" -require "zeitwerk" +require_relative "application/settings/dotenv_store" +require_relative "configuration/logger" +require_relative "configuration/middleware" +require_relative "configuration/router" +require_relative "configuration/sessions" + module Hanami # Hanami application configuration # # @since 2.0.0 # # rubocop:disable Metrics/ClassLength class Configuration - require_relative "configuration/middleware" - require_relative "configuration/router" - require_relative "configuration/sessions" + include Dry::Configurable + DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new } + private_constant :DEFAULT_ENVIRONMENTS + attr_reader :actions + attr_reader :middleware + attr_reader :router attr_reader :views - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + attr_reader :environments + private :environments + def initialize(env:) - @settings = Concurrent::Hash.new + @environments = DEFAULT_ENVIRONMENTS.clone + config.env = env - self.autoloader = Zeitwerk::Loader.new - - self.env = env - self.environments = DEFAULT_ENVIRONMENTS.clone - + # Some default setting values must be assigned at initialize-time to ensure they + # have appropriate values for the current application self.root = Dir.pwd - self.slices_dir = DEFAULT_SLICES_DIR - settings[:slices] = {} + self.settings_store = Application::Settings::DotenvStore.new.with_dotenv_loaded - self.settings_path = DEFAULT_SETTINGS_PATH - self.settings_loader_options = {} - - self.base_url = DEFAULT_BASE_URL - - self.logger = DEFAULT_LOGGER.clone - self.rack_logger_filter_params = DEFAULT_RACK_LOGGER_FILTER_PARAMS.clone - self.sessions = DEFAULT_SESSIONS - - self.router = Router.new(base_url) - self.middleware = Middleware.new - - self.inflections = Dry::Inflector.new - + # Config for actions (same for views, below) may not be available if the gem isn't + # loaded; fall back to a null config object if it's missing @actions = begin require_path = "hanami/action/application_configuration" require require_path Hanami::Action::ApplicationConfiguration.new rescue LoadError => e raise e unless e.path == require_path - Object.new + require_relative "configuration/null_configuration" + NullConfiguration.new end + @middleware = Middleware.new + + @router = Router.new(self) + @views = begin require_path = "hanami/view/application_configuration" require require_path Hanami::View::ApplicationConfiguration.new rescue LoadError => e raise e unless e.path == require_path - Object.new + require_relative "configuration/null_configuration" + NullConfiguration.new end + + yield self if block_given? end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength - def finalize - environment_for(env).each do |blk| - instance_eval(&blk) - end + def environment(env_name, &block) + environments[env_name] << block + apply_env_config - # Finalize nested configuration - # - # TODO: would be good to just create empty configurations for actions/views - # instead of plain objects - actions.finalize! if actions.respond_to?(:finalize!) - views.finalize! if views.respond_to?(:finalize!) - self end - def environment(name, &blk) - environment_for(name).push(blk) - end + def finalize! + apply_env_config - def autoloader=(loader) - settings[:autoloader] = loader || nil - end + # Finalize nested configurations + actions.finalize! + views.finalize! + logger.finalize! + router.finalize! - def autoloader - settings.fetch(:autoloader) + super end - def env=(value) - settings[:env] = value - end + setting :env - def env - settings.fetch(:env) + def env=(new_env) + config.env = env + apply_env_config(new_env) end - def root=(root) - settings[:root] = Pathname(root) - end + setting :root, constructor: -> path { Pathname(path) } - def root - settings.fetch(:root) - end + setting :inflector, default: Dry::Inflector.new, cloneable: true - def slices_dir=(dir) - settings[:slices_dir] = dir + def inflections(&block) + self.inflector = Dry::Inflector.new(&block) end - def slices_dir - settings.fetch(:slices_dir) - end + setting :logger, default: Configuration::Logger.new, cloneable: true - def slices_namespace=(namespace) - settings[:slices_namespace] = namespace + def logger=(logger_instance) + @logger_instance = logger_instance end - def slices_namespace - settings.fetch(:slices_namespace) { Object } + def logger_instance + @logger_instance || logger.logger_class.new(**logger.options) end - def slice(slice_name, &block) - settings[:slices][slice_name] = block - end + setting :settings_path, default: File.join("config", "settings") - def slices - settings[:slices] - end + setting :settings_class_name, default: "Settings" - def settings_path=(value) - settings[:settings_path] = value - end + setting :settings_store, default: Application::Settings::DotenvStore - def settings_path - settings.fetch(:settings_path) - end + setting :slices_dir, default: "slices" - def settings_loader=(loader) - settings[:settings_loader] = loader - end + setting :slices_namespace, default: Object - def settings_loader - settings.fetch(:settings_loader) { - require "hanami/application/settings/loader" - settings[:settings_loader] = Application::Settings::Loader - } - end + # TODO: convert into a dedicated object with explicit behaviour around blocks per + # slice, etc. + setting :slices, default: {}, constructor: :dup.to_proc - def settings_loader_options=(options) - settings[:settings_loader_options] = options - end + # TODO: turn this into a richer "source dirs" setting that can support enabling + # of container component loading as an opt in behvior + setting :component_dir_paths, default: %w[actions repositories views] - def settings_loader_options - settings[:settings_loader_options] + def slice(slice_name, &block) + slices[slice_name] = block end - def base_url=(value) - settings[:base_url] = URI.parse(value) - end + setting :base_url, default: "http://0.0.0.0:2300", constructor: -> url { URI(url) } - def base_url - settings.fetch(:base_url) - end - - def logger=(options) - settings[:logger] = options - end - - def logger - settings.fetch(:logger) - end - - def rack_logger_filter_params=(params) - settings[:rack_logger_filter_params] = params - end - - def rack_logger_filter_params - settings[:rack_logger_filter_params] - end - - def router=(value) - settings[:router] = value - end - - def router - settings.fetch(:router) - end - - def sessions=(*args) - settings[:sessions] = Sessions.new(args) - end - - def sessions - settings.fetch(:sessions) - end - - def middleware - settings.fetch(:middleware) - end - - def inflections(&blk) - if blk.nil? - settings.fetch(:inflections) - else - settings[:inflections] = Dry::Inflector.new(&blk) - end - end - - alias inflector inflections - def for_each_middleware(&blk) stack = middleware.stack.dup stack += sessions.middleware if sessions.enabled? stack.each(&blk) end - protected + setting :sessions, default: :null, constructor: -> *args { Sessions.new(*args) } - def environment_for(name) - settings[:environments][name] - end + private - def environments=(values) - settings[:environments] = values + def apply_env_config(env = self.env) + environments[env].each do |block| + instance_eval(&block) + end end - def middleware=(value) - settings[:middleware] = value + def method_missing(name, *args, &block) + if config.respond_to?(name) + config.public_send(name, *args, &block) + else + super + end end - def inflections=(value) - settings[:inflections] = value + def respond_to_missing?(name, _incude_all = false) + config.respond_to?(name) || super end - - private - - DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new } - private_constant :DEFAULT_ENVIRONMENTS - - DEFAULT_SLICES_DIR = "slices" - private_constant :DEFAULT_SLICES_DIR - - DEFAULT_BASE_URL = "http://0.0.0.0:2300" - private_constant :DEFAULT_BASE_URL - - DEFAULT_LOGGER = { level: :debug }.freeze - private_constant :DEFAULT_LOGGER - - DEFAULT_RACK_LOGGER_FILTER_PARAMS = %w[_csrf password password_confirmation].freeze - private_constant :DEFAULT_RACK_LOGGER_FILTER_PARAMS - - DEFAULT_SETTINGS_PATH = File.join("config", "settings") - private_constant :DEFAULT_SETTINGS_PATH - - DEFAULT_SESSIONS = Sessions.null - private_constant :DEFAULT_SESSIONS - - attr_reader :settings end - # rubocop:enable Metrics/ClassLength end