Update API docs for 2.0.0 (#1229)
This commit is contained in:
parent
70156569b2
commit
a34ff4426c
|
@ -16,6 +16,8 @@ Style/LambdaCall:
|
|||
Enabled: false
|
||||
Style/StabbyLambdaParentheses:
|
||||
Enabled: false
|
||||
Style/StringLiteralsInInterpolation:
|
||||
Enabled: false
|
||||
Style/TrailingCommaInArguments:
|
||||
Enabled: false
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
--markup=markdown
|
||||
|
||||
--plugin junk
|
||||
--junk-log-ignore UnknownError
|
1
Gemfile
1
Gemfile
|
@ -6,6 +6,7 @@ gemspec
|
|||
unless ENV["CI"]
|
||||
gem "byebug", platforms: :mri
|
||||
gem "yard"
|
||||
gem "yard-junk"
|
||||
end
|
||||
|
||||
gem "hanami-utils", github: "hanami/utils", branch: "main"
|
||||
|
|
148
lib/hanami.rb
148
lib/hanami.rb
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "hanami/constants"
|
||||
|
||||
# A complete web framework for Ruby
|
||||
#
|
||||
# @since 0.1.0
|
||||
|
@ -13,7 +15,7 @@ module Hanami
|
|||
#
|
||||
# Raises an exception if the app file cannot be found.
|
||||
#
|
||||
# @return [Hanami::App] the loaded app class
|
||||
# @return [app] the loaded app class
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
|
@ -34,6 +36,52 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the Hamami app class.
|
||||
#
|
||||
# To ensure your Hanami app is loaded, run {.setup} (or `require "hanami/setup"`) first.
|
||||
#
|
||||
# @return [Hanami::App] the app class
|
||||
#
|
||||
# @raise [AppLoadError] if the app has not been loaded
|
||||
#
|
||||
# @see .setup
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.app
|
||||
@_mutex.synchronize do
|
||||
unless defined?(@_app)
|
||||
raise AppLoadError,
|
||||
"Hanami.app is not yet configured. " \
|
||||
"You may need to `require \"hanami/setup\"` to load your config/app.rb file."
|
||||
end
|
||||
|
||||
@_app
|
||||
end
|
||||
end
|
||||
|
||||
# Returns true if the Hanami app class has been loaded.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.app?
|
||||
instance_variable_defined?(:@_app)
|
||||
end
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def self.app=(klass)
|
||||
@_mutex.synchronize do
|
||||
if instance_variable_defined?(:@_app)
|
||||
raise AppLoadError, "Hanami.app is already configured."
|
||||
end
|
||||
|
||||
@_app = klass unless klass.name.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
|
||||
#
|
||||
# Searches within the given directory, then searches upwards through parent directories until the
|
||||
|
@ -57,59 +105,94 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
APP_PATH = "config/app.rb"
|
||||
private_constant :APP_PATH
|
||||
|
||||
def self.app
|
||||
@_mutex.synchronize do
|
||||
unless defined?(@_app)
|
||||
raise AppLoadError,
|
||||
"Hanami.app is not yet configured. " \
|
||||
"You may need to `require \"hanami/setup\"` to load your config/app.rb file."
|
||||
end
|
||||
|
||||
@_app
|
||||
end
|
||||
end
|
||||
|
||||
def self.app?
|
||||
instance_variable_defined?(:@_app)
|
||||
end
|
||||
|
||||
def self.app=(klass)
|
||||
@_mutex.synchronize do
|
||||
if instance_variable_defined?(:@_app)
|
||||
raise AppLoadError, "Hanami.app is already configured."
|
||||
end
|
||||
|
||||
@_app = klass unless klass.name.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the Hanami app environment as loaded from the `HANAMI_ENV` environment variable.
|
||||
#
|
||||
# @example
|
||||
# Hanami.env # => :development
|
||||
#
|
||||
# @return [Symbol] the environment name
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.env
|
||||
ENV.fetch("HANAMI_ENV") { ENV.fetch("RACK_ENV", "development") }.to_sym
|
||||
end
|
||||
|
||||
# Returns true if {.env} matches any of the given names
|
||||
#
|
||||
# @example
|
||||
# Hanami.env # => :development
|
||||
# Hanami.env?(:development, :test) # => true
|
||||
#
|
||||
# @param names [Array<Symbol>] the environment names to check
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.env?(*names)
|
||||
names.map(&:to_sym).include?(env)
|
||||
end
|
||||
|
||||
# Returns the app's logger.
|
||||
#
|
||||
# Direct global access to the logger via this method is not recommended. Instead, consider
|
||||
# accessing the logger via the app or slice container, in most cases as an dependency using the
|
||||
# `Deps` mixin.
|
||||
#
|
||||
# @example
|
||||
# # app/my_component.rb
|
||||
#
|
||||
# module MyApp
|
||||
# class MyComponent
|
||||
# include Deps["logger"]
|
||||
#
|
||||
# def some_method
|
||||
# logger.info("hello")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @return [Hanami::Logger]
|
||||
#
|
||||
# @api public
|
||||
# @since 1.0.0
|
||||
def self.logger
|
||||
app[:logger]
|
||||
end
|
||||
|
||||
# Prepares the Hanami app.
|
||||
#
|
||||
# @see App::ClassMethods#prepare
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.prepare
|
||||
app.prepare
|
||||
end
|
||||
|
||||
# Boots the Hanami app.
|
||||
#
|
||||
# @see App::ClassMethods#boot
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.boot
|
||||
app.boot
|
||||
end
|
||||
|
||||
# Shuts down the Hanami app.
|
||||
#
|
||||
# @see App::ClassMethods#shutdown
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def self.shutdown
|
||||
app.shutdown
|
||||
end
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def self.bundled?(gem_name)
|
||||
@_mutex.synchronize do
|
||||
@_bundled[gem_name] ||= begin
|
||||
|
@ -120,6 +203,11 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Returns an array of bundler group names to be eagerly loaded by hanami-cli and other CLI
|
||||
# extensions.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def self.bundler_groups
|
||||
[:plugins]
|
||||
end
|
||||
|
|
|
@ -6,12 +6,11 @@ require_relative "slice"
|
|||
require_relative "slice_name"
|
||||
|
||||
module Hanami
|
||||
# The Hanami app is a singular slice tasked with managing the core components of
|
||||
# the app and coordinating overall app boot.
|
||||
# The Hanami app is a singular slice tasked with managing the core components of the app and
|
||||
# coordinating overall app boot.
|
||||
#
|
||||
# For smaller apps, the app may be the only slice present, whereas larger apps
|
||||
# may consist of many slices, with the app reserved for holding a small number
|
||||
# of shared components only.
|
||||
# For smaller apps, the app may be the only slice present, whereas larger apps may consist of many
|
||||
# slices, with the app reserved for holding a small number of shared components only.
|
||||
#
|
||||
# @see Slice
|
||||
#
|
||||
|
@ -20,6 +19,8 @@ module Hanami
|
|||
class App < Slice
|
||||
@_mutex = Mutex.new
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
|
||||
|
@ -31,9 +32,9 @@ module Hanami
|
|||
subclass.class_eval do
|
||||
@config = Hanami::Config.new(app_name: slice_name, env: Hanami.env)
|
||||
|
||||
# Prepare the load path (based on the default root of `Dir.pwd`) as early as
|
||||
# possible, so you can make a `require` inside the body of an `App` subclass,
|
||||
# which may be useful for certain kinds of app configuration.
|
||||
# Prepare the load path (based on the default root of `Dir.pwd`) as early as possible, so
|
||||
# you can make a `require` inside the body of an `App` subclass, which may be useful for
|
||||
# certain kinds of app configuration.
|
||||
prepare_load_path
|
||||
|
||||
load_dotenv
|
||||
|
@ -43,29 +44,41 @@ module Hanami
|
|||
|
||||
# App class interface
|
||||
module ClassMethods
|
||||
# Returns the app's config.
|
||||
#
|
||||
# @return [Hanami::Config]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :config
|
||||
|
||||
# Returns the app's {SliceName}.
|
||||
#
|
||||
# @return [Hanami::SliceName]
|
||||
#
|
||||
# @see Slice::ClassMethods#slice_name
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def app_name
|
||||
slice_name
|
||||
end
|
||||
|
||||
# Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/`
|
||||
# directory if it exists. If the lib directory is already added, this will do
|
||||
# nothing.
|
||||
# Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/` directory
|
||||
# if it exists. If the lib directory is already added, this will do nothing.
|
||||
#
|
||||
# In ordinary circumstances, you should never have to call this method: this method
|
||||
# is called immediately upon subclassing {Hanami::App}, as a convenicence to put
|
||||
# lib/ (under the default root of `Dir.pwd`) on the load path automatically. This is
|
||||
# helpful if you need to require files inside the subclass body for performing
|
||||
# certain app configuration steps.
|
||||
# In ordinary circumstances, you should never have to call this method: this method is called
|
||||
# immediately upon subclassing {Hanami::App}, as a convenicence to put lib/ (under the default
|
||||
# root of `Dir.pwd`) on the load path automatically. This is helpful if you need to require
|
||||
# files inside the subclass body for performing certain app configuration steps.
|
||||
#
|
||||
# If you change your app's `config.root` and you need to require files from its
|
||||
# `lib/` directory within your {App} subclass body, you should call
|
||||
# {.prepare_load_path} explicitly after setting the new root.
|
||||
# If you change your app's `config.root` and you need to require files from its `lib/`
|
||||
# directory within your {App} subclass body, you should call {.prepare_load_path} explicitly
|
||||
# after setting the new root.
|
||||
#
|
||||
# Otherwise, this method is called again as part of the app {.prepare} step, so if
|
||||
# you've changed your app's root and do _not_ need to require files within your {App}
|
||||
# subclass body, then you don't need to call this method.
|
||||
# Otherwise, this method is called again as part of the app {.prepare} step, so if you've
|
||||
# changed your app's root and do _not_ need to require files within your {App} subclass body,
|
||||
# then you don't need to call this method.
|
||||
#
|
||||
# @example
|
||||
# module MyApp
|
||||
|
@ -124,12 +137,12 @@ module Hanami
|
|||
# Make app-wide notifications available as early as possible
|
||||
container.use(:notifications)
|
||||
|
||||
# Ensure all basic slice preparation is complete before we make adjustments below
|
||||
# (which rely on the basic prepare steps having already run)
|
||||
# Ensure all basic slice preparation is complete before we make adjustments below (which
|
||||
# rely on the basic prepare steps having already run)
|
||||
super
|
||||
|
||||
# Run specific prepare steps for the app slice. Note also that some
|
||||
# standard steps have been skipped via the empty method overrides below.
|
||||
# Run specific prepare steps for the app slice. Note also that some standard steps have been
|
||||
# skipped via the empty method overrides below.
|
||||
prepare_app_component_dirs
|
||||
prepare_app_providers
|
||||
end
|
||||
|
@ -150,8 +163,8 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# When auto-registering components in app/, ignore files in `app/lib/` (these will
|
||||
# be auto-registered as above), as well as the configured no_auto_register_paths
|
||||
# When auto-registering components in app/, ignore files in `app/lib/` (these will be
|
||||
# auto-registered as above), as well as the configured no_auto_register_paths
|
||||
no_auto_register_paths = ([LIB_DIR] + config.no_auto_register_paths)
|
||||
.map { |path|
|
||||
path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
|
||||
|
@ -183,9 +196,8 @@ module Hanami
|
|||
end
|
||||
|
||||
def prepare_autoloader
|
||||
# Component dirs are automatically pushed to the autoloader by dry-system's
|
||||
# zeitwerk plugin. This method adds other dirs that are not otherwise configured
|
||||
# as component dirs.
|
||||
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
||||
# This method adds other dirs that are not otherwise configured as component dirs.
|
||||
|
||||
# Autoload classes from `lib/[app_namespace]/`
|
||||
if root.join(LIB_DIR, app_name.name).directory?
|
||||
|
|
|
@ -4,21 +4,21 @@ require "dry/configurable"
|
|||
require_relative "config"
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Assets
|
||||
# @since 2.0.0
|
||||
# @api public
|
||||
# App config for assets.
|
||||
#
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
class AppConfig
|
||||
include Dry::Configurable
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
attr_reader :base_config
|
||||
protected :base_config
|
||||
|
||||
setting :server_url, default: "http://localhost:8080"
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
|
@ -30,8 +30,6 @@ module Hanami
|
|||
@base_config = source.base_config.dup
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def finalize!
|
||||
end
|
||||
|
||||
|
@ -39,7 +37,6 @@ module Hanami
|
|||
#
|
||||
# @return [Set]
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def settings
|
||||
base_config.settings + self.class.settings
|
||||
|
@ -47,8 +44,6 @@ module Hanami
|
|||
|
||||
private
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def method_missing(name, *args, &block)
|
||||
if config.respond_to?(name)
|
||||
config.public_send(name, *args, &block)
|
||||
|
@ -59,8 +54,6 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def respond_to_missing?(name, _incude_all = false)
|
||||
config.respond_to?(name) || base_config.respond_to?(name) || super
|
||||
end
|
||||
|
|
|
@ -4,8 +4,11 @@ require "dry/configurable"
|
|||
|
||||
module Hanami
|
||||
module Assets
|
||||
# @since 2.0.0
|
||||
# @api public
|
||||
# App config for assets.
|
||||
#
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
class Config
|
||||
include Dry::Configurable
|
||||
|
||||
|
@ -34,8 +37,6 @@ module Hanami
|
|||
|
||||
private
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def method_missing(name, *args, &block)
|
||||
if config.respond_to?(name)
|
||||
config.public_send(name, *args, &block)
|
||||
|
@ -44,8 +45,6 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def respond_to_missing?(name, _incude_all = false)
|
||||
config.respond_to?(name) || super
|
||||
end
|
||||
|
|
|
@ -14,20 +14,94 @@ require_relative "settings/env_store"
|
|||
require_relative "slice/routing/middleware/stack"
|
||||
|
||||
module Hanami
|
||||
# Hanami app configuration
|
||||
# Hanami app config
|
||||
#
|
||||
# @since 2.0.0
|
||||
class Config
|
||||
# @api private
|
||||
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
||||
private_constant :DEFAULT_ENVIRONMENTS
|
||||
|
||||
include Dry::Configurable
|
||||
|
||||
# @!attribute [rw] root
|
||||
# Sets the root for the app or slice.
|
||||
#
|
||||
# For the app, this defaults to `Dir.pwd`. For slices detected in `slices/` `config/slices/`,
|
||||
# this defaults to `slices/[slice_name]/`.
|
||||
#
|
||||
# Accepts a string path and will return a `Pathname`.
|
||||
#
|
||||
# @return [Pathname]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :root, constructor: ->(path) { Pathname(path) if path }
|
||||
|
||||
setting :no_auto_register_paths, default: %w[entities]
|
||||
|
||||
# @!attribute [rw] inflector
|
||||
# Sets the app's inflector.
|
||||
#
|
||||
# This expects a `Dry::Inflector` (or compatible) inflector instance.
|
||||
#
|
||||
# To configure custom inflection rules without having to assign a whole inflector, see
|
||||
# {#inflections}.
|
||||
#
|
||||
# @return [Dry::Inflector]
|
||||
#
|
||||
# @see #inflections
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :inflector, default: Dry::Inflector.new
|
||||
|
||||
# @!attribute [rw] settings_store
|
||||
# Sets the store used to retrieve {Hanami::Settings} values.
|
||||
#
|
||||
# Defaults to an instance of {Hanami::Settings::EnvStore}.
|
||||
#
|
||||
# @return [#fetch]
|
||||
#
|
||||
# @see Hanami::Settings
|
||||
# @see Hanami::Settings::EnvStore#fetch
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :settings_store, default: Hanami::Settings::EnvStore.new
|
||||
|
||||
# @!attribute [rw] slices
|
||||
# Sets the slices to load when the app is preared or booted.
|
||||
#
|
||||
# Defaults to `nil`, which will load all slices. Set this to an array of slice names to load
|
||||
# only those slices.
|
||||
#
|
||||
# This attribute is also populated from the `HANAMI_SLICES` environment variable.
|
||||
#
|
||||
# @example
|
||||
# config.slices = ["admin", "search"]
|
||||
#
|
||||
# @example
|
||||
# ENV["HANAMI_SLICES"] # => "admin,search"
|
||||
# config.slices # => ["admin", "search"]
|
||||
#
|
||||
# @return [Array<String>, nil]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :slices
|
||||
|
||||
# @!attribute [rw] shared_app_component_keys
|
||||
# Sets the keys for the components to be imported from the app into all other slices.
|
||||
#
|
||||
# You should append items to this array, since the default shared components are essential for
|
||||
# slices to operate within the app.
|
||||
#
|
||||
# @example
|
||||
# config.shared_app_component_keys += ["shared_component_a", "shared_component_b"]
|
||||
#
|
||||
# @return [Array<String>]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :shared_app_component_keys, default: %w[
|
||||
inflector
|
||||
logger
|
||||
|
@ -37,56 +111,119 @@ module Hanami
|
|||
settings
|
||||
]
|
||||
|
||||
setting :slices
|
||||
|
||||
setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }
|
||||
# @!attribute [rw] no_auto_register_paths
|
||||
# Sets the paths to skip from container auto-registration.
|
||||
#
|
||||
# Defaults to `["entities"]`.
|
||||
#
|
||||
# @return [Array<String>] array of relative paths
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :no_auto_register_paths, default: %w[entities]
|
||||
|
||||
# TODO: Remove this; we have `config.actions.sessions` instead
|
||||
#
|
||||
# @api private
|
||||
setting :sessions, default: :null, constructor: ->(*args) { Sessions.new(*args) }
|
||||
|
||||
setting :logger, cloneable: true
|
||||
|
||||
DEFAULT_ENVIRONMENTS = Concurrent::Hash.new { |h, k| h[k] = Concurrent::Array.new }
|
||||
private_constant :DEFAULT_ENVIRONMENTS
|
||||
|
||||
# @return [Symbol] The name of the application
|
||||
# @!attribute [rw] base_url
|
||||
# Sets the base URL for app's web server.
|
||||
#
|
||||
# @api public
|
||||
# This is passed to the {Slice::ClassMethods#router router} and used for generating links.
|
||||
#
|
||||
# Defaults to `"http://0.0.0.0:2300"`. String values passed are turned into `URI` instances.
|
||||
#
|
||||
# @return [URI]
|
||||
#
|
||||
# @see Slice::ClassMethods#router
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }
|
||||
|
||||
# Returns the app or slice's {Hanami::SliceName slice_name}.
|
||||
#
|
||||
# This is useful for default config values that depend on this name.
|
||||
#
|
||||
# @return [Hanami::SliceName]
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
attr_reader :app_name
|
||||
|
||||
# @return [String] The current environment
|
||||
# Returns the app's environment.
|
||||
#
|
||||
# @api public
|
||||
# @example
|
||||
# config.env # => :development
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
attr_reader :env
|
||||
|
||||
# @return [Hanami::Config::Actions]
|
||||
# Returns the app's actions config, or a null config if hanami-controller is not bundled.
|
||||
#
|
||||
# @example When hanami-controller is bundled
|
||||
# config.actions.default_request_format # => :html
|
||||
#
|
||||
# @example When hanami-controller is not bundled
|
||||
# config.actions.default_request_format # => NoMethodError
|
||||
#
|
||||
# @return [Hanami::Config::Actions, Hanami::Config::NullConfig]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :actions
|
||||
|
||||
# @return [Hanami::Slice::Routing::Middleware::Stack]
|
||||
# Returns the app's middleware stack, or nil if hanami-router is not bundled.
|
||||
#
|
||||
# Use this to configure middleware that should apply to all routes.
|
||||
#
|
||||
# @example
|
||||
# config.middleware.use :body_parser, :json
|
||||
# config.middleware.use MyCustomMiddleware
|
||||
#
|
||||
# @return [Hanami::Slice::Routing::Middleware::Stack, nil]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :middleware
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
alias_method :middleware_stack, :middleware
|
||||
|
||||
# @return [Hanami::Config::Router]
|
||||
# Returns the app's router config, or a null config if hanami-router is not bundled.
|
||||
#
|
||||
# @example When hanami-router is bundled
|
||||
# config.router.resolver # => Hanami::Slice::Routing::Resolver
|
||||
#
|
||||
# @example When hanami-router is not bundled
|
||||
# config.router.resolver # => NoMethodError
|
||||
#
|
||||
# @return [Hanami::Config::Router, Hanami::Config::NullConfig]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :router
|
||||
|
||||
# @return [Hanami::Config::Views]
|
||||
# Returns the app's views config, or a null config if hanami-view is not bundled.
|
||||
#
|
||||
# @api public
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
attr_reader :views
|
||||
|
||||
# @return [Hanami::Assets::AppConfiguration]
|
||||
# Returns the app's assets config.
|
||||
#
|
||||
# @api public
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
attr_reader :assets
|
||||
|
||||
# @return [Concurrent::Hash] A hash of default environments
|
||||
# @return [Concurrent::Hash] a hash of default environments
|
||||
#
|
||||
# @api private
|
||||
attr_reader :environments
|
||||
|
@ -103,7 +240,7 @@ module Hanami
|
|||
self.root = Dir.pwd
|
||||
load_from_env
|
||||
|
||||
config.logger = Config::Logger.new(env: env, app_name: app_name)
|
||||
@logger = Config::Logger.new(env: env, app_name: app_name)
|
||||
|
||||
# TODO: Make assets config dependent
|
||||
require "hanami/assets/app_config"
|
||||
|
@ -128,31 +265,6 @@ module Hanami
|
|||
yield self if block_given?
|
||||
end
|
||||
|
||||
# Apply config for the given environment
|
||||
#
|
||||
# @param env [String] the environment name
|
||||
#
|
||||
# @return [Hanami::Config]
|
||||
#
|
||||
# @api public
|
||||
def environment(env_name, &block)
|
||||
environments[env_name] << block
|
||||
apply_env_config
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Configure application's inflections
|
||||
#
|
||||
# @see https://dry-rb.org/gems/dry-inflector
|
||||
#
|
||||
# @return [Dry::Inflector]
|
||||
#
|
||||
# @api public
|
||||
def inflections(&block)
|
||||
self.inflector = Dry::Inflector.new(&block)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def initialize_copy(source)
|
||||
super
|
||||
|
@ -168,7 +280,13 @@ module Hanami
|
|||
end
|
||||
@views = source.views.dup
|
||||
end
|
||||
private :initialize_copy
|
||||
|
||||
# Finalizes the config.
|
||||
#
|
||||
# This is called when the app or slice is prepared. After this, no further changes to config can
|
||||
# be made.
|
||||
#
|
||||
# @api private
|
||||
def finalize!
|
||||
apply_env_config
|
||||
|
@ -183,16 +301,114 @@ module Hanami
|
|||
super
|
||||
end
|
||||
|
||||
# Set a default global logger instance
|
||||
# Applies config for a given app environment.
|
||||
#
|
||||
# The given block will be evaluated in the context of `self` via `instance_eval`.
|
||||
#
|
||||
# @example
|
||||
# config.environment(:test) do
|
||||
# config.logger.level = :info
|
||||
# end
|
||||
#
|
||||
# @param env_name [Symbol] the environment name
|
||||
#
|
||||
# @return [Hanami::Config]
|
||||
#
|
||||
# @see Hanami.env
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def environment(env_name, &block)
|
||||
environments[env_name] << block
|
||||
apply_env_config
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Configures the app's custom inflections.
|
||||
#
|
||||
# You should call this one time only. Subsequent calls will override previously configured
|
||||
# inflections.
|
||||
#
|
||||
# @example
|
||||
# config.inflections do |inflections|
|
||||
# inflections.acronym "WNBA"
|
||||
# end
|
||||
#
|
||||
# @see https://dry-rb.org/gems/dry-inflector
|
||||
#
|
||||
# @return [Dry::Inflector] the configured inflector
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def inflections(&block)
|
||||
self.inflector = Dry::Inflector.new(&block)
|
||||
end
|
||||
|
||||
# Disabling this to permit distinct documentation for `#logger` vs `#logger=`
|
||||
#
|
||||
# rubocop:disable Style/TrivialAccessors
|
||||
|
||||
# Returns the logger config.
|
||||
#
|
||||
# Use this to configure various options for the default `Hanami::Logger` logger instance.
|
||||
#
|
||||
# @example
|
||||
# config.logger.level = :debug
|
||||
#
|
||||
# @return [Hanami::Config::Logger]
|
||||
#
|
||||
# @see Hanami::Config::Logger
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def logger
|
||||
@logger
|
||||
end
|
||||
|
||||
# Sets the app's logger instance.
|
||||
#
|
||||
# This entirely replaces the default `Hanami::Logger` instance that would have been
|
||||
#
|
||||
# @see #logger_instance
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def logger=(logger_instance)
|
||||
@logger_instance = logger_instance
|
||||
end
|
||||
|
||||
# Return configured logger instance
|
||||
# rubocop:enable Style/TrivialAccessors
|
||||
|
||||
# Returns the configured logger instance.
|
||||
#
|
||||
# Unless you've replaced the logger with {#logger=}, this returns an `Hanami::Logger` configured
|
||||
# with the options configured through {#logger}.
|
||||
#
|
||||
# This configured logger is registered in all app and slice containers as `"logger"`. For
|
||||
# typical usage, you should access the logger via this component, not directly from config.
|
||||
#
|
||||
# @example Accessing the logger component
|
||||
# Hanami.app["logger"] # => #<Hanami::Logger>
|
||||
#
|
||||
# @example Injecting the logger as a dependency
|
||||
# module MyApp
|
||||
# class MyClass
|
||||
# include Deps["logger"]
|
||||
#
|
||||
# def my_method
|
||||
# logger.info("hello")
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @return [Hanami::Logger]
|
||||
#
|
||||
# @see #logger
|
||||
# @see Hanami::Config::Logger
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def logger_instance
|
||||
@logger_instance || logger.instance
|
||||
end
|
||||
|
|
|
@ -10,24 +10,99 @@ module Hanami
|
|||
class Config
|
||||
# Hanami actions config
|
||||
#
|
||||
# This exposes all the settings from the standalone `Hanami::Action` class, pre-configured with
|
||||
# sensible defaults for actions within a full Hanami app. It also provides additional settings
|
||||
# for further integration of actions with other full stack app components.
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @api public
|
||||
class Actions
|
||||
include Dry::Configurable
|
||||
|
||||
# @!attribute [rw] cookies
|
||||
# Sets or returns a hash of cookie options for actions.
|
||||
#
|
||||
# The hash is wrapped by {Hanami::Config::Actions::Cookies}, which also provides an
|
||||
# `enabled?` method, returning true in the case of any options provided.
|
||||
#
|
||||
# @example
|
||||
# config.actions.cookies = {max_age: 300}
|
||||
#
|
||||
# @return [Hanami::Config::Actions::Cookies]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :cookies, default: {}, constructor: -> options { Cookies.new(options) }
|
||||
|
||||
# @!attribute [rw] sessions
|
||||
# Sets or returns the session store (and its options) for actions.
|
||||
#
|
||||
# The given values are taken as an argument list to be passed to {Config::Sessions#initialize}.
|
||||
#
|
||||
# The configured session store is used when setting up the app or slice
|
||||
# {Slice::ClassMethods#router router}.
|
||||
#
|
||||
# @example
|
||||
# config.sessions = :cookie, {secret: "xyz"}
|
||||
#
|
||||
# @return [Config::Sessions]
|
||||
#
|
||||
# @see Config::Sessions
|
||||
# @see Slice::ClassMethods#router
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :sessions, constructor: proc { |storage, *options| Sessions.new(storage, *options) }
|
||||
|
||||
# @!attribute [rw] csrf_protection
|
||||
# Sets or returns whether CSRF protection should be enabled for action classes.
|
||||
#
|
||||
# Defaults to true if {#sessions} is enabled. You can override this by explicitly setting a
|
||||
# true or false value.
|
||||
#
|
||||
# When true, this will include `Hanami::Action::CSRFProtection` in all action classes.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :csrf_protection
|
||||
|
||||
setting :name_inference_base, default: "actions"
|
||||
setting :view_context_identifier, default: "views.context"
|
||||
setting :view_name_inferrer, default: Slice::ViewNameInferrer
|
||||
setting :view_name_inference_base, default: "views"
|
||||
|
||||
# Returns the Content Security Policy config for actions.
|
||||
#
|
||||
# The resulting policy is set as a default `"Content-Security-Policy"` response header.
|
||||
#
|
||||
# @return [Hanami::Config::Actions::ContentSecurityPolicy]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_accessor :content_security_policy
|
||||
|
||||
# The following settings are for view and assets integration with actions, and are NOT
|
||||
# publicly released as of 2.0.0. We'll make full documentation available when these become
|
||||
# public in a subsequent release.
|
||||
|
||||
# @!attribute [rw] name_inference_base
|
||||
# @api private
|
||||
setting :name_inference_base, default: "actions"
|
||||
|
||||
# @!attribute [rw] view_context_identifier
|
||||
# @api private
|
||||
setting :view_context_identifier, default: "views.context"
|
||||
|
||||
# @!attribute [rw] view_name_inferrer
|
||||
# @api private
|
||||
setting :view_name_inferrer, default: Slice::ViewNameInferrer
|
||||
|
||||
# @!attribute [rw] view_name_inference_base
|
||||
# @api private
|
||||
setting :view_name_inference_base, default: "views"
|
||||
|
||||
# @api private
|
||||
attr_reader :base_config
|
||||
protected :base_config
|
||||
|
||||
# @api private
|
||||
def initialize(*, **options)
|
||||
super()
|
||||
|
||||
|
@ -42,19 +117,22 @@ module Hanami
|
|||
configure_defaults
|
||||
end
|
||||
|
||||
# @api private
|
||||
def initialize_copy(source)
|
||||
super
|
||||
@base_config = source.base_config.dup
|
||||
@content_security_policy = source.content_security_policy.dup
|
||||
end
|
||||
private :initialize_copy
|
||||
|
||||
# @api private
|
||||
def finalize!
|
||||
# A nil value for `csrf_protection` means it has not been explicitly configured
|
||||
# (neither true nor false), so we can default it to whether sessions are enabled
|
||||
self.csrf_protection = sessions.enabled? if csrf_protection.nil?
|
||||
|
||||
if content_security_policy
|
||||
default_headers["Content-Security-Policy"] = content_security_policy.to_str
|
||||
default_headers["Content-Security-Policy"] = content_security_policy.to_s
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ module Hanami
|
|||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def to_str
|
||||
def to_s
|
||||
@policy.map do |key, value|
|
||||
"#{dasherize(key)} #{value}"
|
||||
end.join(";\n")
|
||||
|
|
|
@ -9,18 +9,45 @@ module Hanami
|
|||
# actions, and adds the `enabled?` method to allow app base action to determine whether to
|
||||
# include the `Action::Cookies` module.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
class Cookies
|
||||
# Returns the cookie options.
|
||||
#
|
||||
# @return [Hash]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :options
|
||||
|
||||
# Returns a new `Cookies`.
|
||||
#
|
||||
# You should not need to initialize this class directly. Instead use
|
||||
# {Hanami::Config::Actions#cookies}.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
# Returns true if any cookie options have been provided.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def enabled?
|
||||
!options.nil?
|
||||
end
|
||||
|
||||
# Returns the cookie options.
|
||||
#
|
||||
# If no options have been provided, returns an empty hash.
|
||||
#
|
||||
# @return [Hash]
|
||||
#
|
||||
# @api public
|
||||
def to_h
|
||||
options.to_h
|
||||
end
|
||||
|
|
|
@ -7,28 +7,114 @@ module Hanami
|
|||
class Config
|
||||
# Hanami logger config
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
class Logger
|
||||
include Dry::Configurable
|
||||
|
||||
# @return [Hanami::SliceName]
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.o
|
||||
attr_reader :app_name
|
||||
|
||||
protected :config
|
||||
|
||||
# @!attribute [rw] level
|
||||
# Sets or returns the logger level.
|
||||
#
|
||||
# Defaults to `:info` for the production environment and `:debug` for all others.
|
||||
#
|
||||
# @return [Symbol]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :level
|
||||
|
||||
# @!attribute [rw] stream
|
||||
# Sets or returns the logger's stream.
|
||||
#
|
||||
# This can be a file path or an `IO`-like object for the logger to write to.
|
||||
#
|
||||
# Defaults to `"log/test.log"` for the test environment and `$stdout` for all others.
|
||||
#
|
||||
# @return [String, #write]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :stream
|
||||
|
||||
# @!attribute [rw] formatter
|
||||
# Sets or returns the logger's formatter.
|
||||
#
|
||||
# This may be a name that matches a formatter registered with `Hanami::Logger`, which
|
||||
# includes `:default` and `:json`.
|
||||
#
|
||||
# This may also be an instance of Ruby's built-in `::Logger::Formatter` or any compatible
|
||||
# object.
|
||||
#
|
||||
# Defaults to `:json` for the production environment, and `nil` for all others. A `nil`
|
||||
# value will result in a plain `::Logger::Formatter` instance.
|
||||
#
|
||||
# @return [Symbol, ::Logger::Formatter]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :formatter
|
||||
|
||||
setting :colors
|
||||
# @!attribute [rw] colors
|
||||
# Sets or returns whether log lines should be colorized.
|
||||
#
|
||||
# Defaults to `false`.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :colors, default: false
|
||||
|
||||
# @!attribute [rw] filters
|
||||
# Sets or returns an array of attribute names to filter from logs.
|
||||
#
|
||||
# Defaults to `["_csrf", "password", "password_confirmation"]`. If you want to preserve
|
||||
# these defaults, append to this array rather than reassigning it.
|
||||
#
|
||||
# @return [Array<String>]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :filters, default: %w[_csrf password password_confirmation].freeze
|
||||
|
||||
setting :options, default: [], constructor: ->(value) { Array(value).flatten }, cloneable: true
|
||||
|
||||
# @!attribute [rw] logger_class
|
||||
# Sets or returns the class to use for the logger.
|
||||
#
|
||||
# This should be compatible with the arguments passed to the logger class' `.new` method in
|
||||
# {#instance}.
|
||||
#
|
||||
# Defaults to `Hanami::Logger`.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :logger_class, default: Hanami::Logger
|
||||
|
||||
# @!attribute [rw] options
|
||||
# Sets or returns an array of positional arguments to pass to the {logger_class} when
|
||||
# initializing the logger.
|
||||
#
|
||||
# Defaults to `[]`
|
||||
#
|
||||
# @return [Array<Object>]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
setting :options, default: [], constructor: ->(value) { Array(value).flatten }
|
||||
|
||||
# Returns a new `Logger` config.
|
||||
#
|
||||
# You should not need to initialize this directly, instead use {Hanami::Config#logger}.
|
||||
#
|
||||
# @param env [Symbol] the Hanami env
|
||||
# @param app_name [Hanami::SliceName]
|
||||
#
|
||||
# @api private
|
||||
def initialize(env:, app_name:)
|
||||
@app_name = app_name
|
||||
|
||||
|
@ -50,13 +136,14 @@ module Hanami
|
|||
when :production
|
||||
:json
|
||||
end
|
||||
|
||||
config.colors = case env
|
||||
when :production, :test
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a new instance of the logger.
|
||||
#
|
||||
# @return [logger_class]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def instance
|
||||
logger_class.new(
|
||||
app_name.name,
|
||||
|
|
|
@ -7,7 +7,9 @@ module Hanami
|
|||
class Config
|
||||
# Hanami views config
|
||||
#
|
||||
# @since 2.0.0
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
class Views
|
||||
include Dry::Configurable
|
||||
|
||||
|
@ -16,6 +18,7 @@ module Hanami
|
|||
attr_reader :base_config
|
||||
protected :base_config
|
||||
|
||||
# @api private
|
||||
def initialize(*)
|
||||
super
|
||||
|
||||
|
@ -24,10 +27,12 @@ module Hanami
|
|||
configure_defaults
|
||||
end
|
||||
|
||||
# @api private
|
||||
def initialize_copy(source)
|
||||
super
|
||||
@base_config = source.base_config.dup
|
||||
end
|
||||
private :initialize_copy
|
||||
|
||||
# Returns the list of available settings
|
||||
#
|
||||
|
@ -39,6 +44,7 @@ module Hanami
|
|||
self.class.settings + View.settings - NON_FORWARDABLE_METHODS
|
||||
end
|
||||
|
||||
# @api private
|
||||
def finalize!
|
||||
return self if frozen?
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@ module Hanami
|
|||
PATH_DELIMITER = "/"
|
||||
private_constant :PATH_DELIMITER
|
||||
|
||||
# @api private
|
||||
APP_PATH = "config/app.rb"
|
||||
private_constant :APP_PATH
|
||||
|
||||
# @api private
|
||||
CONFIG_DIR = "config"
|
||||
private_constant :CONFIG_DIR
|
||||
|
|
|
@ -1,18 +1,35 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# Base class for all Hanami errors.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
# Error raised when {Hanami::App} fails to load.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
AppLoadError = Class.new(Error)
|
||||
|
||||
# Error raised when an {Hanami::Slice} fails to load.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
SliceLoadError = Class.new(Error)
|
||||
|
||||
# Error raised when an individual component fails to load.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
ComponentLoadError = Class.new(Error)
|
||||
|
||||
# Error raised when unsupported middleware configuration is given.
|
||||
#
|
||||
# @see Hanami::Slice::Routing::Middleware::Stack#use
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
UnsupportedMiddlewareSpecError = Class.new(Error)
|
||||
end
|
||||
|
|
|
@ -5,14 +5,17 @@ require_relative "../slice_configurable"
|
|||
require_relative "action/slice_configured_action"
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Extensions
|
||||
# Extended behavior for actions intended for use within an Hanami app.
|
||||
# Integrated behavior for `Hanami::Action` classes within Hanami apps.
|
||||
#
|
||||
# @see Hanami::Action
|
||||
# @see InstanceMethods
|
||||
# @see https://github.com/hanami/controller
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
module Action
|
||||
# @api private
|
||||
def self.included(action_class)
|
||||
super
|
||||
|
||||
|
@ -21,15 +24,45 @@ module Hanami
|
|||
action_class.prepend(InstanceMethods)
|
||||
end
|
||||
|
||||
# Class methods for app-integrated actions.
|
||||
#
|
||||
# @since 2.0.0
|
||||
module ClassMethods
|
||||
# @api private
|
||||
def configure_for_slice(slice)
|
||||
extend SliceConfiguredAction.new(slice)
|
||||
end
|
||||
end
|
||||
|
||||
# Instance methods for app-integrated actions.
|
||||
#
|
||||
# @since 2.0.0
|
||||
module InstanceMethods
|
||||
attr_reader :view, :view_context, :routes
|
||||
# @api private
|
||||
attr_reader :view
|
||||
|
||||
# @api private
|
||||
attr_reader :view_context
|
||||
|
||||
# Returns the app or slice's {Hanami::Slice::RoutesHelper RoutesHelper} for use within
|
||||
# action instance methods.
|
||||
#
|
||||
# @return [Hanami::Slice::RoutesHelper]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :routes
|
||||
|
||||
# @overload def initialize(routes: nil, **kwargs)
|
||||
# Returns a new `Hanami::Action` with app components injected as dependencies.
|
||||
#
|
||||
# These dependencies are injected automatically so that a call to `.new` (with no
|
||||
# arguments) returns a fully integrated action.
|
||||
#
|
||||
# @param routes [Hanami::Slice::RoutesHelper]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def initialize(view: nil, view_context: nil, routes: nil, **kwargs)
|
||||
@view = view
|
||||
@view_context = view_context
|
||||
|
@ -40,27 +73,31 @@ module Hanami
|
|||
|
||||
private
|
||||
|
||||
# @api private
|
||||
def build_response(**options)
|
||||
options = options.merge(view_options: method(:view_options))
|
||||
super(**options)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def finish(req, res, halted)
|
||||
res.render(view, **req.params) if !halted && auto_render?(res)
|
||||
super
|
||||
end
|
||||
|
||||
# @api private
|
||||
def view_options(req, res)
|
||||
{context: view_context&.with(**view_context_options(req, res))}.compact
|
||||
end
|
||||
|
||||
# @api private
|
||||
def view_context_options(req, res)
|
||||
{request: req, response: res}
|
||||
end
|
||||
|
||||
# Returns true if a view should automatically be rendered onto the response body.
|
||||
#
|
||||
# This may be overridden to enable/disable automatic rendering.
|
||||
# This may be overridden to enable or disable automatic rendering.
|
||||
#
|
||||
# @param res [Hanami::Action::Response]
|
||||
#
|
||||
|
|
|
@ -5,14 +5,17 @@ require_relative "../slice_configurable"
|
|||
require_relative "view/slice_configured_view"
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Extensions
|
||||
# Extended behavior for actions intended for use within an Hanami app.
|
||||
# Integrated behavior for `Hanami::View` classes within Hanami apps.
|
||||
#
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @see Hanami::View
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
module View
|
||||
# @api private
|
||||
def self.included(view_class)
|
||||
super
|
||||
|
||||
|
@ -20,6 +23,7 @@ module Hanami
|
|||
view_class.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# @api private
|
||||
module ClassMethods
|
||||
# @api private
|
||||
def configure_for_slice(slice)
|
||||
|
|
|
@ -11,8 +11,9 @@ module Hanami
|
|||
module View
|
||||
# View context for views in Hanami apps.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
# This is NOT RELEASED as of 2.0.0.
|
||||
#
|
||||
# @api private
|
||||
module Context
|
||||
def self.included(context_class)
|
||||
super
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Providers
|
||||
# Provider source to register inflector component in Hanami slices.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Inflector < Dry::System::Provider::Source
|
||||
# @api private
|
||||
def start
|
||||
register :inflector, Hanami.app.inflector
|
||||
end
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Providers
|
||||
# Provider source to register logger component in Hanami slices.
|
||||
#
|
||||
# @see Hanami::Config#logger
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Logger < Dry::System::Provider::Source
|
||||
# @api private
|
||||
def start
|
||||
register :logger, Hanami.app.config.logger_instance
|
||||
end
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Providers
|
||||
# Provider source to register Rack integration components in Hanami slices.
|
||||
#
|
||||
# @see Hanami::Providers::Logger
|
||||
# @see Hanami::Web::RackLogger
|
||||
# @see https://github.com/rack/rack
|
||||
# @see https://dry-rb.org/gems/dry-monitor/
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Rack < Dry::System::Provider::Source
|
||||
# @api private
|
||||
def prepare
|
||||
require "dry/monitor"
|
||||
require "hanami/web/rack_logger"
|
||||
|
@ -10,6 +21,7 @@ module Hanami
|
|||
Dry::Monitor.load_extensions(:rack)
|
||||
end
|
||||
|
||||
# @api private
|
||||
def start
|
||||
target.start :logger
|
||||
|
||||
|
|
|
@ -1,27 +1,37 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Providers
|
||||
# Provider source to register routes helper component in Hanami slices.
|
||||
#
|
||||
# @see Hanami::Slice::RoutesHelper
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Routes < Dry::System::Provider::Source
|
||||
# @api private
|
||||
def self.for_slice(slice)
|
||||
Class.new(self) do |klass|
|
||||
klass.instance_variable_set(:@slice, slice)
|
||||
end
|
||||
end
|
||||
|
||||
# @api private
|
||||
def self.slice
|
||||
@slice || Hanami.app
|
||||
end
|
||||
|
||||
# @api private
|
||||
def prepare
|
||||
require "hanami/slice/routes_helper"
|
||||
end
|
||||
|
||||
# @api private
|
||||
def start
|
||||
# Register a lazy instance of RoutesHelper to ensure we don't load prematurely
|
||||
# load the router during the process of booting. This ensures the router's
|
||||
# resolver can run strict action key checks once when it runs on a fully booted
|
||||
# slice.
|
||||
# Register a lazy instance of RoutesHelper to ensure we don't load prematurely load the
|
||||
# router during the process of booting. This ensures the router's resolver can run strict
|
||||
# action key checks once when it runs on a fully booted slice.
|
||||
register :routes do
|
||||
Hanami::Slice::RoutesHelper.new(self.class.slice.router)
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "constants"
|
||||
require_relative "errors"
|
||||
require_relative "slice/router"
|
||||
|
||||
module Hanami
|
||||
|
@ -26,8 +27,13 @@ module Hanami
|
|||
# @see Hanami::Slice::Router
|
||||
# @since 2.0.0
|
||||
class Routes
|
||||
# Error raised when no action could be found in an app or slice container for the key given in a
|
||||
# routes file.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
class MissingActionError < Error
|
||||
class MissingActionError < Hanami::Error
|
||||
# @api private
|
||||
def initialize(action_key, slice)
|
||||
action_path = action_key.gsub(CONTAINER_KEY_DELIMITER, PATH_DELIMITER)
|
||||
action_constant = slice.inflector.camelize(
|
||||
|
@ -43,8 +49,13 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Error raised when a given routes endpoint does not implement the `#call` interface required
|
||||
# for Rack.
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
class NotCallableEndpointError < Error
|
||||
class NotCallableEndpointError < Hanami::Error
|
||||
# @api private
|
||||
def initialize(endpoint)
|
||||
super("#{endpoint.inspect} is not compatible with Rack. Please make sure it implements #call.")
|
||||
end
|
||||
|
|
|
@ -2,42 +2,94 @@
|
|||
|
||||
require "dry/core"
|
||||
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
|
||||
|
||||
|
@ -94,25 +146,6 @@ module Hanami
|
|||
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
|
||||
|
||||
|
@ -143,14 +176,47 @@ module Hanami
|
|||
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)
|
||||
|
|
|
@ -4,7 +4,7 @@ require "dry/core/constants"
|
|||
|
||||
module Hanami
|
||||
class Settings
|
||||
# The default store for {Hanami:Settings}, loading setting values from `ENV`.
|
||||
# The default store for {Hanami::Settings}, loading setting values from `ENV`.
|
||||
#
|
||||
# If your app loads the dotenv gem, then `ENV` will also be populated from various `.env` files when
|
||||
# you subclass `Hanami::App`.
|
||||
|
|
|
@ -35,6 +35,7 @@ module Hanami
|
|||
class Slice
|
||||
@_mutex = Mutex.new
|
||||
|
||||
# @api private
|
||||
def self.inherited(subclass)
|
||||
super
|
||||
|
||||
|
@ -51,14 +52,69 @@ module Hanami
|
|||
|
||||
# rubocop:disable Metrics/ModuleLength
|
||||
module ClassMethods
|
||||
attr_reader :parent, :autoloader, :container
|
||||
# Returns the slice's parent.
|
||||
#
|
||||
# For top-level slices defined in `slices/` or `config/slices/`, this will be the Hanami app
|
||||
# itself (`Hanami.app`). For nested slices, this will be the slice in which they were
|
||||
# registered.
|
||||
#
|
||||
# @return [Hanami::Slice]
|
||||
#
|
||||
# @see #register_slice
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :parent
|
||||
|
||||
# Returns the slice's autoloader.
|
||||
#
|
||||
# Each slice has its own `Zeitwerk::Loader` autoloader instance, which is setup when the slice
|
||||
# is {#prepare prepared}.
|
||||
#
|
||||
# @return [Zeitwerk::Loader]
|
||||
#
|
||||
# @see https://github.com/fxn/zeitwerk
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :autoloader
|
||||
|
||||
# Returns the slice's container.
|
||||
#
|
||||
# This is a `Dry::System::Container` that is already configured for the slice.
|
||||
#
|
||||
# In ordinary usage, you shouldn't need direct access the container at all, since the slice
|
||||
# provides its own methods for interacting with the container (such as {#[]}, {#keys}, {#key?}
|
||||
# {#register}, {#register_provider}, {#prepare}, {#start}, {#stop}).
|
||||
#
|
||||
# If you need to configure the container directly, use {#prepare_container}.
|
||||
#
|
||||
# @see https://dry-rb.org/gems/dry-system
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :container
|
||||
|
||||
# Returns the Hanami app.
|
||||
#
|
||||
# @return [Hanami::App]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def app
|
||||
Hanami.app
|
||||
end
|
||||
|
||||
# A slice's config is copied from the app config at time of first access. The app should have
|
||||
# its config completed before slices are loaded.
|
||||
# Returns the slice's config.
|
||||
#
|
||||
# A slice's config is copied from the app config at time of first access.
|
||||
#
|
||||
# @return [Hanami::Config]
|
||||
#
|
||||
# @see App::ClassMethods.config
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def config
|
||||
@config ||= app.config.dup.tap do |slice_config|
|
||||
# Remove specific values from app that will not apply to this slice
|
||||
|
@ -66,14 +122,41 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a {SliceName} for the slice, an object with methods returning the name of the slice
|
||||
# in various formats.
|
||||
#
|
||||
# @return [SliceName]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def slice_name
|
||||
@slice_name ||= SliceName.new(self, inflector: method(:inflector))
|
||||
end
|
||||
|
||||
# Returns the constant for the slice's module namespace.
|
||||
#
|
||||
# @example
|
||||
# MySlice::Slice.namespace # => MySlice
|
||||
#
|
||||
# @return [Module] the namespace module constant
|
||||
#
|
||||
# @see SliceName#namespace
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def namespace
|
||||
slice_name.namespace
|
||||
end
|
||||
|
||||
# Returns the slice's root, either the root as explicitly configured, or a default fallback of
|
||||
# the slice's name within the app's `slices/` dir.
|
||||
#
|
||||
# @return [Pathname]
|
||||
#
|
||||
# @see Config#root
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def root
|
||||
# Provide a best guess for a root when it is not yet configured.
|
||||
#
|
||||
|
@ -89,23 +172,105 @@ module Hanami
|
|||
config.root || app.root.join(SLICES_DIR, slice_name.to_s)
|
||||
end
|
||||
|
||||
# Returns the slice's configured inflector.
|
||||
#
|
||||
# Unless explicitly re-configured for the slice, this will be the app's inflector.
|
||||
#
|
||||
# @return [Dry::Inflector]
|
||||
#
|
||||
# @see Config#inflector
|
||||
# @see Config#inflections
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def inflector
|
||||
config.inflector
|
||||
end
|
||||
|
||||
# @overload prepare
|
||||
# Prepares the slice.
|
||||
#
|
||||
# This will define the slice's `Slice` and `Deps` constants, make all Ruby source files
|
||||
# inside the slice's root dir autoloadable, as well as lazily loadable as container
|
||||
# components.
|
||||
#
|
||||
# Call `prepare` when you want to access particular components within the slice while still
|
||||
# minimizing load time. Preparing slices is the approach taken when loading the Hanami
|
||||
# console or when running tests.
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @see #boot
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
#
|
||||
# @overload prepare(provider_name)
|
||||
# Prepares a provider.
|
||||
#
|
||||
# This triggers the provider's `prepare` lifecycle step.
|
||||
#
|
||||
# @param provider_name [Symbol] the name of the provider to start
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def prepare(provider_name = nil)
|
||||
if provider_name
|
||||
container.prepare(provider_name)
|
||||
self
|
||||
else
|
||||
prepare_slice
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Captures the given block to be called with the slice's container during the slice's
|
||||
# `prepare` step, after the slice has already configured the container.
|
||||
#
|
||||
# This is intended for advanced usage only and should not be needed for ordinary slice
|
||||
# configuration and usage.
|
||||
#
|
||||
# @example
|
||||
# module MySlice
|
||||
# class Sliice < Hanami::Slice
|
||||
# prepare_container do |container|
|
||||
# # ...
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @yieldparam container [Dry::System::Container] the slice's container
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @see #prepare
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def prepare_container(&block)
|
||||
@prepare_container_block = block
|
||||
self
|
||||
end
|
||||
|
||||
# Boots the slice.
|
||||
#
|
||||
# This will prepare the slice (if not already prepared), start each of its providers, register
|
||||
# all the slice's components from its Ruby source files, and import components from any other
|
||||
# slices. It will also boot any of the slice's own registered nested slices. It will then
|
||||
# freeze its container so no further components can be registered.
|
||||
#
|
||||
# Call `boot` if you want to fully load a slice and incur all load time up front, such as when
|
||||
# preparing an app to serve web requests. Booting slices is the approach taken when running
|
||||
# Hanami's standard Puma setup (see `config.ru`).
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @see #prepare
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def boot
|
||||
return self if booted?
|
||||
|
||||
|
@ -119,64 +284,345 @@ module Hanami
|
|||
self
|
||||
end
|
||||
|
||||
# Shuts down the slice's providers, as well as the providers in any nested slices.
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def shutdown
|
||||
slices.each(&:shutdown)
|
||||
container.shutdown!
|
||||
self
|
||||
end
|
||||
|
||||
# Returns true if the slice has been prepared.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @see #prepare
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def prepared?
|
||||
!!@prepared
|
||||
end
|
||||
|
||||
# Returns true if the slice has been booted.
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @see #boot
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def booted?
|
||||
!!@booted
|
||||
end
|
||||
|
||||
# Returns the slice's collection of nested slices.
|
||||
#
|
||||
# @return [SliceRegistrar]
|
||||
#
|
||||
# @see #register_slice
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def slices
|
||||
@slices ||= SliceRegistrar.new(self)
|
||||
end
|
||||
|
||||
# @overload register_slice(name, &block)
|
||||
# Registers a nested slice with the given name.
|
||||
#
|
||||
# This will define a new {Slice} subclass for the slice. If a block is given, it is passed
|
||||
# the class object, and will be evaluated in the context of the class like `class_eval`.
|
||||
#
|
||||
# @example
|
||||
# MySlice::Slice.register_slice do
|
||||
# # Configure the slice or do other class-level things here
|
||||
# end
|
||||
#
|
||||
# @param name [Symbol] the identifier for the slice to be registered
|
||||
# @yieldparam slice [Hanami::Slice] the newly defined slice class
|
||||
#
|
||||
# @overload register_slice(name, slice_class)
|
||||
# Registers a nested slice with the given name.
|
||||
#
|
||||
# The given `slice_class` will be registered as the slice. It must be a subclass of {Slice}.
|
||||
#
|
||||
# @param name [Symbol] the identifier for the slice to be registered
|
||||
# @param slice_class [Hanami::Slice]
|
||||
#
|
||||
# @return [slices]
|
||||
#
|
||||
# @see SliceRegistrar#register
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def register_slice(...)
|
||||
slices.register(...)
|
||||
end
|
||||
|
||||
# Registers a component in the slice's container.
|
||||
#
|
||||
# @overload register(key, object)
|
||||
# Registers the given object as the component. This same object will be returned whenever
|
||||
# the component is resolved.
|
||||
#
|
||||
# @param key [String] the component's key
|
||||
# @param object [Object] the object to register as the component
|
||||
#
|
||||
# @overload reigster(key, memoize: false, &block)
|
||||
# Registers the given block as the component. When the component is resolved, the return
|
||||
# value of the block will be returned.
|
||||
#
|
||||
# Since the block is not called until resolution-time, this is a useful way to register
|
||||
# components that have dependencies on other components in the container, which as yet may
|
||||
# be unavailable at the time of registration.
|
||||
#
|
||||
# All auto-registered components are registered in block form.
|
||||
#
|
||||
# When `memoize` is true, the component will be memoized upon first resolution and the same
|
||||
# object returned on all subsequent resolutions, meaning the block is only called once.
|
||||
# Otherwise, the block will be called and a new object returned on every resolution.
|
||||
#
|
||||
# @param key [String] the component's key
|
||||
# @param memoize [Boolean]
|
||||
# @yieldreturn [Object] the object to register as the component
|
||||
#
|
||||
# @overload reigster(key, call: true, &block)
|
||||
# Registers the given block as the component. When `call: false` is given, then the block
|
||||
# itself will become the component.
|
||||
#
|
||||
# When such a component is resolved, the block will not be called, and instead the `Proc`
|
||||
# object for that block will be returned.
|
||||
#
|
||||
# @param key [String] the component's key
|
||||
# @param call [Booelan]
|
||||
#
|
||||
# @return [container]
|
||||
#
|
||||
# @see #[]
|
||||
# @see #resolve
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def register(...)
|
||||
container.register(...)
|
||||
end
|
||||
|
||||
# @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
|
||||
# Registers a provider and its lifecycle hooks.
|
||||
#
|
||||
# In most cases, you should call this from a dedicated file for the provider in your app or
|
||||
# slice's `config/providers/` dir. This allows the provider to be loaded when individual
|
||||
# matching components are resolved (for prepared slices) or when slices are booted.
|
||||
#
|
||||
# @example Simple provider
|
||||
# # config/providers/db.rb
|
||||
# Hanami.app.register_provider(:db) do
|
||||
# start do
|
||||
# require "db"
|
||||
# register("db", DB.new)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @example Provider with lifecycle steps, also using dependencies from the target container
|
||||
# # config/providers/db.rb
|
||||
# Hanami.app.register_provider(:db) do
|
||||
# prepare do
|
||||
# require "db"
|
||||
# db = DB.new(target_container["settings"].database_url)
|
||||
# register("db", db)
|
||||
# end
|
||||
#
|
||||
# start do
|
||||
# container["db"].establish_connection
|
||||
# end
|
||||
#
|
||||
# stop do
|
||||
# container["db"].close_connection
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @example Probvider registration under a namespace
|
||||
# # config/providers/db.rb
|
||||
# Hanami.app.register_provider(:persistence, namespace: true) do
|
||||
# start do
|
||||
# require "db"
|
||||
#
|
||||
# # Namespace option above means this will be registered as "persistence.db"
|
||||
# register("db", DB.new)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @param name [Symbol] the unique name for the provider
|
||||
# @param namespace [Boolean, String, nil] register components from the provider with given
|
||||
# namespace. May be an explicit string, or `true` for the namespace to be the provider's
|
||||
# name
|
||||
# @param from [Symbol, nil] the group for an external provider source to use, with the
|
||||
# provider source name inferred from `name` or passsed explicitly as `source:`
|
||||
# @param source [Symbol, nil] the name of the external provider source to use, if different
|
||||
# from the value provided as `name`
|
||||
# @param if [Boolean] a boolean-returning expression to determine whether to register the
|
||||
# provider
|
||||
#
|
||||
# @return [container]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def register_provider(...)
|
||||
container.register_provider(...)
|
||||
end
|
||||
|
||||
# @overload start(provider_name)
|
||||
# Starts a provider.
|
||||
#
|
||||
# This triggers the provider's `prepare` and `start` lifecycle steps.
|
||||
#
|
||||
# @example
|
||||
# MySlice::Slice.start(:persistence)
|
||||
#
|
||||
# @param provider_name [Symbol] the name of the provider to start
|
||||
#
|
||||
# @return [container]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def start(...)
|
||||
container.start(...)
|
||||
end
|
||||
|
||||
# @overload stop(provider_name)
|
||||
# Stops a provider.
|
||||
#
|
||||
# This triggers the provider's `stop` lifecycle hook.
|
||||
#
|
||||
# @example
|
||||
# MySlice::Slice.stop(:persistence)
|
||||
#
|
||||
# @param provider_name [Symbol] the name of the provider to start
|
||||
#
|
||||
# @return [container]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def stop(...)
|
||||
container.stop(...)
|
||||
end
|
||||
|
||||
# @overload key?(key)
|
||||
# Returns true if the component with the given key is registered in the container.
|
||||
#
|
||||
# For a prepared slice, calling `key?` will also try to load the component if not loaded
|
||||
# already.
|
||||
#
|
||||
# @param key [String, Symbol] the component key
|
||||
#
|
||||
# @return [Boolean]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def key?(...)
|
||||
container.key?(...)
|
||||
end
|
||||
|
||||
# Returns an array of keys for all currently registered components in the container.
|
||||
#
|
||||
# For a prepared slice, this will be the set of components that have been previously resolved.
|
||||
# For a booted slice, this will be all components available for the slice.
|
||||
#
|
||||
# @return [Array<String>]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def keys
|
||||
container.keys
|
||||
end
|
||||
|
||||
# @overload [](key)
|
||||
# Resolves the component with the given key from the container.
|
||||
#
|
||||
# For a prepared slice, this will attempt to load and register the matching component if it
|
||||
# is not loaded already. For a booted slice, this will return from already registered
|
||||
# components only.
|
||||
#
|
||||
# @return [Object] the resolved component's object
|
||||
#
|
||||
# @raise Dry::Container::KeyError if the component could not be found or loaded
|
||||
#
|
||||
# @see #resolve
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def [](...)
|
||||
container.[](...)
|
||||
end
|
||||
|
||||
# @see #[]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def resolve(...)
|
||||
container.resolve(...)
|
||||
end
|
||||
|
||||
# Specifies the components to export from the slice.
|
||||
#
|
||||
# Slices importing from this slice can import the specified components only.
|
||||
#
|
||||
# @example
|
||||
# module MySlice
|
||||
# class Slice < Hanami::Slice
|
||||
# export ["search", "index_entity"]
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @param keys [Array<String>] the component keys to export
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def export(keys)
|
||||
container.config.exports = keys
|
||||
self
|
||||
end
|
||||
|
||||
# @overload import(from:, as: nil, keys: nil)
|
||||
# Specifies components to import from another slice.
|
||||
#
|
||||
# Booting a slice will register all imported components. For a prepared slice, these
|
||||
# components will be be imported automatically when resolved.
|
||||
#
|
||||
# @example
|
||||
# module MySlice
|
||||
# class Slice < Hanami:Slice
|
||||
# # Component from Search::Slice will import as "search.index_entity"
|
||||
# import keys: ["index_entity"], from: :search
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @example Other import variations
|
||||
# # Different key namespace: component will be "search_backend.index_entity"
|
||||
# import keys: ["index_entity"], from: :search, as: "search_backend"
|
||||
#
|
||||
# # Import to root key namespace: component will be "index_entity"
|
||||
# import keys: ["index_entity"], from: :search, as: nil
|
||||
#
|
||||
# # Import all components
|
||||
# import from: :search
|
||||
#
|
||||
# @param keys [Array<String>, nil] Array of component keys to import. To import all
|
||||
# available components, omit this argument.
|
||||
# @param from [Symbol] name of the slice to import from
|
||||
# @param as [Symbol, String, nil]
|
||||
#
|
||||
# @see #export
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def import(from:, **kwargs)
|
||||
slice = self
|
||||
|
||||
|
@ -192,16 +638,51 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the slice's settings, or nil if no settings are defined.
|
||||
#
|
||||
# You can define your settings in `config/settings.rb`.
|
||||
#
|
||||
# @return [Hanami::Settings, nil]
|
||||
#
|
||||
# @see Hanami::Settings
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def settings
|
||||
return @settings if instance_variable_defined?(:@settings)
|
||||
|
||||
@settings = Settings.load_for_slice(self)
|
||||
end
|
||||
|
||||
# Returns the slice's routes, or nil if no routes are defined.
|
||||
#
|
||||
# You can define your routes in `config/routes.rb`.
|
||||
#
|
||||
# @return [Hanami::Routes, nil]
|
||||
#
|
||||
# @see Hanami::Routes
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def routes
|
||||
@routes ||= load_routes
|
||||
end
|
||||
|
||||
# Returns the slice's router, if or nil if no routes are defined.
|
||||
#
|
||||
# An optional `inspector`, implementing the `Hanami::Router::Inspector` interface, may be
|
||||
# provided at first call (the router is then memoized for subsequent accesses). An inspector
|
||||
# is used by the `hanami routes` CLI comment to provide a list of available routes.
|
||||
#
|
||||
# The returned router is a {Slice::Router}, which provides all `Hanami::Router` functionality,
|
||||
# with the addition of support for slice mounting with the {Slice::Router#slice}.
|
||||
#
|
||||
# @param inspector [Hanami::Router::Inspector, nil] an optional routes inspector
|
||||
#
|
||||
# @return [Hanami::Slice::Router, nil]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def router(inspector: nil)
|
||||
raise SliceLoadError, "#{self} must be prepared before loading the router" unless prepared?
|
||||
|
||||
|
@ -210,12 +691,38 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# Returns a [Rack][rack] app for the slice, or nil if no routes are defined.
|
||||
#
|
||||
# The rack app will be memoized on first access.
|
||||
#
|
||||
# [rack]: https://github.com/rack/rack
|
||||
#
|
||||
# @return [#call, nil] the rack app, or nil if no routes are defined
|
||||
#
|
||||
# @see #routes
|
||||
# @see #router
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def rack_app
|
||||
return unless router
|
||||
|
||||
@rack_app ||= router.to_rack_app
|
||||
end
|
||||
|
||||
# @overload call(rack_env)
|
||||
# Calls the slice's [Rack][rack] app and returns a Rack-compatible response object
|
||||
#
|
||||
# [rack]: https://github.com/rack/rack
|
||||
#
|
||||
# @param rack_env [Hash] the Rack environment for the request
|
||||
#
|
||||
# @return [Array] the three-element Rack response array
|
||||
#
|
||||
# @see #rack_app
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def call(...)
|
||||
rack_app.call(...)
|
||||
end
|
||||
|
|
|
@ -5,22 +5,27 @@ require_relative "routing/middleware/stack"
|
|||
|
||||
module Hanami
|
||||
class Slice
|
||||
# Hanami app router
|
||||
# `Hanami::Router` subclass with enhancements for use within Hanami apps.
|
||||
#
|
||||
# This is loaded from Hanami apps and slices and made available as their
|
||||
# {Hanami::Slice::ClassMethods#router router}.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Router < ::Hanami::Router
|
||||
# @api private
|
||||
attr_reader :middleware_stack
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def initialize(routes:, middleware_stack: Routing::Middleware::Stack.new, **kwargs, &blk)
|
||||
@middleware_stack = middleware_stack
|
||||
instance_eval(&blk)
|
||||
super(**kwargs, &routes)
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def freeze
|
||||
return self if frozen?
|
||||
|
||||
|
@ -28,20 +33,40 @@ module Hanami
|
|||
super
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def use(...)
|
||||
middleware_stack.use(...)
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def scope(*args, &blk)
|
||||
middleware_stack.with(args.first) do
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Yields a block for routes to resolve their action components from the given slice.
|
||||
#
|
||||
# An optional URL prefix may be supplied with `at:`.
|
||||
#
|
||||
# @example
|
||||
# # config/routes.rb
|
||||
#
|
||||
# module MyApp
|
||||
# class Routes < Hanami::Routes
|
||||
# slice :admin, at: "/admin" do
|
||||
# # Will route to the "actions.posts.index" component in Admin::Slice
|
||||
# get "posts", to: "posts.index"
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @param slice_name [Symbol] the slice's name
|
||||
# @param at [String, nil] optional URL prefix for the routes
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
def slice(slice_name, at:, &blk)
|
||||
blk ||= @resolver.find_slice(slice_name).routes
|
||||
|
@ -54,8 +79,8 @@ module Hanami
|
|||
@resolver = prev_resolver
|
||||
end
|
||||
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def to_rack_app
|
||||
middleware_stack.to_rack_app(self)
|
||||
end
|
||||
|
|
|
@ -11,8 +11,8 @@ module Hanami
|
|||
module Middleware
|
||||
# Wraps a rack app with a middleware stack
|
||||
#
|
||||
# We use this class to add middlewares to the rack application generated
|
||||
# from {Hanami::Slice::Router}.
|
||||
# We use this class to add middlewares to the rack application generated from
|
||||
# {Hanami::Slice::Router}.
|
||||
#
|
||||
# ```
|
||||
# stack = Hanami::Slice::Routing::Middleware::Stack.new
|
||||
|
@ -28,6 +28,8 @@ module Hanami
|
|||
# end
|
||||
# ```
|
||||
#
|
||||
# @see Hanami::Config#middleware
|
||||
#
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
class Stack
|
||||
|
@ -42,8 +44,15 @@ module Hanami
|
|||
# @api private
|
||||
attr_reader :stack
|
||||
|
||||
# @since 2.0.0
|
||||
# Returns an array of Ruby namespaces from which to load middleware classes specified by
|
||||
# symbol names given to {#use}.
|
||||
#
|
||||
# Defaults to `[Hanami::Middleware]`.
|
||||
#
|
||||
# @return [Array<Object>]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
attr_reader :namespaces
|
||||
|
||||
# @since 2.0.0
|
||||
|
@ -63,8 +72,30 @@ module Hanami
|
|||
@namespaces = namespaces.dup
|
||||
end
|
||||
|
||||
# Adds a middleware to the stack.
|
||||
#
|
||||
# @example
|
||||
# # Using a symbol name; adds Hanami::Middleware::BodyParser.new([:json])
|
||||
# middleware.use :body_parser, :json
|
||||
#
|
||||
# # Using a class name
|
||||
# middleware.use MyMiddleware
|
||||
#
|
||||
# # Adding a middleware before or after others
|
||||
# middleware.use MyMiddleware, before: SomeMiddleware
|
||||
# middleware.use MyMiddleware, after: OtherMiddleware
|
||||
#
|
||||
# @param spec [Symbol, Class] the middleware name or class name
|
||||
# @param args [Array, nil] Arguments to pass to the middleware's `.new` method
|
||||
# @param before [Class, nil] an optional (already added) middleware class to add the
|
||||
# middleware before
|
||||
# @param after [Class, nil] an optional (already added) middleware class to add the
|
||||
# middleware after
|
||||
#
|
||||
# @return [self]
|
||||
#
|
||||
# @api public
|
||||
# @since 2.0.0
|
||||
# @api private
|
||||
def use(spec, *args, before: nil, after: nil, &blk)
|
||||
middleware = resolve_middleware_class(spec)
|
||||
item = [middleware, args, blk]
|
||||
|
|
|
@ -4,9 +4,14 @@ require_relative "../../routes"
|
|||
|
||||
module Hanami
|
||||
class Slice
|
||||
# @api private
|
||||
module Routing
|
||||
# Hanami app router endpoint resolver
|
||||
#
|
||||
# This resolves endpoints objects from a slice container using the strings passed to `to:` as
|
||||
# their container keys.
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class Resolver
|
||||
SLICE_ACTIONS_KEY_NAMESPACE = "actions"
|
||||
|
|
|
@ -23,7 +23,7 @@ module Hanami
|
|||
# ViewNameInferrer.call(action_name: "Main::Actions::Posts::Create", slice: Main::Slice)
|
||||
# # => ["views.posts.create", "views.posts.new"]
|
||||
#
|
||||
# @param action_name [String] action class name
|
||||
# @param action_class_name [String] action class name
|
||||
# @param slice [Hanami::Slice, Hanami::Application] Hanami slice containing the action
|
||||
#
|
||||
# @return [Array<string>] array of paired view container keys
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Hanami
|
||||
# @api private
|
||||
module Web
|
||||
# Rack logger for Hanami apps
|
||||
#
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
class RackLogger
|
||||
REQUEST_METHOD = "REQUEST_METHOD"
|
||||
private_constant :REQUEST_METHOD
|
||||
|
@ -25,10 +29,14 @@ module Hanami
|
|||
CONTENT_LENGTH = "Content-Length"
|
||||
private_constant :CONTENT_LENGTH
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def initialize(logger)
|
||||
@logger = logger
|
||||
end
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def attach(rack_monitor)
|
||||
rack_monitor.on :stop do |event|
|
||||
log_request event[:env], event[:status], event[:time]
|
||||
|
@ -39,6 +47,8 @@ module Hanami
|
|||
end
|
||||
end
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def log_request(env, status, elapsed)
|
||||
data = {
|
||||
verb: env[REQUEST_METHOD],
|
||||
|
@ -54,6 +64,8 @@ module Hanami
|
|||
logger.info(data)
|
||||
end
|
||||
|
||||
# @api private
|
||||
# @since 2.0.0
|
||||
def log_exception(exception)
|
||||
logger.error exception.message
|
||||
logger.error exception.backtrace.join("\n")
|
||||
|
|
|
@ -29,7 +29,7 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
|
|||
%(style-src 'self' 'unsafe-inline' https:)
|
||||
].join("\n")
|
||||
|
||||
expect(content_security_policy.to_str).to eq(expected)
|
||||
expect(content_security_policy.to_s).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -51,35 +51,35 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
|
|||
content_security_policy[:script_src] += " #{cdn_url}"
|
||||
|
||||
expect(content_security_policy[:script_src]).to eq("'self' #{cdn_url}")
|
||||
expect(content_security_policy.to_str).to match("'self' #{cdn_url}")
|
||||
expect(content_security_policy.to_s).to match("'self' #{cdn_url}")
|
||||
end
|
||||
|
||||
it "overrides default values" do
|
||||
content_security_policy[:style_src] = cdn_url
|
||||
|
||||
expect(content_security_policy[:style_src]).to eq(cdn_url)
|
||||
expect(content_security_policy.to_str).to match(cdn_url)
|
||||
expect(content_security_policy.to_s).to match(cdn_url)
|
||||
end
|
||||
|
||||
it "nullifies value" do
|
||||
content_security_policy[:plugin_types] = nil
|
||||
|
||||
expect(content_security_policy[:plugin_types]).to be(nil)
|
||||
expect(content_security_policy.to_str).to match("plugin-types ;")
|
||||
expect(content_security_policy.to_s).to match("plugin-types ;")
|
||||
end
|
||||
|
||||
it "deletes key" do
|
||||
content_security_policy.delete(:object_src)
|
||||
|
||||
expect(content_security_policy[:object_src]).to be(nil)
|
||||
expect(content_security_policy.to_str).to_not match("object-src")
|
||||
expect(content_security_policy.to_s).to_not match("object-src")
|
||||
end
|
||||
|
||||
it "adds a custom key" do
|
||||
content_security_policy[:a_custom_key] = "foo"
|
||||
|
||||
expect(content_security_policy[:a_custom_key]).to eq("foo")
|
||||
expect(content_security_policy.to_str).to match("a-custom-key foo")
|
||||
expect(content_security_policy.to_s).to match("a-custom-key foo")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,7 +87,7 @@ RSpec.describe Hanami::Config::Actions, "#content_security_policy" do
|
|||
it "sets default header" do
|
||||
config.finalize!
|
||||
|
||||
expect(config.default_headers.fetch("Content-Security-Policy")).to eq(content_security_policy.to_str)
|
||||
expect(config.default_headers.fetch("Content-Security-Policy")).to eq(content_security_policy.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ RSpec.describe Hanami::Config::Actions, "default values" do
|
|||
"X-Frame-Options" => "DENY",
|
||||
"X-Content-Type-Options" => "nosniff",
|
||||
"X-XSS-Protection" => "1; mode=block",
|
||||
"Content-Security-Policy" => config.content_security_policy.to_str
|
||||
"Content-Security-Policy" => config.content_security_policy.to_s
|
||||
)
|
||||
}
|
||||
end
|
||||
|
|
|
@ -102,32 +102,16 @@ RSpec.describe Hanami::Config::Logger do
|
|||
end
|
||||
|
||||
describe "#colors" do
|
||||
it "defaults to nil" do
|
||||
expect(subject.colors).to eq(nil)
|
||||
end
|
||||
|
||||
context "when :test environment" do
|
||||
let(:env) { :test }
|
||||
|
||||
it "returns false" do
|
||||
expect(subject.colors).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
context "when :production environment" do
|
||||
let(:env) { :production }
|
||||
|
||||
it "returns false" do
|
||||
expect(subject.colors).to eq(false)
|
||||
end
|
||||
it "defaults to false" do
|
||||
expect(subject.colors).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#colors=" do
|
||||
it "accepts a value" do
|
||||
expect { subject.colors = false }
|
||||
expect { subject.colors = true }
|
||||
.to change { subject.colors }
|
||||
.to(false)
|
||||
.to(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue