piko-lite-box 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/hanami-3.0.1/CHANGELOG.md +1701 -0
- data/hanami-3.0.1/LICENSE +20 -0
- data/hanami-3.0.1/README.md +89 -0
- data/hanami-3.0.1/hanami.gemspec +49 -0
- data/hanami-3.0.1/lib/hanami/app.rb +197 -0
- data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
- data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
- data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
- data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
- data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
- data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
- data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
- data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
- data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
- data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
- data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
- data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
- data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
- data/hanami-3.0.1/lib/hanami/config.rb +564 -0
- data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
- data/hanami-3.0.1/lib/hanami/env.rb +52 -0
- data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
- data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
- data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
- data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
- data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
- data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
- data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
- data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
- data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
- data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
- data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
- data/hanami-3.0.1/lib/hanami/port.rb +45 -0
- data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
- data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
- data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
- data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
- data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
- data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
- data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
- data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
- data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
- data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
- data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
- data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
- data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
- data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
- data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
- data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
- data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
- data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
- data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
- data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
- data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
- data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
- data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
- data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
- data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
- data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
- data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
- data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
- data/hanami-3.0.1/lib/hanami/version.rb +45 -0
- data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
- data/hanami-3.0.1/lib/hanami.rb +276 -0
- data/piko-lite-box.gemspec +11 -0
- metadata +137 -0
|
@@ -0,0 +1,1239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
require "dry/system"
|
|
5
|
+
|
|
6
|
+
require_relative "constants"
|
|
7
|
+
require_relative "errors"
|
|
8
|
+
|
|
9
|
+
module Hanami
|
|
10
|
+
# A slice represents any distinct area of concern within an Hanami app.
|
|
11
|
+
#
|
|
12
|
+
# For smaller apps, a slice may encompass the whole app itself (see
|
|
13
|
+
# {Hanami::App}), whereas larger apps may consist of many slices.
|
|
14
|
+
#
|
|
15
|
+
# Each slice corresponds a single module namespace and a single root directory of source
|
|
16
|
+
# files for loading as components into its container.
|
|
17
|
+
#
|
|
18
|
+
# Each slice has its own config, and may optionally have its own settings, routes, as well as
|
|
19
|
+
# other nested slices.
|
|
20
|
+
#
|
|
21
|
+
# Slices expect an Hanami app to be defined (which itself is a slice). They will initialize their
|
|
22
|
+
# config as a copy of the app's, and will also configure certain components
|
|
23
|
+
#
|
|
24
|
+
# Slices must be _prepared_ and optionally _booted_ before they can be used (see
|
|
25
|
+
# {ClassMethods.prepare} and {ClassMethods.boot}). A prepared slice will lazily load its
|
|
26
|
+
# components and nested slices (useful for minimising initial load time), whereas a
|
|
27
|
+
# booted slice will eagerly load all its components and nested slices, then freeze its
|
|
28
|
+
# container.
|
|
29
|
+
#
|
|
30
|
+
# @since 2.0.0
|
|
31
|
+
class Slice
|
|
32
|
+
@_mutex = Mutex.new
|
|
33
|
+
|
|
34
|
+
# @api private
|
|
35
|
+
def self.inherited(subclass)
|
|
36
|
+
super
|
|
37
|
+
|
|
38
|
+
subclass.extend(ClassMethods)
|
|
39
|
+
|
|
40
|
+
@_mutex.synchronize do
|
|
41
|
+
subclass.class_eval do
|
|
42
|
+
@_mutex = Mutex.new
|
|
43
|
+
@autoloader = Zeitwerk::Loader.new
|
|
44
|
+
@container = Class.new(Dry::System::Container)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module ClassMethods
|
|
50
|
+
# Returns the slice's parent.
|
|
51
|
+
#
|
|
52
|
+
# For top-level slices defined in `slices/` or `config/slices/`, this will be the Hanami app
|
|
53
|
+
# itself (`Hanami.app`). For nested slices, this will be the slice in which they were
|
|
54
|
+
# registered.
|
|
55
|
+
#
|
|
56
|
+
# @return [Hanami::Slice]
|
|
57
|
+
#
|
|
58
|
+
# @see #register_slice
|
|
59
|
+
#
|
|
60
|
+
# @api public
|
|
61
|
+
# @since 2.0.0
|
|
62
|
+
attr_reader :parent
|
|
63
|
+
|
|
64
|
+
# Returns the slice's autoloader.
|
|
65
|
+
#
|
|
66
|
+
# Each slice has its own `Zeitwerk::Loader` autoloader instance, which is setup when the slice
|
|
67
|
+
# is {#prepare prepared}.
|
|
68
|
+
#
|
|
69
|
+
# @return [Zeitwerk::Loader]
|
|
70
|
+
#
|
|
71
|
+
# @see https://github.com/fxn/zeitwerk
|
|
72
|
+
#
|
|
73
|
+
# @api public
|
|
74
|
+
# @since 2.0.0
|
|
75
|
+
attr_reader :autoloader
|
|
76
|
+
|
|
77
|
+
# Returns the slice's container.
|
|
78
|
+
#
|
|
79
|
+
# This is a `Dry::System::Container` that is already configured for the slice.
|
|
80
|
+
#
|
|
81
|
+
# In ordinary usage, you shouldn't need direct access the container at all, since the slice
|
|
82
|
+
# provides its own methods for interacting with the container (such as {#[]}, {#keys}, {#key?}
|
|
83
|
+
# {#register}, {#register_provider}, {#prepare}, {#start}, {#stop}).
|
|
84
|
+
#
|
|
85
|
+
# If you need to configure the container directly, use {#prepare_container}.
|
|
86
|
+
#
|
|
87
|
+
# @see https://dry-rb.org/gems/dry-system
|
|
88
|
+
#
|
|
89
|
+
# @api public
|
|
90
|
+
# @since 2.0.0
|
|
91
|
+
attr_reader :container
|
|
92
|
+
|
|
93
|
+
# Returns the Hanami app.
|
|
94
|
+
#
|
|
95
|
+
# @return [Hanami::App]
|
|
96
|
+
#
|
|
97
|
+
# @api public
|
|
98
|
+
# @since 2.0.0
|
|
99
|
+
def app
|
|
100
|
+
Hanami.app
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns true if the slice is Hanami.app
|
|
104
|
+
#
|
|
105
|
+
# @return [Boolean]
|
|
106
|
+
#
|
|
107
|
+
# @api public
|
|
108
|
+
# @since 2.2.0
|
|
109
|
+
def app?
|
|
110
|
+
eql?(app)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns the slice's config.
|
|
114
|
+
#
|
|
115
|
+
# A slice's config is copied from the app config at time of first access.
|
|
116
|
+
#
|
|
117
|
+
# @return [Hanami::Config]
|
|
118
|
+
#
|
|
119
|
+
# @see App::ClassMethods.config
|
|
120
|
+
#
|
|
121
|
+
# @api public
|
|
122
|
+
# @since 2.0.0
|
|
123
|
+
def config
|
|
124
|
+
@config ||= app.config.dup.tap do |slice_config|
|
|
125
|
+
# Unset config from app that does not apply to ordinary slices
|
|
126
|
+
slice_config.root = nil
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Evaluates the block for a given app environment only.
|
|
131
|
+
#
|
|
132
|
+
# If the given `env_name` matches {Hanami.env}, then the block will be evaluated in the
|
|
133
|
+
# context of `self` (the slice) via `instance_eval`. The slice is also passed as the block's
|
|
134
|
+
# optional argument.
|
|
135
|
+
#
|
|
136
|
+
# If the env does not match, then the block is not evaluated at all.
|
|
137
|
+
#
|
|
138
|
+
# @example
|
|
139
|
+
# module MySlice
|
|
140
|
+
# class Slice < Hanami::Slice
|
|
141
|
+
# environment(:test) do
|
|
142
|
+
# config.logger.level = :info
|
|
143
|
+
# end
|
|
144
|
+
# end
|
|
145
|
+
# end
|
|
146
|
+
#
|
|
147
|
+
# @overload environment(env_name)
|
|
148
|
+
# @param env_name [Symbol] the environment name
|
|
149
|
+
#
|
|
150
|
+
# @overload environment(env_name)
|
|
151
|
+
# @param env_name [Symbol] the environment name
|
|
152
|
+
# @yieldparam slice [self] the slice
|
|
153
|
+
#
|
|
154
|
+
# @return [self]
|
|
155
|
+
#
|
|
156
|
+
# @see Hanami.env
|
|
157
|
+
#
|
|
158
|
+
# @api public
|
|
159
|
+
# @since 2.0.0
|
|
160
|
+
def environment(env_name, &block)
|
|
161
|
+
instance_eval(&block) if env_name == config.env
|
|
162
|
+
self
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns a {SliceName} for the slice, an object with methods returning the name of the slice
|
|
166
|
+
# in various formats.
|
|
167
|
+
#
|
|
168
|
+
# @return [SliceName]
|
|
169
|
+
#
|
|
170
|
+
# @api public
|
|
171
|
+
# @since 2.0.0
|
|
172
|
+
def slice_name
|
|
173
|
+
@slice_name ||= SliceName.new(self, inflector: method(:inflector))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Returns the constant for the slice's module namespace.
|
|
177
|
+
#
|
|
178
|
+
# @example
|
|
179
|
+
# MySlice::Slice.namespace # => MySlice
|
|
180
|
+
#
|
|
181
|
+
# @return [Module] the namespace module constant
|
|
182
|
+
#
|
|
183
|
+
# @see SliceName#namespace
|
|
184
|
+
#
|
|
185
|
+
# @api public
|
|
186
|
+
# @since 2.0.0
|
|
187
|
+
def namespace
|
|
188
|
+
slice_name.namespace
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Returns the slice's root, either the root as explicitly configured, or a default fallback of
|
|
192
|
+
# the slice's name within the app's `slices/` dir.
|
|
193
|
+
#
|
|
194
|
+
# @return [Pathname]
|
|
195
|
+
#
|
|
196
|
+
# @see Config#root
|
|
197
|
+
#
|
|
198
|
+
# @api public
|
|
199
|
+
# @since 2.0.0
|
|
200
|
+
def root
|
|
201
|
+
# Provides a best guess for a root when it is not yet configured.
|
|
202
|
+
#
|
|
203
|
+
# This is particularly useful for user-defined slice classes that access `settings` inside
|
|
204
|
+
# the class body (since the root needed to find the settings file). In this case,
|
|
205
|
+
# `configuration.root` may be nil when `settings` is called, since the root is configured by
|
|
206
|
+
# `SliceRegistrar#configure_slice` _after_ the class is loaded.
|
|
207
|
+
#
|
|
208
|
+
# In common cases, this best guess will be correct since most Hanami slices will be expected
|
|
209
|
+
# to live in the app SLICES_DIR. For advanced cases, the correct slice root should be
|
|
210
|
+
# explicitly configured at the beginning of the slice class body, before any calls to
|
|
211
|
+
# `settings`.
|
|
212
|
+
config.root || app.root.join(SLICES_DIR, slice_name.to_s)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Returns the slice's root component directory, accounting for App as a special case.
|
|
216
|
+
#
|
|
217
|
+
# @return [Pathname]
|
|
218
|
+
#
|
|
219
|
+
# @api public
|
|
220
|
+
# @since 2.2.0
|
|
221
|
+
def source_path
|
|
222
|
+
app? ? root.join(APP_DIR) : root
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns the slice's root component directory, as a path relative to the app's root.
|
|
226
|
+
#
|
|
227
|
+
# @return [Pathname]
|
|
228
|
+
#
|
|
229
|
+
# @see #source_path
|
|
230
|
+
#
|
|
231
|
+
# @api public
|
|
232
|
+
# @since 2.3.0
|
|
233
|
+
def relative_source_path
|
|
234
|
+
source_path.relative_path_from(app.root)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Returns the slice's configured inflector.
|
|
238
|
+
#
|
|
239
|
+
# Unless explicitly re-configured for the slice, this will be the app's inflector.
|
|
240
|
+
#
|
|
241
|
+
# @return [Dry::Inflector]
|
|
242
|
+
#
|
|
243
|
+
# @see Config#inflector
|
|
244
|
+
# @see Config#inflections
|
|
245
|
+
#
|
|
246
|
+
# @api public
|
|
247
|
+
# @since 2.0.0
|
|
248
|
+
def inflector
|
|
249
|
+
config.inflector
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# @overload prepare
|
|
253
|
+
# Prepares the slice.
|
|
254
|
+
#
|
|
255
|
+
# This will define the slice's `Slice` and `Deps` constants, make all Ruby source files
|
|
256
|
+
# inside the slice's root dir autoloadable, as well as lazily loadable as container
|
|
257
|
+
# components.
|
|
258
|
+
#
|
|
259
|
+
# Call `prepare` when you want to access particular components within the slice while still
|
|
260
|
+
# minimizing load time. Preparing slices is the approach taken when loading the Hanami
|
|
261
|
+
# console or when running tests.
|
|
262
|
+
#
|
|
263
|
+
# @return [self]
|
|
264
|
+
#
|
|
265
|
+
# @see #boot
|
|
266
|
+
#
|
|
267
|
+
# @api public
|
|
268
|
+
# @since 2.0.0
|
|
269
|
+
#
|
|
270
|
+
# @overload prepare(provider_name)
|
|
271
|
+
# Prepares a provider.
|
|
272
|
+
#
|
|
273
|
+
# This triggers the provider's `prepare` lifecycle step.
|
|
274
|
+
#
|
|
275
|
+
# @param provider_name [Symbol] the name of the provider to start
|
|
276
|
+
#
|
|
277
|
+
# @return [self]
|
|
278
|
+
#
|
|
279
|
+
# @api public
|
|
280
|
+
# @since 2.0.0
|
|
281
|
+
def prepare(provider_name = nil)
|
|
282
|
+
if provider_name
|
|
283
|
+
container.prepare(provider_name)
|
|
284
|
+
else
|
|
285
|
+
prepare_slice
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
self
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Captures the given block to be called with the slice's container during the slice's
|
|
292
|
+
# `prepare` step, after the slice has already configured the container.
|
|
293
|
+
#
|
|
294
|
+
# This is intended for advanced usage only and should not be needed for ordinary slice
|
|
295
|
+
# configuration and usage.
|
|
296
|
+
#
|
|
297
|
+
# @example
|
|
298
|
+
# module MySlice
|
|
299
|
+
# class Slice < Hanami::Slice
|
|
300
|
+
# prepare_container do |container|
|
|
301
|
+
# # ...
|
|
302
|
+
# end
|
|
303
|
+
# end
|
|
304
|
+
# end
|
|
305
|
+
#
|
|
306
|
+
# @yieldparam container [Dry::System::Container] the slice's container
|
|
307
|
+
#
|
|
308
|
+
# @return [self]
|
|
309
|
+
#
|
|
310
|
+
# @see #prepare
|
|
311
|
+
#
|
|
312
|
+
# @api public
|
|
313
|
+
# @since 2.0.0
|
|
314
|
+
def prepare_container(&block)
|
|
315
|
+
@prepare_container_block = block
|
|
316
|
+
self
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Boots the slice.
|
|
320
|
+
#
|
|
321
|
+
# This will prepare the slice (if not already prepared), start each of its providers, register
|
|
322
|
+
# all the slice's components from its Ruby source files, and import components from any other
|
|
323
|
+
# slices. It will also boot any of the slice's own registered nested slices. It will then
|
|
324
|
+
# freeze its container so no further components can be registered.
|
|
325
|
+
#
|
|
326
|
+
# Call `boot` if you want to fully load a slice and incur all load time up front, such as when
|
|
327
|
+
# preparing an app to serve web requests. Booting slices is the approach taken when running
|
|
328
|
+
# Hanami's standard Puma setup (see `config.ru`).
|
|
329
|
+
#
|
|
330
|
+
# @return [self]
|
|
331
|
+
#
|
|
332
|
+
# @see #prepare
|
|
333
|
+
#
|
|
334
|
+
# @api public
|
|
335
|
+
# @since 2.0.0
|
|
336
|
+
def boot
|
|
337
|
+
return self if booted?
|
|
338
|
+
|
|
339
|
+
prepare
|
|
340
|
+
|
|
341
|
+
container.finalize!
|
|
342
|
+
slices.each(&:boot)
|
|
343
|
+
|
|
344
|
+
@booted = true
|
|
345
|
+
|
|
346
|
+
self
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Shuts down the slice's providers, as well as the providers in any nested slices.
|
|
350
|
+
#
|
|
351
|
+
# @return [self]
|
|
352
|
+
#
|
|
353
|
+
# @api public
|
|
354
|
+
# @since 2.0.0
|
|
355
|
+
def shutdown
|
|
356
|
+
slices.each(&:shutdown)
|
|
357
|
+
container.shutdown!
|
|
358
|
+
self
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Returns true if the slice has been prepared.
|
|
362
|
+
#
|
|
363
|
+
# @return [Boolean]
|
|
364
|
+
#
|
|
365
|
+
# @see #prepare
|
|
366
|
+
#
|
|
367
|
+
# @api public
|
|
368
|
+
# @since 2.0.0
|
|
369
|
+
def prepared?
|
|
370
|
+
!!@prepared
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Returns true if the slice has been booted.
|
|
374
|
+
#
|
|
375
|
+
# @return [Boolean]
|
|
376
|
+
#
|
|
377
|
+
# @see #boot
|
|
378
|
+
#
|
|
379
|
+
# @api public
|
|
380
|
+
# @since 2.0.0
|
|
381
|
+
def booted?
|
|
382
|
+
!!@booted
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
# Returns the slice's collection of nested slices.
|
|
386
|
+
#
|
|
387
|
+
# @return [SliceRegistrar]
|
|
388
|
+
#
|
|
389
|
+
# @see #register_slice
|
|
390
|
+
#
|
|
391
|
+
# @api public
|
|
392
|
+
# @since 2.0.0
|
|
393
|
+
def slices
|
|
394
|
+
@slices ||= SliceRegistrar.new(self)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Returns an array of this slice and all of its nested slices.
|
|
398
|
+
#
|
|
399
|
+
# The nested slices are returned first (with their more specific namespaces ahead of their
|
|
400
|
+
# parents'), and this slice is returned last. This ordering means a class can be matched to
|
|
401
|
+
# its most specific slice by detecting the first slice whose namespace it belongs to. It is
|
|
402
|
+
# useful when you need to operate across every container, such as in test support code.
|
|
403
|
+
#
|
|
404
|
+
# @example
|
|
405
|
+
# Hanami.app.with_slices # => [Main::Nested::Slice, Main::Slice, Admin::Slice, MyApp::App]
|
|
406
|
+
#
|
|
407
|
+
# @return [Array<Hanami::Slice>] this slice and all of its (nested) slices, this slice last
|
|
408
|
+
#
|
|
409
|
+
# @see #slices
|
|
410
|
+
#
|
|
411
|
+
# @api public
|
|
412
|
+
# @since 3.0.0
|
|
413
|
+
def with_slices
|
|
414
|
+
slices.with_nested + [self]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# @overload register_slice(name, &block)
|
|
418
|
+
# Registers a nested slice with the given name.
|
|
419
|
+
#
|
|
420
|
+
# This will define a new {Slice} subclass for the slice. If a block is given, it is passed
|
|
421
|
+
# the class object, and will be evaluated in the context of the class like `class_eval`.
|
|
422
|
+
#
|
|
423
|
+
# @example
|
|
424
|
+
# MySlice::Slice.register_slice do
|
|
425
|
+
# # Configure the slice or do other class-level things here
|
|
426
|
+
# end
|
|
427
|
+
#
|
|
428
|
+
# @param name [Symbol] the identifier for the slice to be registered
|
|
429
|
+
# @yieldparam slice [Hanami::Slice] the newly defined slice class
|
|
430
|
+
#
|
|
431
|
+
# @overload register_slice(name, slice_class)
|
|
432
|
+
# Registers a nested slice with the given name.
|
|
433
|
+
#
|
|
434
|
+
# The given `slice_class` will be registered as the slice. It must be a subclass of {Slice}.
|
|
435
|
+
#
|
|
436
|
+
# @param name [Symbol] the identifier for the slice to be registered
|
|
437
|
+
# @param slice_class [Hanami::Slice]
|
|
438
|
+
#
|
|
439
|
+
# @return [slices]
|
|
440
|
+
#
|
|
441
|
+
# @see SliceRegistrar#register
|
|
442
|
+
#
|
|
443
|
+
# @api public
|
|
444
|
+
# @since 2.0.0
|
|
445
|
+
def register_slice(...)
|
|
446
|
+
slices.register(...)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# Registers a component in the slice's container.
|
|
450
|
+
#
|
|
451
|
+
# @overload register(key, object)
|
|
452
|
+
# Registers the given object as the component. This same object will be returned whenever
|
|
453
|
+
# the component is resolved.
|
|
454
|
+
#
|
|
455
|
+
# @param key [String] the component's key
|
|
456
|
+
# @param object [Object] the object to register as the component
|
|
457
|
+
#
|
|
458
|
+
# @overload register(key, memoize: false, &block)
|
|
459
|
+
# Registers the given block as the component. When the component is resolved, the return
|
|
460
|
+
# value of the block will be returned.
|
|
461
|
+
#
|
|
462
|
+
# Since the block is not called until resolution-time, this is a useful way to register
|
|
463
|
+
# components that have dependencies on other components in the container, which as yet may
|
|
464
|
+
# be unavailable at the time of registration.
|
|
465
|
+
#
|
|
466
|
+
# All auto-registered components are registered in block form.
|
|
467
|
+
#
|
|
468
|
+
# When `memoize` is true, the component will be memoized upon first resolution and the same
|
|
469
|
+
# object returned on all subsequent resolutions, meaning the block is only called once.
|
|
470
|
+
# Otherwise, the block will be called and a new object returned on every resolution.
|
|
471
|
+
#
|
|
472
|
+
# @param key [String] the component's key
|
|
473
|
+
# @param memoize [Boolean]
|
|
474
|
+
# @yieldreturn [Object] the object to register as the component
|
|
475
|
+
#
|
|
476
|
+
# @overload register(key, call: true, &block)
|
|
477
|
+
# Registers the given block as the component. When `call: false` is given, then the block
|
|
478
|
+
# itself will become the component.
|
|
479
|
+
#
|
|
480
|
+
# When such a component is resolved, the block will not be called, and instead the `Proc`
|
|
481
|
+
# object for that block will be returned.
|
|
482
|
+
#
|
|
483
|
+
# @param key [String] the component's key
|
|
484
|
+
# @param call [Boolean]
|
|
485
|
+
#
|
|
486
|
+
# @return [container]
|
|
487
|
+
#
|
|
488
|
+
# @see #[]
|
|
489
|
+
# @see #resolve
|
|
490
|
+
#
|
|
491
|
+
# @api public
|
|
492
|
+
# @since 2.0.0
|
|
493
|
+
def register(...)
|
|
494
|
+
container.register(...)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
|
|
498
|
+
# Registers a provider and its lifecycle hooks.
|
|
499
|
+
#
|
|
500
|
+
# In most cases, you should call this from a dedicated file for the provider in your app or
|
|
501
|
+
# slice's `config/providers/` dir. This allows the provider to be loaded when individual
|
|
502
|
+
# matching components are resolved (for prepared slices) or when slices are booted.
|
|
503
|
+
#
|
|
504
|
+
# @example Simple provider
|
|
505
|
+
# # config/providers/db.rb
|
|
506
|
+
# Hanami.app.register_provider(:db) do
|
|
507
|
+
# start do
|
|
508
|
+
# require "db"
|
|
509
|
+
# register("db", DB.new)
|
|
510
|
+
# end
|
|
511
|
+
# end
|
|
512
|
+
#
|
|
513
|
+
# @example Provider with lifecycle steps, also using dependencies from the target container
|
|
514
|
+
# # config/providers/db.rb
|
|
515
|
+
# Hanami.app.register_provider(:db) do
|
|
516
|
+
# prepare do
|
|
517
|
+
# require "db"
|
|
518
|
+
# db = DB.new(target_container["settings"].database_url)
|
|
519
|
+
# register("db", db)
|
|
520
|
+
# end
|
|
521
|
+
#
|
|
522
|
+
# start do
|
|
523
|
+
# container["db"].establish_connection
|
|
524
|
+
# end
|
|
525
|
+
#
|
|
526
|
+
# stop do
|
|
527
|
+
# container["db"].close_connection
|
|
528
|
+
# end
|
|
529
|
+
# end
|
|
530
|
+
#
|
|
531
|
+
# @example Probvider registration under a namespace
|
|
532
|
+
# # config/providers/db.rb
|
|
533
|
+
# Hanami.app.register_provider(:persistence, namespace: true) do
|
|
534
|
+
# start do
|
|
535
|
+
# require "db"
|
|
536
|
+
#
|
|
537
|
+
# # Namespace option above means this will be registered as "persistence.db"
|
|
538
|
+
# register("db", DB.new)
|
|
539
|
+
# end
|
|
540
|
+
# end
|
|
541
|
+
#
|
|
542
|
+
# @param name [Symbol] the unique name for the provider
|
|
543
|
+
# @param namespace [Boolean, String, nil] register components from the provider with given
|
|
544
|
+
# namespace. May be an explicit string, or `true` for the namespace to be the provider's
|
|
545
|
+
# name
|
|
546
|
+
# @param from [Symbol, nil] the group for an external provider source to use, with the
|
|
547
|
+
# provider source name inferred from `name` or passed explicitly as `source:`
|
|
548
|
+
# @param source [Symbol, nil] the name of the external provider source to use, if different
|
|
549
|
+
# from the value provided as `name`
|
|
550
|
+
# @param if [Boolean] a boolean-returning expression to determine whether to register the
|
|
551
|
+
# provider
|
|
552
|
+
#
|
|
553
|
+
# @return [container]
|
|
554
|
+
#
|
|
555
|
+
# @api public
|
|
556
|
+
# @since 2.0.0
|
|
557
|
+
def register_provider(...)
|
|
558
|
+
container.register_provider(...)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# @api public
|
|
562
|
+
# @since 2.1.0
|
|
563
|
+
def configure_provider(*args, **kwargs, &block)
|
|
564
|
+
container.register_provider(*args, **kwargs, from: :hanami, &block)
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
# @overload start(provider_name)
|
|
568
|
+
# Starts a provider.
|
|
569
|
+
#
|
|
570
|
+
# This triggers the provider's `prepare` and `start` lifecycle steps.
|
|
571
|
+
#
|
|
572
|
+
# @example
|
|
573
|
+
# MySlice::Slice.start(:persistence)
|
|
574
|
+
#
|
|
575
|
+
# @param provider_name [Symbol] the name of the provider to start
|
|
576
|
+
#
|
|
577
|
+
# @return [container]
|
|
578
|
+
#
|
|
579
|
+
# @api public
|
|
580
|
+
# @since 2.0.0
|
|
581
|
+
def start(...)
|
|
582
|
+
container.start(...)
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
# @overload stop(provider_name)
|
|
586
|
+
# Stops a provider.
|
|
587
|
+
#
|
|
588
|
+
# This triggers the provider's `stop` lifecycle hook.
|
|
589
|
+
#
|
|
590
|
+
# @example
|
|
591
|
+
# MySlice::Slice.stop(:persistence)
|
|
592
|
+
#
|
|
593
|
+
# @param provider_name [Symbol] the name of the provider to start
|
|
594
|
+
#
|
|
595
|
+
# @return [container]
|
|
596
|
+
#
|
|
597
|
+
# @api public
|
|
598
|
+
# @since 2.0.0
|
|
599
|
+
def stop(...)
|
|
600
|
+
container.stop(...)
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
# @overload key?(key)
|
|
604
|
+
# Returns true if the component with the given key is registered in the container.
|
|
605
|
+
#
|
|
606
|
+
# For a prepared slice, calling `key?` will also try to load the component if not loaded
|
|
607
|
+
# already.
|
|
608
|
+
#
|
|
609
|
+
# @param key [String, Symbol] the component key
|
|
610
|
+
#
|
|
611
|
+
# @return [Boolean]
|
|
612
|
+
#
|
|
613
|
+
# @api public
|
|
614
|
+
# @since 2.0.0
|
|
615
|
+
def key?(...)
|
|
616
|
+
container.key?(...)
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
# Required for the slice to act as a provider target
|
|
620
|
+
# @api public
|
|
621
|
+
# @since 2.2.0
|
|
622
|
+
def registered?(...)
|
|
623
|
+
container.registered?(...)
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
# Returns an array of keys for all currently registered components in the container.
|
|
627
|
+
#
|
|
628
|
+
# For a prepared slice, this will be the set of components that have been previously resolved.
|
|
629
|
+
# For a booted slice, this will be all components available for the slice.
|
|
630
|
+
#
|
|
631
|
+
# @return [Array<String>]
|
|
632
|
+
#
|
|
633
|
+
# @api public
|
|
634
|
+
# @since 2.0.0
|
|
635
|
+
def keys
|
|
636
|
+
container.keys
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
# @overload [](key)
|
|
640
|
+
# Resolves the component with the given key from the container.
|
|
641
|
+
#
|
|
642
|
+
# For a prepared slice, this will attempt to load and register the matching component if it
|
|
643
|
+
# is not loaded already. For a booted slice, this will return from already registered
|
|
644
|
+
# components only.
|
|
645
|
+
#
|
|
646
|
+
# @return [Object] the resolved component's object
|
|
647
|
+
#
|
|
648
|
+
# @raise Dry::Container::KeyError if the component could not be found or loaded
|
|
649
|
+
#
|
|
650
|
+
# @see #resolve
|
|
651
|
+
#
|
|
652
|
+
# @api public
|
|
653
|
+
# @since 2.0.0
|
|
654
|
+
def [](...)
|
|
655
|
+
container.[](...)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# @see #[]
|
|
659
|
+
#
|
|
660
|
+
# @api public
|
|
661
|
+
# @since 2.0.0
|
|
662
|
+
def resolve(...)
|
|
663
|
+
container.resolve(...)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Specifies the components to export from the slice.
|
|
667
|
+
#
|
|
668
|
+
# Slices importing from this slice can import the specified components only.
|
|
669
|
+
#
|
|
670
|
+
# @example
|
|
671
|
+
# module MySlice
|
|
672
|
+
# class Slice < Hanami::Slice
|
|
673
|
+
# export ["search", "index_entity"]
|
|
674
|
+
# end
|
|
675
|
+
# end
|
|
676
|
+
#
|
|
677
|
+
# @param keys [Array<String>] the component keys to export
|
|
678
|
+
#
|
|
679
|
+
# @return [self]
|
|
680
|
+
#
|
|
681
|
+
# @api public
|
|
682
|
+
# @since 2.0.0
|
|
683
|
+
def export(keys)
|
|
684
|
+
container.config.exports = keys
|
|
685
|
+
self
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# @overload import(from:, as: nil, keys: nil)
|
|
689
|
+
# Specifies components to import from another slice.
|
|
690
|
+
#
|
|
691
|
+
# Booting a slice will register all imported components. For a prepared slice, these
|
|
692
|
+
# components will be be imported automatically when resolved.
|
|
693
|
+
#
|
|
694
|
+
# @example
|
|
695
|
+
# module MySlice
|
|
696
|
+
# class Slice < Hanami:Slice
|
|
697
|
+
# # Component from Search::Slice will import as "search.index_entity"
|
|
698
|
+
# import keys: ["index_entity"], from: :search
|
|
699
|
+
# end
|
|
700
|
+
# end
|
|
701
|
+
#
|
|
702
|
+
# @example Other import variations
|
|
703
|
+
# # Different key namespace: component will be "search_backend.index_entity"
|
|
704
|
+
# import keys: ["index_entity"], from: :search, as: "search_backend"
|
|
705
|
+
#
|
|
706
|
+
# # Import to root key namespace: component will be "index_entity"
|
|
707
|
+
# import keys: ["index_entity"], from: :search, as: nil
|
|
708
|
+
#
|
|
709
|
+
# # Import all components
|
|
710
|
+
# import from: :search
|
|
711
|
+
#
|
|
712
|
+
# @param keys [Array<String>, nil] Array of component keys to import. To import all
|
|
713
|
+
# available components, omit this argument.
|
|
714
|
+
# @param from [Symbol] name of the slice to import from
|
|
715
|
+
# @param as [Symbol, String, nil]
|
|
716
|
+
#
|
|
717
|
+
# @see #export
|
|
718
|
+
#
|
|
719
|
+
# @api public
|
|
720
|
+
# @since 2.0.0
|
|
721
|
+
def import(from:, **kwargs)
|
|
722
|
+
slice = self
|
|
723
|
+
|
|
724
|
+
container.after(:configure) do
|
|
725
|
+
if from.is_a?(Symbol) || from.is_a?(String)
|
|
726
|
+
slice_name = from
|
|
727
|
+
from = slice.parent.slices[from.to_sym].container
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
as = kwargs[:as] || slice_name
|
|
731
|
+
|
|
732
|
+
import(from: from, as: as, **kwargs)
|
|
733
|
+
end
|
|
734
|
+
end
|
|
735
|
+
|
|
736
|
+
# Returns the slice's settings, or nil if no settings are defined.
|
|
737
|
+
#
|
|
738
|
+
# You can define your settings in `config/settings.rb`.
|
|
739
|
+
#
|
|
740
|
+
# @return [Hanami::Settings, nil]
|
|
741
|
+
#
|
|
742
|
+
# @see Hanami::Settings
|
|
743
|
+
#
|
|
744
|
+
# @api public
|
|
745
|
+
# @since 2.0.0
|
|
746
|
+
def settings
|
|
747
|
+
return @settings if instance_variable_defined?(:@settings)
|
|
748
|
+
|
|
749
|
+
@settings = Settings.load_for_slice(self)
|
|
750
|
+
end
|
|
751
|
+
|
|
752
|
+
# Returns the slice's routes, or nil if no routes are defined.
|
|
753
|
+
#
|
|
754
|
+
# You can define your routes in `config/routes.rb`.
|
|
755
|
+
#
|
|
756
|
+
# @return [Hanami::Routes, nil]
|
|
757
|
+
#
|
|
758
|
+
# @see Hanami::Routes
|
|
759
|
+
#
|
|
760
|
+
# @api public
|
|
761
|
+
# @since 2.0.0
|
|
762
|
+
def routes
|
|
763
|
+
return @routes if instance_variable_defined?(:@routes)
|
|
764
|
+
|
|
765
|
+
@routes = load_routes
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
# Returns the slice's router, if or nil if no routes are defined.
|
|
769
|
+
#
|
|
770
|
+
# An optional `inspector`, implementing the `Hanami::Router::Inspector` interface, may be
|
|
771
|
+
# provided at first call (the router is then memoized for subsequent accesses). An inspector
|
|
772
|
+
# is used by the `hanami routes` CLI comment to provide a list of available routes.
|
|
773
|
+
#
|
|
774
|
+
# The returned router is a {Slice::Router}, which provides all `Hanami::Router` functionality,
|
|
775
|
+
# with the addition of support for slice mounting with the {Slice::Router#slice}.
|
|
776
|
+
#
|
|
777
|
+
# @param inspector [Hanami::Router::Inspector, nil] an optional routes inspector
|
|
778
|
+
#
|
|
779
|
+
# @return [Hanami::Slice::Router, nil]
|
|
780
|
+
#
|
|
781
|
+
# @api public
|
|
782
|
+
# @since 2.0.0
|
|
783
|
+
def router(inspector: nil)
|
|
784
|
+
raise SliceLoadError, "#{self} must be prepared before loading the router" unless prepared?
|
|
785
|
+
|
|
786
|
+
@_mutex.synchronize do
|
|
787
|
+
@_router ||= load_router(inspector: inspector)
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
# Returns a [Rack][rack] app for the slice, or nil if no routes are defined.
|
|
792
|
+
#
|
|
793
|
+
# The rack app will be memoized on first access.
|
|
794
|
+
#
|
|
795
|
+
# [rack]: https://github.com/rack/rack
|
|
796
|
+
#
|
|
797
|
+
# @return [#call, nil] the rack app, or nil if no routes are defined
|
|
798
|
+
#
|
|
799
|
+
# @see #routes
|
|
800
|
+
# @see #router
|
|
801
|
+
#
|
|
802
|
+
# @api public
|
|
803
|
+
# @since 2.0.0
|
|
804
|
+
def rack_app
|
|
805
|
+
return unless router
|
|
806
|
+
|
|
807
|
+
@rack_app ||= router.to_rack_app
|
|
808
|
+
end
|
|
809
|
+
|
|
810
|
+
# @overload call(rack_env)
|
|
811
|
+
# Calls the slice's [Rack][rack] app and returns a Rack-compatible response object
|
|
812
|
+
#
|
|
813
|
+
# [rack]: https://github.com/rack/rack
|
|
814
|
+
#
|
|
815
|
+
# @param rack_env [Hash] the Rack environment for the request
|
|
816
|
+
#
|
|
817
|
+
# @return [Array] the three-element Rack response array
|
|
818
|
+
#
|
|
819
|
+
# @raise [Hanami::NoRoutesDefinedError] if the hanami-router gem is missing or
|
|
820
|
+
# no routes are defined.
|
|
821
|
+
#
|
|
822
|
+
# @see #rack_app
|
|
823
|
+
#
|
|
824
|
+
# @api public
|
|
825
|
+
# @since 2.0.0
|
|
826
|
+
def call(...)
|
|
827
|
+
if rack_app
|
|
828
|
+
rack_app.call(...)
|
|
829
|
+
else
|
|
830
|
+
error_message = if Hanami.bundled?("hanami-router")
|
|
831
|
+
"Could not handle this rack request because no routes are defined"
|
|
832
|
+
else
|
|
833
|
+
"Could not handle this rack request because the hanami router gem is missing, please add it"
|
|
834
|
+
end
|
|
835
|
+
raise NoRoutesDefinedError, error_message
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
private
|
|
840
|
+
|
|
841
|
+
# rubocop:disable Metrics/AbcSize
|
|
842
|
+
|
|
843
|
+
def prepare_slice
|
|
844
|
+
return self if prepared?
|
|
845
|
+
|
|
846
|
+
config.finalize!
|
|
847
|
+
|
|
848
|
+
ensure_slice_name
|
|
849
|
+
ensure_slice_consts
|
|
850
|
+
ensure_root
|
|
851
|
+
|
|
852
|
+
prepare_all
|
|
853
|
+
|
|
854
|
+
instance_exec(container, &@prepare_container_block) if @prepare_container_block
|
|
855
|
+
container.configured!
|
|
856
|
+
|
|
857
|
+
prepare_autoloader
|
|
858
|
+
|
|
859
|
+
# Load child slices last, ensuring their parent is fully prepared beforehand
|
|
860
|
+
# (useful e.g. for slices that may wish to access constants defined in the
|
|
861
|
+
# parent's autoloaded directories)
|
|
862
|
+
prepare_slices
|
|
863
|
+
|
|
864
|
+
@prepared = true
|
|
865
|
+
|
|
866
|
+
self
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
def ensure_slice_name
|
|
870
|
+
unless name
|
|
871
|
+
raise SliceLoadError, "Slice must have a class name before it can be prepared"
|
|
872
|
+
end
|
|
873
|
+
end
|
|
874
|
+
|
|
875
|
+
def ensure_slice_consts
|
|
876
|
+
if namespace.const_defined?(:Container) || namespace.const_defined?(:Deps)
|
|
877
|
+
raise(
|
|
878
|
+
SliceLoadError,
|
|
879
|
+
"#{namespace}::Container and #{namespace}::Deps constants must not already be defined"
|
|
880
|
+
)
|
|
881
|
+
end
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def ensure_root
|
|
885
|
+
unless config.root
|
|
886
|
+
raise SliceLoadError, "Slice must have a `config.root` before it can be prepared"
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
def prepare_all
|
|
891
|
+
prepare_settings
|
|
892
|
+
prepare_container_consts
|
|
893
|
+
prepare_container_plugins
|
|
894
|
+
prepare_container_base_config
|
|
895
|
+
prepare_container_component_dirs
|
|
896
|
+
prepare_container_imports
|
|
897
|
+
prepare_container_providers
|
|
898
|
+
end
|
|
899
|
+
|
|
900
|
+
def prepare_settings
|
|
901
|
+
container.register(:settings, settings) if settings
|
|
902
|
+
end
|
|
903
|
+
|
|
904
|
+
def prepare_container_consts
|
|
905
|
+
namespace.const_set :Container, container
|
|
906
|
+
namespace.const_set :Deps, container.injector
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
def prepare_container_plugins
|
|
910
|
+
container.use(:env, inferrer: -> { Hanami.env })
|
|
911
|
+
|
|
912
|
+
container.use(
|
|
913
|
+
:zeitwerk,
|
|
914
|
+
loader: autoloader,
|
|
915
|
+
run_setup: false,
|
|
916
|
+
eager_load: false
|
|
917
|
+
)
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
def prepare_container_base_config
|
|
921
|
+
container.config.name = slice_name.to_sym
|
|
922
|
+
container.config.root = root
|
|
923
|
+
container.config.provider_registrar = ProviderRegistrar.for_slice(self)
|
|
924
|
+
container.config.provider_dirs = [File.join("config", "providers")]
|
|
925
|
+
container.config.registrations_dir = File.join("config", "registrations")
|
|
926
|
+
|
|
927
|
+
container.config.component_dirs.memoize = memoize_policy
|
|
928
|
+
|
|
929
|
+
container.config.env = config.env
|
|
930
|
+
container.config.inflector = config.inflector
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
def memoize_policy
|
|
934
|
+
# Do not memoize components in the test env, so they can be stubbed if required.
|
|
935
|
+
return false if config.env == :test
|
|
936
|
+
|
|
937
|
+
no_memoize = config.no_memoize
|
|
938
|
+
|
|
939
|
+
if no_memoize.respond_to?(:call)
|
|
940
|
+
->(component) { !no_memoize.call(component) }
|
|
941
|
+
elsif no_memoize.is_a?(Array) && no_memoize.any?
|
|
942
|
+
->(component) { !component.key.start_with?(*no_memoize) }
|
|
943
|
+
else
|
|
944
|
+
true
|
|
945
|
+
end
|
|
946
|
+
end
|
|
947
|
+
|
|
948
|
+
def prepare_container_component_dirs
|
|
949
|
+
return unless root.directory?
|
|
950
|
+
|
|
951
|
+
# Component files in both the root and `lib/` define classes in the slice's
|
|
952
|
+
# namespace
|
|
953
|
+
|
|
954
|
+
if root.join(LIB_DIR)&.directory?
|
|
955
|
+
container.config.component_dirs.add(LIB_DIR) do |dir|
|
|
956
|
+
dir.namespaces.add_root(key: nil, const: slice_name.name)
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# When auto-registering components in the root, ignore files in `config/` (this is
|
|
961
|
+
# for framework config only), `lib/` (these will be auto-registered as above), as
|
|
962
|
+
# well as the configured no_auto_register_paths
|
|
963
|
+
no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + config.no_auto_register_paths)
|
|
964
|
+
.map { |path|
|
|
965
|
+
path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
# TODO: Change `""` (signifying the root) once dry-rb/dry-system#238 is resolved
|
|
969
|
+
container.config.component_dirs.add("") do |dir|
|
|
970
|
+
dir.namespaces.add_root(key: nil, const: slice_name.name)
|
|
971
|
+
dir.auto_register = -> component {
|
|
972
|
+
relative_path = component.file_path.relative_path_from(root).to_s
|
|
973
|
+
!relative_path.start_with?(*no_auto_register_paths)
|
|
974
|
+
}
|
|
975
|
+
end
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
def prepare_container_imports
|
|
979
|
+
import(
|
|
980
|
+
keys: config.shared_app_component_keys,
|
|
981
|
+
from: app.container,
|
|
982
|
+
as: nil
|
|
983
|
+
)
|
|
984
|
+
end
|
|
985
|
+
|
|
986
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
987
|
+
def prepare_container_providers
|
|
988
|
+
# Check here for the `routes` definition only, not `router` itself, because the
|
|
989
|
+
# `router` requires the slice to be prepared before it can be loaded, and at this
|
|
990
|
+
# point we're still in the process of preparing.
|
|
991
|
+
if routes?
|
|
992
|
+
require_relative "providers/routes"
|
|
993
|
+
register_provider(:routes, source: Providers::Routes)
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
if assets_dir? && Hanami.bundled?("hanami-assets")
|
|
997
|
+
require_relative "providers/assets"
|
|
998
|
+
register_provider(:assets, source: Providers::Assets)
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
if Hanami.bundled?("hanami-db")
|
|
1002
|
+
# Explicit require here to ensure the provider source registers itself, to allow the user
|
|
1003
|
+
# to configure it within their own concrete provider file.
|
|
1004
|
+
require_relative "providers/db"
|
|
1005
|
+
|
|
1006
|
+
if register_db_provider?
|
|
1007
|
+
# Only register providers if the user hasn't provided their own
|
|
1008
|
+
unless container.providers[:db]
|
|
1009
|
+
register_provider(:db, namespace: true, source: Providers::DB)
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
unless container.providers[:relations]
|
|
1013
|
+
register_provider(:relations, namespace: true, source: Providers::Relations)
|
|
1014
|
+
end
|
|
1015
|
+
end
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
if Hanami.bundled?("i18n")
|
|
1019
|
+
require_relative "providers/i18n"
|
|
1020
|
+
|
|
1021
|
+
if register_i18n_provider? && !container.providers[:i18n]
|
|
1022
|
+
register_provider(:i18n, source: Providers::I18n)
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
|
|
1026
|
+
if Hanami.bundled?("hanami-mailer")
|
|
1027
|
+
# Explicit require here to ensure the provider source registers itself, to allow the
|
|
1028
|
+
# user to configure it within their own concrete provider file.
|
|
1029
|
+
require_relative "providers/mailers"
|
|
1030
|
+
|
|
1031
|
+
# Only register the provider if the user hasn't provided their own.
|
|
1032
|
+
if register_mailers_provider? && !container.providers[:mailers]
|
|
1033
|
+
register_provider(:mailers, namespace: true, source: Providers::Mailers)
|
|
1034
|
+
end
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
1037
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1038
|
+
|
|
1039
|
+
def prepare_autoloader
|
|
1040
|
+
autoloader.tag = "hanami.slices.#{slice_name}"
|
|
1041
|
+
|
|
1042
|
+
# Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
|
|
1043
|
+
# This method adds other dirs that are not otherwise configured as component dirs.
|
|
1044
|
+
|
|
1045
|
+
# Everything in the slice root can be autoloaded except `config/` and `slices/`,
|
|
1046
|
+
# which are framework-managed directories
|
|
1047
|
+
|
|
1048
|
+
if root.join(CONFIG_DIR)&.directory?
|
|
1049
|
+
autoloader.ignore(root.join(CONFIG_DIR))
|
|
1050
|
+
end
|
|
1051
|
+
|
|
1052
|
+
if root.join(SLICES_DIR)&.directory?
|
|
1053
|
+
autoloader.ignore(root.join(SLICES_DIR))
|
|
1054
|
+
end
|
|
1055
|
+
|
|
1056
|
+
autoloader.setup
|
|
1057
|
+
end
|
|
1058
|
+
|
|
1059
|
+
def prepare_slices
|
|
1060
|
+
slices.load_slices.each(&:prepare)
|
|
1061
|
+
slices.freeze
|
|
1062
|
+
end
|
|
1063
|
+
|
|
1064
|
+
def routes?
|
|
1065
|
+
return false unless Hanami.bundled?("hanami-router")
|
|
1066
|
+
|
|
1067
|
+
return true if namespace.const_defined?(ROUTES_CLASS_NAME)
|
|
1068
|
+
|
|
1069
|
+
root.join("#{ROUTES_PATH}#{RB_EXT}").file?
|
|
1070
|
+
end
|
|
1071
|
+
|
|
1072
|
+
def load_routes
|
|
1073
|
+
return false unless Hanami.bundled?("hanami-router")
|
|
1074
|
+
|
|
1075
|
+
if root.directory?
|
|
1076
|
+
routes_require_path = root.join(ROUTES_PATH).to_s
|
|
1077
|
+
|
|
1078
|
+
begin
|
|
1079
|
+
require_relative "./routes"
|
|
1080
|
+
require routes_require_path
|
|
1081
|
+
rescue LoadError => exception
|
|
1082
|
+
raise exception unless exception.path == routes_require_path
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
begin
|
|
1087
|
+
routes_class = namespace.const_get(ROUTES_CLASS_NAME)
|
|
1088
|
+
routes_class.routes
|
|
1089
|
+
rescue NameError => exception
|
|
1090
|
+
raise exception unless exception.name == ROUTES_CLASS_NAME.to_sym
|
|
1091
|
+
end
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1095
|
+
def load_router(inspector:)
|
|
1096
|
+
return unless routes
|
|
1097
|
+
|
|
1098
|
+
require_relative "slice/router"
|
|
1099
|
+
|
|
1100
|
+
slice = self
|
|
1101
|
+
config = self.config
|
|
1102
|
+
rack_monitor = self["rack.monitor"]
|
|
1103
|
+
|
|
1104
|
+
show_welcome = Hanami.env?(:development) && routes.empty?
|
|
1105
|
+
render_errors = render_errors?
|
|
1106
|
+
render_detailed_errors = render_detailed_errors?
|
|
1107
|
+
|
|
1108
|
+
error_handlers = {}.tap do |hsh|
|
|
1109
|
+
if render_errors || render_detailed_errors
|
|
1110
|
+
hsh[:not_allowed] = ROUTER_NOT_ALLOWED_HANDLER
|
|
1111
|
+
hsh[:not_found] = ROUTER_NOT_FOUND_HANDLER
|
|
1112
|
+
end
|
|
1113
|
+
end
|
|
1114
|
+
|
|
1115
|
+
Slice::Router.new(
|
|
1116
|
+
inspector: inspector,
|
|
1117
|
+
inflector: inflector,
|
|
1118
|
+
routes: routes,
|
|
1119
|
+
resolver: config.router.resolver.new(slice: self),
|
|
1120
|
+
**error_handlers,
|
|
1121
|
+
**config.router.options
|
|
1122
|
+
) do
|
|
1123
|
+
use(rack_monitor)
|
|
1124
|
+
|
|
1125
|
+
use(Hanami::Web::Welcome) if show_welcome
|
|
1126
|
+
|
|
1127
|
+
use(
|
|
1128
|
+
Hanami::Middleware::RenderErrors,
|
|
1129
|
+
config,
|
|
1130
|
+
Hanami::Middleware::PublicErrorsApp.new(slice.root.join("public"))
|
|
1131
|
+
)
|
|
1132
|
+
|
|
1133
|
+
if render_detailed_errors
|
|
1134
|
+
require "hanami/webconsole"
|
|
1135
|
+
use(Hanami::Webconsole::Middleware, config)
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1138
|
+
if Hanami.bundled?("hanami-action")
|
|
1139
|
+
if config.actions.method_override
|
|
1140
|
+
require "rack/method_override"
|
|
1141
|
+
use(Rack::MethodOverride)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
if config.actions.sessions.enabled?
|
|
1145
|
+
use(*config.actions.sessions.middleware)
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
if config.actions.content_security_policy && # rubocop:disable Style/SafeNavigation
|
|
1149
|
+
config.actions.content_security_policy.nonce?
|
|
1150
|
+
use(*config.actions.content_security_policy.middleware)
|
|
1151
|
+
end
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
if Hanami.bundled?("hanami-assets") && config.assets.serve
|
|
1155
|
+
use(Hanami::Middleware::Assets)
|
|
1156
|
+
end
|
|
1157
|
+
|
|
1158
|
+
middleware_stack.update(config.middleware_stack)
|
|
1159
|
+
end
|
|
1160
|
+
end
|
|
1161
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
1162
|
+
|
|
1163
|
+
def render_errors?
|
|
1164
|
+
config.render_errors
|
|
1165
|
+
end
|
|
1166
|
+
|
|
1167
|
+
def render_detailed_errors?
|
|
1168
|
+
config.render_detailed_errors && Hanami.bundled?("hanami-webconsole")
|
|
1169
|
+
end
|
|
1170
|
+
|
|
1171
|
+
ROUTER_NOT_ALLOWED_HANDLER = -> env, allowed_http_methods {
|
|
1172
|
+
raise Hanami::Router::NotAllowedError.new(env, allowed_http_methods)
|
|
1173
|
+
}.freeze
|
|
1174
|
+
private_constant :ROUTER_NOT_ALLOWED_HANDLER
|
|
1175
|
+
|
|
1176
|
+
ROUTER_NOT_FOUND_HANDLER = -> env {
|
|
1177
|
+
raise Hanami::Router::NotFoundError.new(env)
|
|
1178
|
+
}.freeze
|
|
1179
|
+
private_constant :ROUTER_NOT_FOUND_HANDLER
|
|
1180
|
+
|
|
1181
|
+
def assets_dir?
|
|
1182
|
+
source_path.join("assets").directory?
|
|
1183
|
+
end
|
|
1184
|
+
|
|
1185
|
+
# Ensures an i18n provider is available in every slice.
|
|
1186
|
+
#
|
|
1187
|
+
# For the app, this will always be a standalone provider. For slices, this will be a
|
|
1188
|
+
# standalone provider unless the slice is configured to share the app's "i18n" component.
|
|
1189
|
+
def register_i18n_provider?
|
|
1190
|
+
return true if self == app
|
|
1191
|
+
|
|
1192
|
+
!config.shared_app_component_keys.include?("i18n")
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
# Ensures a mailers provider is available in every slice.
|
|
1196
|
+
#
|
|
1197
|
+
# For the app, this will always be a standalone provider. For slices, this will be a
|
|
1198
|
+
# standalone provider unless the slice is configured to share the app's
|
|
1199
|
+
# "mailers.delivery_method" component.
|
|
1200
|
+
def register_mailers_provider?
|
|
1201
|
+
return true if self == app
|
|
1202
|
+
|
|
1203
|
+
!config.shared_app_component_keys.include?("mailers.delivery_method")
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
def register_db_provider?
|
|
1207
|
+
concrete_db_provider? ||
|
|
1208
|
+
db_config_dir? ||
|
|
1209
|
+
relations_dir? ||
|
|
1210
|
+
db_source_dir? ||
|
|
1211
|
+
import_db_from_parent?
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
def concrete_db_provider?
|
|
1215
|
+
root.join(CONFIG_DIR, "providers", "db.rb").exist?
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
def db_config_dir?
|
|
1219
|
+
root.join("config", "db").directory?
|
|
1220
|
+
end
|
|
1221
|
+
|
|
1222
|
+
def relations_dir?
|
|
1223
|
+
source_path.join("relations").directory?
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
def db_source_dir?
|
|
1227
|
+
source_path.join("db").directory?
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
def import_db_from_parent?
|
|
1231
|
+
parent &&
|
|
1232
|
+
config.db.import_from_parent &&
|
|
1233
|
+
parent.container.providers[:db]
|
|
1234
|
+
end
|
|
1235
|
+
|
|
1236
|
+
# rubocop:enable Metrics/AbcSize
|
|
1237
|
+
end
|
|
1238
|
+
end
|
|
1239
|
+
end
|