hanami 2.0.0.beta3 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/hanami.gemspec +9 -8
  4. data/lib/hanami/app.rb +50 -39
  5. data/lib/hanami/assets/app_config.rb +61 -0
  6. data/lib/hanami/assets/{configuration.rb → config.rb} +9 -10
  7. data/lib/hanami/{configuration → config}/actions/content_security_policy.rb +3 -3
  8. data/lib/hanami/config/actions/cookies.rb +57 -0
  9. data/lib/hanami/config/actions/sessions.rb +83 -0
  10. data/lib/hanami/config/actions.rb +164 -0
  11. data/lib/hanami/config/logger.rb +176 -0
  12. data/lib/hanami/config/null_config.rb +14 -0
  13. data/lib/hanami/{configuration → config}/router.rb +8 -9
  14. data/lib/hanami/{configuration → config}/views.rb +16 -20
  15. data/lib/hanami/config.rb +396 -0
  16. data/lib/hanami/constants.rb +4 -0
  17. data/lib/hanami/errors.rb +20 -0
  18. data/lib/hanami/extensions/action/slice_configured_action.rb +10 -6
  19. data/lib/hanami/extensions/action.rb +59 -7
  20. data/lib/hanami/extensions/view/context.rb +3 -4
  21. data/lib/hanami/extensions/view/slice_configured_view.rb +4 -4
  22. data/lib/hanami/extensions/view.rb +7 -5
  23. data/lib/hanami/providers/inflector.rb +6 -2
  24. data/lib/hanami/providers/logger.rb +9 -3
  25. data/lib/hanami/providers/rack.rb +12 -2
  26. data/lib/hanami/providers/routes.rb +14 -6
  27. data/lib/hanami/routes.rb +36 -1
  28. data/lib/hanami/settings/env_store.rb +4 -4
  29. data/lib/hanami/settings.rb +169 -21
  30. data/lib/hanami/slice/router.rb +38 -16
  31. data/lib/hanami/slice/routing/middleware/stack.rb +108 -40
  32. data/lib/hanami/slice/routing/resolver.rb +10 -17
  33. data/lib/hanami/slice/view_name_inferrer.rb +1 -1
  34. data/lib/hanami/slice.rb +605 -51
  35. data/lib/hanami/slice_configurable.rb +1 -1
  36. data/lib/hanami/slice_registrar.rb +25 -14
  37. data/lib/hanami/version.rb +2 -3
  38. data/lib/hanami/web/rack_logger.rb +14 -4
  39. data/lib/hanami.rb +122 -24
  40. data/spec/integration/action/csrf_protection_spec.rb +1 -1
  41. data/spec/integration/container/application_routes_helper_spec.rb +3 -1
  42. data/spec/integration/container/prepare_container_spec.rb +2 -0
  43. data/spec/integration/container/provider_lifecycle_spec.rb +61 -0
  44. data/spec/integration/container/standard_providers/rack_provider_spec.rb +44 -0
  45. data/spec/integration/container/{standard_bootable_components_spec.rb → standard_providers_spec.rb} +3 -3
  46. data/spec/integration/rack_app/body_parser_spec.rb +111 -0
  47. data/spec/integration/rack_app/middleware_spec.rb +455 -3
  48. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +2 -1
  49. data/spec/integration/rack_app/rack_app_spec.rb +39 -11
  50. data/spec/integration/settings/access_in_slice_class_body_spec.rb +82 -0
  51. data/spec/integration/settings/access_to_constants_spec.rb +23 -146
  52. data/spec/integration/{slices/slice_settings_spec.rb → settings/slice_registration_spec.rb} +5 -1
  53. data/spec/integration/settings/using_types_spec.rb +4 -11
  54. data/spec/integration/setup_spec.rb +4 -4
  55. data/spec/integration/slices/external_slice_spec.rb +2 -1
  56. data/spec/integration/slices/slice_configuration_spec.rb +3 -1
  57. data/spec/integration/slices/slice_loading_spec.rb +4 -4
  58. data/spec/integration/slices/slice_routing_spec.rb +4 -3
  59. data/spec/integration/slices_spec.rb +100 -0
  60. data/spec/isolation/hanami/boot/success_spec.rb +1 -1
  61. data/spec/support/app_integration.rb +10 -15
  62. data/spec/unit/hanami/{configuration → config}/actions/content_security_policy_spec.rb +16 -16
  63. data/spec/unit/hanami/{configuration → config}/actions/cookies_spec.rb +6 -6
  64. data/spec/unit/hanami/{configuration → config}/actions/csrf_protection_spec.rb +12 -12
  65. data/spec/unit/hanami/config/actions/default_values_spec.rb +54 -0
  66. data/spec/unit/hanami/{configuration → config}/actions/sessions_spec.rb +6 -8
  67. data/spec/unit/hanami/{configuration → config}/actions_spec.rb +8 -20
  68. data/spec/unit/hanami/{configuration → config}/base_url_spec.rb +2 -2
  69. data/spec/unit/hanami/{configuration → config}/inflector_spec.rb +2 -2
  70. data/spec/unit/hanami/{configuration → config}/logger_spec.rb +42 -59
  71. data/spec/unit/hanami/{configuration → config}/router_spec.rb +7 -8
  72. data/spec/unit/hanami/{configuration → config}/slices_spec.rb +2 -2
  73. data/spec/unit/hanami/{configuration → config}/views_spec.rb +13 -24
  74. data/spec/unit/hanami/settings_spec.rb +65 -10
  75. data/spec/unit/hanami/slice_configurable_spec.rb +21 -2
  76. data/spec/unit/hanami/slice_spec.rb +32 -0
  77. data/spec/unit/hanami/version_spec.rb +1 -1
  78. data/spec/unit/hanami/web/rack_logger_spec.rb +13 -2
  79. metadata +100 -76
  80. data/lib/hanami/assets/app_configuration.rb +0 -69
  81. data/lib/hanami/configuration/actions/cookies.rb +0 -29
  82. data/lib/hanami/configuration/actions/sessions.rb +0 -46
  83. data/lib/hanami/configuration/actions.rb +0 -101
  84. data/lib/hanami/configuration/logger.rb +0 -87
  85. data/lib/hanami/configuration/null_configuration.rb +0 -14
  86. data/lib/hanami/configuration/sessions.rb +0 -50
  87. data/lib/hanami/configuration.rb +0 -234
  88. data/lib/hanami/providers/settings.rb +0 -98
  89. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +0 -52
  90. data/spec/unit/hanami/configuration_spec.rb +0 -43
data/lib/hanami/slice.rb CHANGED
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/system/container"
4
3
  require "zeitwerk"
4
+ require "dry/system"
5
+
5
6
  require_relative "constants"
6
7
  require_relative "errors"
7
- require_relative "slice_name"
8
- require_relative "slice_registrar"
9
- require_relative "providers/settings"
10
8
 
11
9
  module Hanami
12
10
  # A slice represents any distinct area of concern within an Hanami app.
@@ -17,12 +15,11 @@ module Hanami
17
15
  # Each slice corresponds a single module namespace and a single root directory of source
18
16
  # files for loading as components into its container.
19
17
  #
20
- # Each slice has its own configuration, and may optionally have its own settings,
21
- # routes, as well as other nested slices.
18
+ # Each slice has its own config, and may optionally have its own settings, routes, as well as
19
+ # other nested slices.
22
20
  #
23
- # Slices expect an Hanami app to be defined (which itself is a slice). They will
24
- # initialize their configuration as a copy of the app's, and will also configure
25
- # certain components
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
26
23
  #
27
24
  # Slices must be _prepared_ and optionally _booted_ before they can be used (see
28
25
  # {ClassMethods.prepare} and {ClassMethods.boot}). A prepared slice will lazily load its
@@ -34,6 +31,7 @@ module Hanami
34
31
  class Slice
35
32
  @_mutex = Mutex.new
36
33
 
34
+ # @api private
37
35
  def self.inherited(subclass)
38
36
  super
39
37
 
@@ -50,51 +48,260 @@ module Hanami
50
48
 
51
49
  # rubocop:disable Metrics/ModuleLength
52
50
  module ClassMethods
53
- attr_reader :parent, :autoloader, :container
54
-
51
+ # Returns the slice's parent.
52
+ #
53
+ # For top-level slices defined in `slices/` or `config/slices/`, this will be the Hanami app
54
+ # itself (`Hanami.app`). For nested slices, this will be the slice in which they were
55
+ # registered.
56
+ #
57
+ # @return [Hanami::Slice]
58
+ #
59
+ # @see #register_slice
60
+ #
61
+ # @api public
62
+ # @since 2.0.0
63
+ attr_reader :parent
64
+
65
+ # Returns the slice's autoloader.
66
+ #
67
+ # Each slice has its own `Zeitwerk::Loader` autoloader instance, which is setup when the slice
68
+ # is {#prepare prepared}.
69
+ #
70
+ # @return [Zeitwerk::Loader]
71
+ #
72
+ # @see https://github.com/fxn/zeitwerk
73
+ #
74
+ # @api public
75
+ # @since 2.0.0
76
+ attr_reader :autoloader
77
+
78
+ # Returns the slice's container.
79
+ #
80
+ # This is a `Dry::System::Container` that is already configured for the slice.
81
+ #
82
+ # In ordinary usage, you shouldn't need direct access the container at all, since the slice
83
+ # provides its own methods for interacting with the container (such as {#[]}, {#keys}, {#key?}
84
+ # {#register}, {#register_provider}, {#prepare}, {#start}, {#stop}).
85
+ #
86
+ # If you need to configure the container directly, use {#prepare_container}.
87
+ #
88
+ # @see https://dry-rb.org/gems/dry-system
89
+ #
90
+ # @api public
91
+ # @since 2.0.0
92
+ attr_reader :container
93
+
94
+ # Returns the Hanami app.
95
+ #
96
+ # @return [Hanami::App]
97
+ #
98
+ # @api public
99
+ # @since 2.0.0
55
100
  def app
56
101
  Hanami.app
57
102
  end
58
103
 
59
- # A slice's configuration is copied from the app configuration at time of first access. The
60
- # app should have its configuration completed before slices are loaded.
61
- def configuration
62
- @configuration ||= app.configuration.dup.tap do |config|
63
- # Remove specific values from app that will not apply to this slice
64
- config.root = nil
104
+ # Returns the slice's config.
105
+ #
106
+ # A slice's config is copied from the app config at time of first access.
107
+ #
108
+ # @return [Hanami::Config]
109
+ #
110
+ # @see App::ClassMethods.config
111
+ #
112
+ # @api public
113
+ # @since 2.0.0
114
+ def config
115
+ @config ||= app.config.dup.tap do |slice_config|
116
+ # Unset config from app that does not apply to ordinary slices
117
+ slice_config.root = nil
65
118
  end
66
119
  end
67
- alias_method :config, :configuration
68
120
 
121
+ # Evaluates the block for a given app environment only.
122
+ #
123
+ # If the given `env_name` matches {Hanami.env}, then the block will be evaluated in the
124
+ # context of `self` (the slice) via `instance_eval`. The slice is also passed as the block's
125
+ # optional argument.
126
+ #
127
+ # If the env does not match, then the block is not evaluated at all.
128
+ #
129
+ # @example
130
+ # module MySlice
131
+ # class Slice < Hanami::Slice
132
+ # environment(:test) do
133
+ # config.logger.level = :info
134
+ # end
135
+ # end
136
+ # end
137
+ #
138
+ # @overload environment(env_name)
139
+ # @param env_name [Symbol] the environment name
140
+ #
141
+ # @overload environment(env_name)
142
+ # @param env_name [Symbol] the environment name
143
+ # @yieldparam slice [self] the slice
144
+ #
145
+ # @return [self]
146
+ #
147
+ # @see Hanami.env
148
+ #
149
+ # @api public
150
+ # @since 2.0.0
151
+ def environment(env_name, &block)
152
+ instance_eval(&block) if env_name == config.env
153
+ self
154
+ end
155
+
156
+ # Returns a {SliceName} for the slice, an object with methods returning the name of the slice
157
+ # in various formats.
158
+ #
159
+ # @return [SliceName]
160
+ #
161
+ # @api public
162
+ # @since 2.0.0
69
163
  def slice_name
70
164
  @slice_name ||= SliceName.new(self, inflector: method(:inflector))
71
165
  end
72
166
 
167
+ # Returns the constant for the slice's module namespace.
168
+ #
169
+ # @example
170
+ # MySlice::Slice.namespace # => MySlice
171
+ #
172
+ # @return [Module] the namespace module constant
173
+ #
174
+ # @see SliceName#namespace
175
+ #
176
+ # @api public
177
+ # @since 2.0.0
73
178
  def namespace
74
179
  slice_name.namespace
75
180
  end
76
181
 
182
+ # Returns the slice's root, either the root as explicitly configured, or a default fallback of
183
+ # the slice's name within the app's `slices/` dir.
184
+ #
185
+ # @return [Pathname]
186
+ #
187
+ # @see Config#root
188
+ #
189
+ # @api public
190
+ # @since 2.0.0
77
191
  def root
78
- configuration.root
79
- end
80
-
192
+ # Provide a best guess for a root when it is not yet configured.
193
+ #
194
+ # This is particularly useful for user-defined slice classes that access `settings` inside
195
+ # the class body (since the root needed to find the settings file). In this case,
196
+ # `configuration.root` may be nil when `settings` is called, since the root is configured by
197
+ # `SliceRegistrar#configure_slice` _after_ the class is loaded.
198
+ #
199
+ # In common cases, this best guess will be correct since most Hanami slices will be expected
200
+ # to live in the app SLICES_DIR. For advanced cases, the correct slice root should be
201
+ # explicitly configured at the beginning of the slice class body, before any calls to
202
+ # `settings`.
203
+ config.root || app.root.join(SLICES_DIR, slice_name.to_s)
204
+ end
205
+
206
+ # Returns the slice's configured inflector.
207
+ #
208
+ # Unless explicitly re-configured for the slice, this will be the app's inflector.
209
+ #
210
+ # @return [Dry::Inflector]
211
+ #
212
+ # @see Config#inflector
213
+ # @see Config#inflections
214
+ #
215
+ # @api public
216
+ # @since 2.0.0
81
217
  def inflector
82
- configuration.inflector
83
- end
84
-
218
+ config.inflector
219
+ end
220
+
221
+ # @overload prepare
222
+ # Prepares the slice.
223
+ #
224
+ # This will define the slice's `Slice` and `Deps` constants, make all Ruby source files
225
+ # inside the slice's root dir autoloadable, as well as lazily loadable as container
226
+ # components.
227
+ #
228
+ # Call `prepare` when you want to access particular components within the slice while still
229
+ # minimizing load time. Preparing slices is the approach taken when loading the Hanami
230
+ # console or when running tests.
231
+ #
232
+ # @return [self]
233
+ #
234
+ # @see #boot
235
+ #
236
+ # @api public
237
+ # @since 2.0.0
238
+ #
239
+ # @overload prepare(provider_name)
240
+ # Prepares a provider.
241
+ #
242
+ # This triggers the provider's `prepare` lifecycle step.
243
+ #
244
+ # @param provider_name [Symbol] the name of the provider to start
245
+ #
246
+ # @return [self]
247
+ #
248
+ # @api public
249
+ # @since 2.0.0
85
250
  def prepare(provider_name = nil)
86
251
  if provider_name
87
252
  container.prepare(provider_name)
88
- self
89
253
  else
90
254
  prepare_slice
91
255
  end
256
+
257
+ self
92
258
  end
93
259
 
260
+ # Captures the given block to be called with the slice's container during the slice's
261
+ # `prepare` step, after the slice has already configured the container.
262
+ #
263
+ # This is intended for advanced usage only and should not be needed for ordinary slice
264
+ # configuration and usage.
265
+ #
266
+ # @example
267
+ # module MySlice
268
+ # class Sliice < Hanami::Slice
269
+ # prepare_container do |container|
270
+ # # ...
271
+ # end
272
+ # end
273
+ # end
274
+ #
275
+ # @yieldparam container [Dry::System::Container] the slice's container
276
+ #
277
+ # @return [self]
278
+ #
279
+ # @see #prepare
280
+ #
281
+ # @api public
282
+ # @since 2.0.0
94
283
  def prepare_container(&block)
95
284
  @prepare_container_block = block
285
+ self
96
286
  end
97
287
 
288
+ # Boots the slice.
289
+ #
290
+ # This will prepare the slice (if not already prepared), start each of its providers, register
291
+ # all the slice's components from its Ruby source files, and import components from any other
292
+ # slices. It will also boot any of the slice's own registered nested slices. It will then
293
+ # freeze its container so no further components can be registered.
294
+ #
295
+ # Call `boot` if you want to fully load a slice and incur all load time up front, such as when
296
+ # preparing an app to serve web requests. Booting slices is the approach taken when running
297
+ # Hanami's standard Puma setup (see `config.ru`).
298
+ #
299
+ # @return [self]
300
+ #
301
+ # @see #prepare
302
+ #
303
+ # @api public
304
+ # @since 2.0.0
98
305
  def boot
99
306
  return self if booted?
100
307
 
@@ -108,64 +315,346 @@ module Hanami
108
315
  self
109
316
  end
110
317
 
318
+ # Shuts down the slice's providers, as well as the providers in any nested slices.
319
+ #
320
+ # @return [self]
321
+ #
322
+ # @api public
323
+ # @since 2.0.0
111
324
  def shutdown
112
325
  slices.each(&:shutdown)
113
326
  container.shutdown!
114
327
  self
115
328
  end
116
329
 
330
+ # Returns true if the slice has been prepared.
331
+ #
332
+ # @return [Boolean]
333
+ #
334
+ # @see #prepare
335
+ #
336
+ # @api public
337
+ # @since 2.0.0
117
338
  def prepared?
118
339
  !!@prepared
119
340
  end
120
341
 
342
+ # Returns true if the slice has been booted.
343
+ #
344
+ # @return [Boolean]
345
+ #
346
+ # @see #boot
347
+ #
348
+ # @api public
349
+ # @since 2.0.0
121
350
  def booted?
122
351
  !!@booted
123
352
  end
124
353
 
354
+ # Returns the slice's collection of nested slices.
355
+ #
356
+ # @return [SliceRegistrar]
357
+ #
358
+ # @see #register_slice
359
+ #
360
+ # @api public
361
+ # @since 2.0.0
125
362
  def slices
126
363
  @slices ||= SliceRegistrar.new(self)
127
364
  end
128
365
 
366
+ # @overload register_slice(name, &block)
367
+ # Registers a nested slice with the given name.
368
+ #
369
+ # This will define a new {Slice} subclass for the slice. If a block is given, it is passed
370
+ # the class object, and will be evaluated in the context of the class like `class_eval`.
371
+ #
372
+ # @example
373
+ # MySlice::Slice.register_slice do
374
+ # # Configure the slice or do other class-level things here
375
+ # end
376
+ #
377
+ # @param name [Symbol] the identifier for the slice to be registered
378
+ # @yieldparam slice [Hanami::Slice] the newly defined slice class
379
+ #
380
+ # @overload register_slice(name, slice_class)
381
+ # Registers a nested slice with the given name.
382
+ #
383
+ # The given `slice_class` will be registered as the slice. It must be a subclass of {Slice}.
384
+ #
385
+ # @param name [Symbol] the identifier for the slice to be registered
386
+ # @param slice_class [Hanami::Slice]
387
+ #
388
+ # @return [slices]
389
+ #
390
+ # @see SliceRegistrar#register
391
+ #
392
+ # @api public
393
+ # @since 2.0.0
129
394
  def register_slice(...)
130
395
  slices.register(...)
131
396
  end
132
397
 
398
+ # Registers a component in the slice's container.
399
+ #
400
+ # @overload register(key, object)
401
+ # Registers the given object as the component. This same object will be returned whenever
402
+ # the component is resolved.
403
+ #
404
+ # @param key [String] the component's key
405
+ # @param object [Object] the object to register as the component
406
+ #
407
+ # @overload reigster(key, memoize: false, &block)
408
+ # Registers the given block as the component. When the component is resolved, the return
409
+ # value of the block will be returned.
410
+ #
411
+ # Since the block is not called until resolution-time, this is a useful way to register
412
+ # components that have dependencies on other components in the container, which as yet may
413
+ # be unavailable at the time of registration.
414
+ #
415
+ # All auto-registered components are registered in block form.
416
+ #
417
+ # When `memoize` is true, the component will be memoized upon first resolution and the same
418
+ # object returned on all subsequent resolutions, meaning the block is only called once.
419
+ # Otherwise, the block will be called and a new object returned on every resolution.
420
+ #
421
+ # @param key [String] the component's key
422
+ # @param memoize [Boolean]
423
+ # @yieldreturn [Object] the object to register as the component
424
+ #
425
+ # @overload reigster(key, call: true, &block)
426
+ # Registers the given block as the component. When `call: false` is given, then the block
427
+ # itself will become the component.
428
+ #
429
+ # When such a component is resolved, the block will not be called, and instead the `Proc`
430
+ # object for that block will be returned.
431
+ #
432
+ # @param key [String] the component's key
433
+ # @param call [Booelan]
434
+ #
435
+ # @return [container]
436
+ #
437
+ # @see #[]
438
+ # @see #resolve
439
+ #
440
+ # @api public
441
+ # @since 2.0.0
133
442
  def register(...)
134
443
  container.register(...)
135
444
  end
136
445
 
446
+ # @overload register_provider(name, namespace: nil, from: nil, source: nil, if: true, &block)
447
+ # Registers a provider and its lifecycle hooks.
448
+ #
449
+ # In most cases, you should call this from a dedicated file for the provider in your app or
450
+ # slice's `config/providers/` dir. This allows the provider to be loaded when individual
451
+ # matching components are resolved (for prepared slices) or when slices are booted.
452
+ #
453
+ # @example Simple provider
454
+ # # config/providers/db.rb
455
+ # Hanami.app.register_provider(:db) do
456
+ # start do
457
+ # require "db"
458
+ # register("db", DB.new)
459
+ # end
460
+ # end
461
+ #
462
+ # @example Provider with lifecycle steps, also using dependencies from the target container
463
+ # # config/providers/db.rb
464
+ # Hanami.app.register_provider(:db) do
465
+ # prepare do
466
+ # require "db"
467
+ # db = DB.new(target_container["settings"].database_url)
468
+ # register("db", db)
469
+ # end
470
+ #
471
+ # start do
472
+ # container["db"].establish_connection
473
+ # end
474
+ #
475
+ # stop do
476
+ # container["db"].close_connection
477
+ # end
478
+ # end
479
+ #
480
+ # @example Probvider registration under a namespace
481
+ # # config/providers/db.rb
482
+ # Hanami.app.register_provider(:persistence, namespace: true) do
483
+ # start do
484
+ # require "db"
485
+ #
486
+ # # Namespace option above means this will be registered as "persistence.db"
487
+ # register("db", DB.new)
488
+ # end
489
+ # end
490
+ #
491
+ # @param name [Symbol] the unique name for the provider
492
+ # @param namespace [Boolean, String, nil] register components from the provider with given
493
+ # namespace. May be an explicit string, or `true` for the namespace to be the provider's
494
+ # name
495
+ # @param from [Symbol, nil] the group for an external provider source to use, with the
496
+ # provider source name inferred from `name` or passsed explicitly as `source:`
497
+ # @param source [Symbol, nil] the name of the external provider source to use, if different
498
+ # from the value provided as `name`
499
+ # @param if [Boolean] a boolean-returning expression to determine whether to register the
500
+ # provider
501
+ #
502
+ # @return [container]
503
+ #
504
+ # @api public
505
+ # @since 2.0.0
137
506
  def register_provider(...)
138
507
  container.register_provider(...)
139
508
  end
140
509
 
510
+ # @overload start(provider_name)
511
+ # Starts a provider.
512
+ #
513
+ # This triggers the provider's `prepare` and `start` lifecycle steps.
514
+ #
515
+ # @example
516
+ # MySlice::Slice.start(:persistence)
517
+ #
518
+ # @param provider_name [Symbol] the name of the provider to start
519
+ #
520
+ # @return [container]
521
+ #
522
+ # @api public
523
+ # @since 2.0.0
141
524
  def start(...)
142
525
  container.start(...)
143
526
  end
144
527
 
528
+ # @overload stop(provider_name)
529
+ # Stops a provider.
530
+ #
531
+ # This triggers the provider's `stop` lifecycle hook.
532
+ #
533
+ # @example
534
+ # MySlice::Slice.stop(:persistence)
535
+ #
536
+ # @param provider_name [Symbol] the name of the provider to start
537
+ #
538
+ # @return [container]
539
+ #
540
+ # @api public
541
+ # @since 2.0.0
542
+ def stop(...)
543
+ container.stop(...)
544
+ end
545
+
546
+ # @overload key?(key)
547
+ # Returns true if the component with the given key is registered in the container.
548
+ #
549
+ # For a prepared slice, calling `key?` will also try to load the component if not loaded
550
+ # already.
551
+ #
552
+ # @param key [String, Symbol] the component key
553
+ #
554
+ # @return [Boolean]
555
+ #
556
+ # @api public
557
+ # @since 2.0.0
145
558
  def key?(...)
146
559
  container.key?(...)
147
560
  end
148
561
 
562
+ # Returns an array of keys for all currently registered components in the container.
563
+ #
564
+ # For a prepared slice, this will be the set of components that have been previously resolved.
565
+ # For a booted slice, this will be all components available for the slice.
566
+ #
567
+ # @return [Array<String>]
568
+ #
569
+ # @api public
570
+ # @since 2.0.0
149
571
  def keys
150
572
  container.keys
151
573
  end
152
574
 
575
+ # @overload [](key)
576
+ # Resolves the component with the given key from the container.
577
+ #
578
+ # For a prepared slice, this will attempt to load and register the matching component if it
579
+ # is not loaded already. For a booted slice, this will return from already registered
580
+ # components only.
581
+ #
582
+ # @return [Object] the resolved component's object
583
+ #
584
+ # @raise Dry::Container::KeyError if the component could not be found or loaded
585
+ #
586
+ # @see #resolve
587
+ #
588
+ # @api public
589
+ # @since 2.0.0
153
590
  def [](...)
154
591
  container.[](...)
155
592
  end
156
593
 
594
+ # @see #[]
595
+ #
596
+ # @api public
597
+ # @since 2.0.0
157
598
  def resolve(...)
158
599
  container.resolve(...)
159
600
  end
160
601
 
602
+ # Specifies the components to export from the slice.
603
+ #
604
+ # Slices importing from this slice can import the specified components only.
605
+ #
606
+ # @example
607
+ # module MySlice
608
+ # class Slice < Hanami::Slice
609
+ # export ["search", "index_entity"]
610
+ # end
611
+ # end
612
+ #
613
+ # @param keys [Array<String>] the component keys to export
614
+ #
615
+ # @return [self]
616
+ #
617
+ # @api public
618
+ # @since 2.0.0
161
619
  def export(keys)
162
620
  container.config.exports = keys
621
+ self
163
622
  end
164
623
 
624
+ # @overload import(from:, as: nil, keys: nil)
625
+ # Specifies components to import from another slice.
626
+ #
627
+ # Booting a slice will register all imported components. For a prepared slice, these
628
+ # components will be be imported automatically when resolved.
629
+ #
630
+ # @example
631
+ # module MySlice
632
+ # class Slice < Hanami:Slice
633
+ # # Component from Search::Slice will import as "search.index_entity"
634
+ # import keys: ["index_entity"], from: :search
635
+ # end
636
+ # end
637
+ #
638
+ # @example Other import variations
639
+ # # Different key namespace: component will be "search_backend.index_entity"
640
+ # import keys: ["index_entity"], from: :search, as: "search_backend"
641
+ #
642
+ # # Import to root key namespace: component will be "index_entity"
643
+ # import keys: ["index_entity"], from: :search, as: nil
644
+ #
645
+ # # Import all components
646
+ # import from: :search
647
+ #
648
+ # @param keys [Array<String>, nil] Array of component keys to import. To import all
649
+ # available components, omit this argument.
650
+ # @param from [Symbol] name of the slice to import from
651
+ # @param as [Symbol, String, nil]
652
+ #
653
+ # @see #export
654
+ #
655
+ # @api public
656
+ # @since 2.0.0
165
657
  def import(from:, **kwargs)
166
- # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
167
- raise "Cannot import after booting" if booted?
168
-
169
658
  slice = self
170
659
 
171
660
  container.after(:configure) do
@@ -180,10 +669,51 @@ module Hanami
180
669
  end
181
670
  end
182
671
 
672
+ # Returns the slice's settings, or nil if no settings are defined.
673
+ #
674
+ # You can define your settings in `config/settings.rb`.
675
+ #
676
+ # @return [Hanami::Settings, nil]
677
+ #
678
+ # @see Hanami::Settings
679
+ #
680
+ # @api public
681
+ # @since 2.0.0
682
+ def settings
683
+ return @settings if instance_variable_defined?(:@settings)
684
+
685
+ @settings = Settings.load_for_slice(self)
686
+ end
687
+
688
+ # Returns the slice's routes, or nil if no routes are defined.
689
+ #
690
+ # You can define your routes in `config/routes.rb`.
691
+ #
692
+ # @return [Hanami::Routes, nil]
693
+ #
694
+ # @see Hanami::Routes
695
+ #
696
+ # @api public
697
+ # @since 2.0.0
183
698
  def routes
184
699
  @routes ||= load_routes
185
700
  end
186
701
 
702
+ # Returns the slice's router, if or nil if no routes are defined.
703
+ #
704
+ # An optional `inspector`, implementing the `Hanami::Router::Inspector` interface, may be
705
+ # provided at first call (the router is then memoized for subsequent accesses). An inspector
706
+ # is used by the `hanami routes` CLI comment to provide a list of available routes.
707
+ #
708
+ # The returned router is a {Slice::Router}, which provides all `Hanami::Router` functionality,
709
+ # with the addition of support for slice mounting with the {Slice::Router#slice}.
710
+ #
711
+ # @param inspector [Hanami::Router::Inspector, nil] an optional routes inspector
712
+ #
713
+ # @return [Hanami::Slice::Router, nil]
714
+ #
715
+ # @api public
716
+ # @since 2.0.0
187
717
  def router(inspector: nil)
188
718
  raise SliceLoadError, "#{self} must be prepared before loading the router" unless prepared?
189
719
 
@@ -192,12 +722,38 @@ module Hanami
192
722
  end
193
723
  end
194
724
 
725
+ # Returns a [Rack][rack] app for the slice, or nil if no routes are defined.
726
+ #
727
+ # The rack app will be memoized on first access.
728
+ #
729
+ # [rack]: https://github.com/rack/rack
730
+ #
731
+ # @return [#call, nil] the rack app, or nil if no routes are defined
732
+ #
733
+ # @see #routes
734
+ # @see #router
735
+ #
736
+ # @api public
737
+ # @since 2.0.0
195
738
  def rack_app
196
739
  return unless router
197
740
 
198
741
  @rack_app ||= router.to_rack_app
199
742
  end
200
743
 
744
+ # @overload call(rack_env)
745
+ # Calls the slice's [Rack][rack] app and returns a Rack-compatible response object
746
+ #
747
+ # [rack]: https://github.com/rack/rack
748
+ #
749
+ # @param rack_env [Hash] the Rack environment for the request
750
+ #
751
+ # @return [Array] the three-element Rack response array
752
+ #
753
+ # @see #rack_app
754
+ #
755
+ # @api public
756
+ # @since 2.0.0
201
757
  def call(...)
202
758
  rack_app.call(...)
203
759
  end
@@ -209,7 +765,7 @@ module Hanami
209
765
  def prepare_slice
210
766
  return self if prepared?
211
767
 
212
- configuration.finalize!
768
+ config.finalize!
213
769
 
214
770
  ensure_slice_name
215
771
  ensure_slice_consts
@@ -222,8 +778,6 @@ module Hanami
222
778
 
223
779
  prepare_autoloader
224
780
 
225
- ensure_prepared
226
-
227
781
  # Load child slices last, ensuring their parent is fully prepared beforehand
228
782
  # (useful e.g. for slices that may wish to access constants defined in the
229
783
  # parent's autoloaded directories)
@@ -250,17 +804,13 @@ module Hanami
250
804
  end
251
805
 
252
806
  def ensure_root
253
- unless configuration.root
807
+ unless config.root
254
808
  raise SliceLoadError, "Slice must have a `config.root` before it can be prepared"
255
809
  end
256
810
  end
257
811
 
258
- def ensure_prepared
259
- # Load settings so we can fail early in case of non-conformant values
260
- self[:settings] if key?(:settings)
261
- end
262
-
263
812
  def prepare_all
813
+ prepare_settings
264
814
  prepare_container_consts
265
815
  prepare_container_plugins
266
816
  prepare_container_base_config
@@ -269,6 +819,15 @@ module Hanami
269
819
  prepare_container_providers
270
820
  end
271
821
 
822
+ def prepare_settings
823
+ container.register(:settings, settings) if settings
824
+ end
825
+
826
+ def prepare_container_consts
827
+ namespace.const_set :Container, container
828
+ namespace.const_set :Deps, container.injector
829
+ end
830
+
272
831
  def prepare_container_plugins
273
832
  container.use(:env, inferrer: -> { Hanami.env })
274
833
 
@@ -285,8 +844,8 @@ module Hanami
285
844
  container.config.root = root
286
845
  container.config.provider_dirs = [File.join("config", "providers")]
287
846
 
288
- container.config.env = configuration.env
289
- container.config.inflector = configuration.inflector
847
+ container.config.env = config.env
848
+ container.config.inflector = config.inflector
290
849
  end
291
850
 
292
851
  def prepare_container_component_dirs
@@ -304,7 +863,7 @@ module Hanami
304
863
  # When auto-registering components in the root, ignore files in `config/` (this is
305
864
  # for framework config only), `lib/` (these will be auto-registered as above), as
306
865
  # well as the configured no_auto_register_paths
307
- no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + configuration.no_auto_register_paths)
866
+ no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + config.no_auto_register_paths)
308
867
  .map { |path|
309
868
  path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
310
869
  }
@@ -335,8 +894,6 @@ module Hanami
335
894
  require_relative "providers/routes"
336
895
  register_provider(:routes, source: Providers::Routes.for_slice(self))
337
896
  end
338
-
339
- Providers::Settings.register_with_slice(self)
340
897
  end
341
898
 
342
899
  def prepare_autoloader
@@ -358,17 +915,14 @@ module Hanami
358
915
  autoloader.setup
359
916
  end
360
917
 
361
- def prepare_container_consts
362
- namespace.const_set :Container, container
363
- namespace.const_set :Deps, container.injector
364
- end
365
-
366
918
  def prepare_slices
367
919
  slices.load_slices.each(&:prepare)
368
920
  slices.freeze
369
921
  end
370
922
 
371
923
  def load_routes
924
+ return false unless Hanami.bundled?("hanami-router")
925
+
372
926
  if root.directory?
373
927
  routes_require_path = File.join(root, ROUTES_PATH)
374
928
 
@@ -393,17 +947,17 @@ module Hanami
393
947
 
394
948
  require_relative "slice/router"
395
949
 
396
- config = configuration
950
+ config = self.config
397
951
  rack_monitor = self["rack.monitor"]
398
952
 
399
953
  Slice::Router.new(
400
954
  inspector: inspector,
401
955
  routes: routes,
402
- resolver: configuration.router.resolver.new(slice: self),
403
- **configuration.router.options
956
+ resolver: config.router.resolver.new(slice: self),
957
+ **config.router.options
404
958
  ) do
405
959
  use(rack_monitor)
406
- use(*config.sessions.middleware) if config.sessions.enabled?
960
+ use(*config.actions.sessions.middleware) if config.actions.sessions.enabled?
407
961
 
408
962
  middleware_stack.update(config.middleware_stack)
409
963
  end