hanami 2.0.0.beta1.1 → 2.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/hanami.gemspec +1 -2
  4. data/lib/hanami/app.rb +76 -16
  5. data/lib/hanami/assets/{application_configuration.rb → app_configuration.rb} +1 -1
  6. data/lib/hanami/configuration/null_configuration.rb +2 -2
  7. data/lib/hanami/configuration.rb +36 -36
  8. data/lib/hanami/extensions/action/slice_configured_action.rb +44 -1
  9. data/lib/hanami/extensions/view/slice_configured_view.rb +47 -7
  10. data/lib/hanami/providers/rack.rb +2 -0
  11. data/lib/hanami/providers/settings.rb +81 -6
  12. data/lib/hanami/routes.rb +48 -21
  13. data/lib/hanami/settings/env_store.rb +32 -0
  14. data/lib/hanami/settings.rb +8 -12
  15. data/lib/hanami/setup.rb +1 -6
  16. data/lib/hanami/slice/routing/middleware/stack.rb +26 -5
  17. data/lib/hanami/slice.rb +38 -45
  18. data/lib/hanami/slice_configurable.rb +14 -1
  19. data/lib/hanami/slice_registrar.rb +65 -5
  20. data/lib/hanami/version.rb +1 -1
  21. data/lib/hanami.rb +53 -2
  22. data/spec/{new_integration → integration}/action/cookies_spec.rb +0 -0
  23. data/spec/{new_integration → integration}/action/csrf_protection_spec.rb +0 -0
  24. data/spec/{new_integration → integration}/action/routes_spec.rb +3 -5
  25. data/spec/{new_integration → integration}/action/sessions_spec.rb +0 -0
  26. data/spec/integration/action/slice_configuration_spec.rb +287 -0
  27. data/spec/{new_integration → integration}/action/view_integration_spec.rb +0 -0
  28. data/spec/{new_integration → integration}/action/view_rendering/automatic_rendering_spec.rb +0 -0
  29. data/spec/{new_integration → integration}/action/view_rendering/paired_view_inference_spec.rb +0 -0
  30. data/spec/{new_integration → integration}/action/view_rendering_spec.rb +0 -0
  31. data/spec/{new_integration → integration}/code_loading/loading_from_app_spec.rb +0 -0
  32. data/spec/integration/code_loading/loading_from_lib_spec.rb +208 -0
  33. data/spec/{new_integration → integration}/code_loading/loading_from_slice_spec.rb +0 -0
  34. data/spec/{new_integration → integration}/container/application_routes_helper_spec.rb +1 -3
  35. data/spec/{new_integration → integration}/container/auto_injection_spec.rb +0 -0
  36. data/spec/{new_integration → integration}/container/auto_registration_spec.rb +0 -0
  37. data/spec/{new_integration → integration}/container/autoloader_spec.rb +0 -0
  38. data/spec/{new_integration → integration}/container/imports_spec.rb +0 -0
  39. data/spec/{new_integration → integration}/container/prepare_container_spec.rb +0 -0
  40. data/spec/{new_integration → integration}/container/shutdown_spec.rb +0 -0
  41. data/spec/{new_integration → integration}/container/standard_bootable_components_spec.rb +0 -0
  42. data/spec/integration/dotenv_loading_spec.rb +137 -0
  43. data/spec/{new_integration → integration}/rack_app/middleware_spec.rb +9 -15
  44. data/spec/{new_integration → integration}/rack_app/non_booted_rack_app_spec.rb +3 -5
  45. data/spec/{new_integration → integration}/rack_app/rack_app_spec.rb +28 -48
  46. data/spec/integration/settings/access_to_constants_spec.rb +169 -0
  47. data/spec/integration/settings/loading_from_env_spec.rb +187 -0
  48. data/spec/integration/settings/settings_component_loading_spec.rb +113 -0
  49. data/spec/integration/settings/using_types_spec.rb +87 -0
  50. data/spec/integration/setup_spec.rb +165 -0
  51. data/spec/{new_integration → integration}/slices/external_slice_spec.rb +2 -4
  52. data/spec/{new_integration → integration}/slices/slice_configuration_spec.rb +0 -0
  53. data/spec/integration/slices/slice_loading_spec.rb +171 -0
  54. data/spec/{new_integration → integration}/slices/slice_routing_spec.rb +5 -13
  55. data/spec/{new_integration → integration}/slices/slice_settings_spec.rb +0 -0
  56. data/spec/{new_integration → integration}/slices_spec.rb +0 -0
  57. data/spec/{new_integration → integration}/view/context/assets_spec.rb +0 -0
  58. data/spec/{new_integration → integration}/view/context/inflector_spec.rb +0 -0
  59. data/spec/{new_integration → integration}/view/context/request_spec.rb +0 -0
  60. data/spec/{new_integration → integration}/view/context/routes_spec.rb +1 -3
  61. data/spec/{new_integration → integration}/view/context/settings_spec.rb +5 -1
  62. data/spec/{new_integration → integration}/view/inflector_spec.rb +0 -0
  63. data/spec/{new_integration → integration}/view/part_namespace_spec.rb +0 -0
  64. data/spec/{new_integration → integration}/view/path_spec.rb +0 -0
  65. data/spec/integration/view/slice_configuration_spec.rb +289 -0
  66. data/spec/{new_integration → integration}/view/template_spec.rb +0 -0
  67. data/spec/{new_integration → integration}/view/views_spec.rb +0 -0
  68. data/spec/support/app_integration.rb +4 -5
  69. data/spec/unit/hanami/configuration/actions_spec.rb +4 -15
  70. data/spec/unit/hanami/configuration/router_spec.rb +45 -0
  71. data/spec/unit/hanami/configuration/slices_spec.rb +34 -0
  72. data/spec/unit/hanami/configuration/views_spec.rb +4 -15
  73. data/spec/unit/hanami/settings/env_store_spec.rb +52 -0
  74. data/spec/unit/hanami/slice_configurable_spec.rb +2 -2
  75. data/spec/unit/hanami/version_spec.rb +1 -1
  76. metadata +105 -250
  77. data/lib/hanami/server.rb +0 -29
  78. data/lib/hanami/settings/dotenv_store.rb +0 -58
  79. data/spec/integration/application_middleware_stack_spec.rb +0 -84
  80. data/spec/integration/assets/cdn_spec.rb +0 -48
  81. data/spec/integration/assets/fingerprint_spec.rb +0 -42
  82. data/spec/integration/assets/helpers_spec.rb +0 -50
  83. data/spec/integration/assets/serve_spec.rb +0 -70
  84. data/spec/integration/assets/subresource_integrity_spec.rb +0 -54
  85. data/spec/integration/body_parsers_spec.rb +0 -50
  86. data/spec/integration/cli/assets/precompile_spec.rb +0 -147
  87. data/spec/integration/cli/assets_spec.rb +0 -14
  88. data/spec/integration/cli/console_spec.rb +0 -105
  89. data/spec/integration/cli/db/apply_spec.rb +0 -74
  90. data/spec/integration/cli/db/console_spec.rb +0 -40
  91. data/spec/integration/cli/db/create_spec.rb +0 -50
  92. data/spec/integration/cli/db/drop_spec.rb +0 -54
  93. data/spec/integration/cli/db/migrate_spec.rb +0 -108
  94. data/spec/integration/cli/db/prepare_spec.rb +0 -36
  95. data/spec/integration/cli/db/rollback_spec.rb +0 -96
  96. data/spec/integration/cli/db/version_spec.rb +0 -38
  97. data/spec/integration/cli/db_spec.rb +0 -21
  98. data/spec/integration/cli/destroy/action_spec.rb +0 -143
  99. data/spec/integration/cli/destroy/app_spec.rb +0 -118
  100. data/spec/integration/cli/destroy/mailer_spec.rb +0 -74
  101. data/spec/integration/cli/destroy/migration_spec.rb +0 -70
  102. data/spec/integration/cli/destroy/model_spec.rb +0 -113
  103. data/spec/integration/cli/destroy_spec.rb +0 -18
  104. data/spec/integration/cli/generate/action_spec.rb +0 -469
  105. data/spec/integration/cli/generate/app_spec.rb +0 -215
  106. data/spec/integration/cli/generate/mailer_spec.rb +0 -189
  107. data/spec/integration/cli/generate/migration_spec.rb +0 -72
  108. data/spec/integration/cli/generate/model_spec.rb +0 -290
  109. data/spec/integration/cli/generate/secret_spec.rb +0 -56
  110. data/spec/integration/cli/generate_spec.rb +0 -19
  111. data/spec/integration/cli/new/database_spec.rb +0 -235
  112. data/spec/integration/cli/new/hanami_head_spec.rb +0 -27
  113. data/spec/integration/cli/new/template_spec.rb +0 -118
  114. data/spec/integration/cli/new/test_spec.rb +0 -274
  115. data/spec/integration/cli/new_spec.rb +0 -970
  116. data/spec/integration/cli/plugins_spec.rb +0 -39
  117. data/spec/integration/cli/routes_spec.rb +0 -49
  118. data/spec/integration/cli/server_spec.rb +0 -626
  119. data/spec/integration/cli/version_spec.rb +0 -85
  120. data/spec/integration/early_hints_spec.rb +0 -35
  121. data/spec/integration/handle_exceptions_spec.rb +0 -244
  122. data/spec/integration/head_spec.rb +0 -89
  123. data/spec/integration/http_headers_spec.rb +0 -29
  124. data/spec/integration/mailer_spec.rb +0 -32
  125. data/spec/integration/middleware_spec.rb +0 -81
  126. data/spec/integration/mount_applications_spec.rb +0 -88
  127. data/spec/integration/project_initializers_spec.rb +0 -40
  128. data/spec/integration/rackup_spec.rb +0 -35
  129. data/spec/integration/rake/with_minitest_spec.rb +0 -67
  130. data/spec/integration/rake/with_rspec_spec.rb +0 -69
  131. data/spec/integration/routing_helpers_spec.rb +0 -61
  132. data/spec/integration/security/content_security_policy_spec.rb +0 -46
  133. data/spec/integration/security/csrf_protection_spec.rb +0 -42
  134. data/spec/integration/security/force_ssl_spec.rb +0 -29
  135. data/spec/integration/security/x_content_type_options_spec.rb +0 -46
  136. data/spec/integration/security/x_frame_options_spec.rb +0 -46
  137. data/spec/integration/security/x_xss_protection_spec.rb +0 -46
  138. data/spec/integration/send_file_spec.rb +0 -51
  139. data/spec/integration/sessions_spec.rb +0 -247
  140. data/spec/integration/static_middleware_spec.rb +0 -21
  141. data/spec/integration/streaming_spec.rb +0 -41
  142. data/spec/integration/unsafe_send_file_spec.rb +0 -52
  143. data/spec/new_integration/action/configuration_spec.rb +0 -26
  144. data/spec/new_integration/settings_spec.rb +0 -115
  145. data/spec/new_integration/view/configuration_spec.rb +0 -49
  146. data/spec/support/fixtures/hanami-plugin/Gemfile +0 -8
  147. data/spec/support/fixtures/hanami-plugin/README.md +0 -35
  148. data/spec/support/fixtures/hanami-plugin/Rakefile +0 -4
  149. data/spec/support/fixtures/hanami-plugin/bin/console +0 -15
  150. data/spec/support/fixtures/hanami-plugin/bin/setup +0 -8
  151. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +0 -28
  152. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +0 -19
  153. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +0 -7
  154. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +0 -8
  155. data/spec/unit/hanami/routes_spec.rb +0 -25
  156. data/spec/unit/hanami/settings/dotenv_store_spec.rb +0 -119
data/lib/hanami/routes.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "hanami/slice/router"
4
+
3
5
  module Hanami
4
6
  # App routes
5
7
  #
@@ -14,38 +16,63 @@ module Hanami
14
16
  #
15
17
  # module MyApp
16
18
  # class Routes < Hanami::Routes
17
- # define do
18
- # root to: "home.show"
19
- # end
19
+ # root to: "home.show"
20
20
  # end
21
21
  # end
22
22
  #
23
- # See {Hanami::Slice::Router} for the syntax allowed within the
24
- # `define` block.
23
+ # See {Hanami::Slice::Router} for the syntax allowed within the `define` block.
25
24
  #
26
25
  # @see Hanami::Slice::Router
27
26
  # @since 2.0.0
28
27
  class Routes
29
- # Defines app routes
30
- #
31
- # @yield DSL syntax to define app routes executed in the context
32
- # of {Hanami::Slice::Router}
33
- #
34
- # @return [Proc]
35
- def self.define(&block)
36
- @_routes = block
37
- end
38
-
39
28
  # @api private
40
29
  def self.routes
41
- @_routes || raise(<<~MSG)
42
- Routes need to be defined before being able to fetch them. E.g.,
43
- define do
44
- slice :main, at: "/" do
45
- root to: "home.show"
30
+ @routes ||= build_routes
31
+ end
32
+
33
+ class << self
34
+ # @api private
35
+ def build_routes(definitions = self.definitions)
36
+ return if definitions.empty?
37
+
38
+ proc do
39
+ definitions.each do |(name, args, kwargs, block)|
40
+ if block
41
+ public_send(name, *args, **kwargs, &block)
42
+ else
43
+ public_send(name, *args, **kwargs)
46
44
  end
47
45
  end
48
- MSG
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def definitions
51
+ @definitions ||= []
52
+ end
53
+
54
+ private
55
+
56
+ # @api private
57
+ def supported_methods
58
+ @supported_methods ||= Slice::Router.public_instance_methods
59
+ end
60
+
61
+ # @api private
62
+ def respond_to_missing?(name, include_private = false)
63
+ supported_methods.include?(name) || super
64
+ end
65
+
66
+ # Capture all method calls that are supported by the router DSL
67
+ # so that it can be evaluated lazily during configuration/boot
68
+ # process
69
+ #
70
+ # @api private
71
+ def method_missing(name, *args, **kwargs, &block)
72
+ return super unless respond_to?(name)
73
+ definitions << [name, args, kwargs, block]
74
+ self
75
+ end
49
76
  end
50
77
  end
51
78
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/constants"
4
+
5
+ module Hanami
6
+ class Settings
7
+ # The default store for {Hanami:Settings}, loading setting values from `ENV`.
8
+ #
9
+ # If your app loads the dotenv gem, then `ENV` will also be populated from various `.env` files when
10
+ # you subclass `Hanami::App`.
11
+ #
12
+ # @since 2.0.0
13
+ # @api private
14
+ class EnvStore
15
+ Undefined = Dry::Core::Constants::Undefined
16
+
17
+ attr_reader :store, :hanami_env
18
+
19
+ def initialize(store: ENV, hanami_env: Hanami.env)
20
+ @store = store
21
+ @hanami_env = hanami_env
22
+ end
23
+
24
+ def fetch(name, default_value = Undefined, &block)
25
+ name = name.to_s.upcase
26
+ args = default_value == Undefined ? [name] : [name, default_value]
27
+
28
+ store.fetch(*args, &block)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -22,26 +22,22 @@ module Hanami
22
22
  # end
23
23
  # end
24
24
  #
25
- # Settings are defined with
26
- # [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you can
27
- # take a look there to see the supported syntax.
25
+ # Settings are defined with [dry-configurable](https://dry-rb.org/gems/dry-configurable/), so you
26
+ # can take a look there to see the supported syntax.
28
27
  #
29
- # Users work with an instance of this class made available within the
30
- # `settings` key in the container. The instance gets its settings populated
31
- # from a configurable store, which defaults to
32
- # {Hanami::Settings::DotenvStore}.
28
+ # Users work with an instance of this class made available within the `settings` key in the
29
+ # container. The instance gets its settings populated from a configurable store, which defaults to
30
+ # {Hanami::Settings::EnvStore}.
33
31
  #
34
- # A different store can be set through the `settings_store` Hanami
35
- # configuration option. All it needs to do is implementing a `#fetch` method
36
- # with the same signature as `Hash#fetch`.
32
+ # A different store can be set through the `settings_store` Hanami configuration option. All it
33
+ # needs to do is implementing a `#fetch` method with the same signature as `Hash#fetch`.
37
34
  #
38
35
  # @see Hanami::Settings::DotenvStore
39
36
  # @since 2.0.0
40
37
  class Settings
41
38
  # Exception for errors in the definition of settings.
42
39
  #
43
- # Its message collects all the individual errors that can be raised for
44
- # each setting.
40
+ # Its message collects all the individual errors that can be raised for each setting.
45
41
  InvalidSettingsError = Class.new(StandardError) do
46
42
  def initialize(errors)
47
43
  @errors = errors
data/lib/hanami/setup.rb CHANGED
@@ -3,9 +3,4 @@
3
3
  require "bundler/setup"
4
4
  require "hanami"
5
5
 
6
- begin
7
- app_require_path = File.join(Dir.pwd, "config/app")
8
- require app_require_path
9
- rescue LoadError => e
10
- raise e unless e.path == app_require_path
11
- end
6
+ Hanami.setup
@@ -1,20 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rack/builder"
4
-
5
3
  module Hanami
6
4
  class Slice
7
5
  module Routing
8
- # Hanami::Applicatione::Router middleware stack
9
- #
10
6
  # @since 2.0.0
11
7
  # @api private
12
8
  module Middleware
13
- # Middleware stack
9
+ # Wraps a rack app with a middleware stack
10
+ #
11
+ # We use this class to add middlewares to the rack application generated
12
+ # from {Hanami::Slice::Router}.
13
+ #
14
+ # ```
15
+ # stack = Hanami::Slice::Routing::Middleware::Stack.new
16
+ # stack.use(Rack::ContentType, "text/html")
17
+ # stack.to_rack_app(a_rack_app)
18
+ # ```
19
+ #
20
+ # Middlewares can be mounted on specific paths:
21
+ #
22
+ # ```
23
+ # stack.with("/api") do
24
+ # stack.use(Rack::ContentType, "application/json")
25
+ # end
26
+ # ```
14
27
  #
15
28
  # @since 2.0.0
16
29
  # @api private
17
30
  class Stack
31
+ include Enumerable
32
+
18
33
  # @since 2.0.0
19
34
  # @api private
20
35
  ROOT_PREFIX = "/"
@@ -77,6 +92,12 @@ module Hanami
77
92
  # @since 2.0.0
78
93
  # @api private
79
94
  def to_rack_app(app)
95
+ unless Hanami.bundled?("rack")
96
+ raise "Add \"rack\" to your `Gemfile` to run Hanami as a rack app"
97
+ end
98
+
99
+ require "rack/builder"
100
+
80
101
  s = self
81
102
 
82
103
  Rack::Builder.new do
data/lib/hanami/slice.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
+ require "zeitwerk"
4
5
  require_relative "constants"
5
6
  require_relative "errors"
6
7
  require_relative "slice_name"
7
8
  require_relative "slice_registrar"
9
+ require_relative "providers/settings"
8
10
 
9
11
  module Hanami
10
12
  # A slice represents any distinct area of concern within an Hanami app.
@@ -40,6 +42,7 @@ module Hanami
40
42
  @_mutex.synchronize do
41
43
  subclass.class_eval do
42
44
  @_mutex = Mutex.new
45
+ @autoloader = Zeitwerk::Loader.new
43
46
  @container = Class.new(Dry::System::Container)
44
47
  end
45
48
  end
@@ -47,15 +50,14 @@ module Hanami
47
50
 
48
51
  # rubocop:disable Metrics/ModuleLength
49
52
  module ClassMethods
50
- attr_reader :parent, :container
53
+ attr_reader :parent, :autoloader, :container
51
54
 
52
55
  def app
53
56
  Hanami.app
54
57
  end
55
58
 
56
- # A slice's configuration is copied from the app configuration at time of
57
- # first access. The app should have its configuration completed before
58
- # slices are loaded.
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.
59
61
  def configuration
60
62
  @configuration ||= app.configuration.dup.tap do |config|
61
63
  # Remove specific values from app that will not apply to this slice
@@ -178,10 +180,6 @@ module Hanami
178
180
  end
179
181
  end
180
182
 
181
- def settings
182
- @settings ||= load_settings
183
- end
184
-
185
183
  def routes
186
184
  @routes ||= load_routes
187
185
  end
@@ -222,6 +220,15 @@ module Hanami
222
220
  instance_exec(container, &@prepare_container_block) if @prepare_container_block
223
221
  container.configured!
224
222
 
223
+ prepare_autoloader
224
+
225
+ ensure_prepared
226
+
227
+ # Load child slices last, ensuring their parent is fully prepared beforehand
228
+ # (useful e.g. for slices that may wish to access constants defined in the
229
+ # parent's autoloaded directories)
230
+ prepare_slices
231
+
225
232
  @prepared = true
226
233
 
227
234
  self
@@ -248,19 +255,18 @@ module Hanami
248
255
  end
249
256
  end
250
257
 
251
- def prepare_all
252
- # Load settings first, to fail early in case of missing/unexpected values
253
- settings
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
254
262
 
263
+ def prepare_all
255
264
  prepare_container_consts
256
265
  prepare_container_plugins
257
266
  prepare_container_base_config
258
267
  prepare_container_component_dirs
259
268
  prepare_container_imports
260
269
  prepare_container_providers
261
- prepare_autoloader
262
-
263
- prepare_slices
264
270
  end
265
271
 
266
272
  def prepare_container_plugins
@@ -268,7 +274,7 @@ module Hanami
268
274
 
269
275
  container.use(
270
276
  :zeitwerk,
271
- loader: app.autoloader,
277
+ loader: autoloader,
272
278
  run_setup: false,
273
279
  eager_load: false
274
280
  )
@@ -309,14 +315,13 @@ module Hanami
309
315
  dir.auto_register = -> component {
310
316
  relative_path = component.file_path.relative_path_from(root).to_s
311
317
  !relative_path.start_with?(*no_auto_register_paths)
312
-
313
318
  }
314
319
  end
315
320
  end
316
321
 
317
322
  def prepare_container_imports
318
323
  import(
319
- keys: config.slices.shared_component_keys,
324
+ keys: config.shared_app_component_keys,
320
325
  from: app.container,
321
326
  as: nil
322
327
  )
@@ -328,21 +333,29 @@ module Hanami
328
333
  # point we're still in the process of preparing.
329
334
  if routes
330
335
  require_relative "providers/routes"
331
- register_provider(:routes, source: Hanami::Providers::Routes.for_slice(self))
336
+ register_provider(:routes, source: Providers::Routes.for_slice(self))
332
337
  end
333
338
 
334
- if settings
335
- require_relative "providers/settings"
336
- register_provider(:settings, source: Hanami::Providers::Settings.for_slice(self))
337
- end
339
+ Providers::Settings.register_with_slice(self)
338
340
  end
339
341
 
340
342
  def prepare_autoloader
341
- # Everything in the slice directory can be autoloaded _except_ `config/`, which is
342
- # where we keep files loaded specially by the framework as part of slice setup.
343
+ # Component dirs are automatically pushed to the autoloader by dry-system's
344
+ # zeitwerk plugin. This method adds other dirs that are not otherwise configured
345
+ # as component dirs.
346
+
347
+ # Everything in the slice root can be autoloaded except `config/` and `slices/`,
348
+ # which are framework-managed directories
349
+
343
350
  if root.join(CONFIG_DIR)&.directory?
344
- container.config.autoloader.ignore(root.join(CONFIG_DIR))
351
+ autoloader.ignore(root.join(CONFIG_DIR))
352
+ end
353
+
354
+ if root.join(SLICES_DIR)&.directory?
355
+ autoloader.ignore(root.join(SLICES_DIR))
345
356
  end
357
+
358
+ autoloader.setup
346
359
  end
347
360
 
348
361
  def prepare_container_consts
@@ -355,26 +368,6 @@ module Hanami
355
368
  slices.freeze
356
369
  end
357
370
 
358
- def load_settings
359
- if root.directory?
360
- settings_require_path = File.join(root, SETTINGS_PATH)
361
-
362
- begin
363
- require_relative "./settings"
364
- require settings_require_path
365
- rescue LoadError => e
366
- raise e unless e.path == settings_require_path
367
- end
368
- end
369
-
370
- begin
371
- settings_class = namespace.const_get(SETTINGS_CLASS_NAME)
372
- settings_class.new(configuration.settings_store)
373
- rescue NameError => e
374
- raise e unless e.name == SETTINGS_CLASS_NAME.to_sym
375
- end
376
- end
377
-
378
371
  def load_routes
379
372
  if root.directory?
380
373
  routes_require_path = File.join(root, ROUTES_PATH)
@@ -36,10 +36,15 @@ module Hanami
36
36
 
37
37
  super(subclass)
38
38
 
39
+ subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)
40
+
39
41
  slice = slice_for.(subclass)
40
42
  return unless slice
41
43
 
42
- subclass.configure_for_slice(slice)
44
+ unless subclass.configured_for_slice?(slice)
45
+ subclass.configure_for_slice(slice)
46
+ subclass.configured_for_slices << slice # WIP
47
+ end
43
48
  end
44
49
  end
45
50
 
@@ -58,5 +63,13 @@ module Hanami
58
63
  end
59
64
 
60
65
  def configure_for_slice(slice); end
66
+
67
+ def configured_for_slice?(slice)
68
+ configured_for_slices.include?(slice)
69
+ end
70
+
71
+ def configured_for_slices
72
+ @configured_for_slices ||= []
73
+ end
61
74
  end
62
75
  end
@@ -6,6 +6,8 @@ require_relative "slice"
6
6
  module Hanami
7
7
  # @api private
8
8
  class SliceRegistrar
9
+ SLICE_DELIMITER = CONTAINER_KEY_DELIMITER
10
+
9
11
  attr_reader :parent, :slices
10
12
  private :parent, :slices
11
13
 
@@ -15,6 +17,8 @@ module Hanami
15
17
  end
16
18
 
17
19
  def register(name, slice_class = nil, &block)
20
+ return unless filter_slice_names([name]).any?
21
+
18
22
  if slices.key?(name.to_sym)
19
23
  raise SliceLoadError, "Slice '#{name}' is already registered"
20
24
  end
@@ -49,7 +53,10 @@ module Hanami
49
53
  .select { |path| File.directory?(path) }
50
54
  .map { |path| File.basename(path) }
51
55
 
52
- (slice_dirs + slice_configs).uniq.sort.each do |slice_name|
56
+ slice_names = (slice_dirs + slice_configs).uniq.sort
57
+ .then { filter_slice_names(_1) }
58
+
59
+ slice_names.each do |slice_name|
53
60
  load_slice(slice_name)
54
61
  end
55
62
 
@@ -60,12 +67,24 @@ module Hanami
60
67
  slices.each_value(&block)
61
68
  end
62
69
 
70
+ def keys
71
+ slices.keys
72
+ end
73
+
63
74
  def to_a
64
75
  slices.values
65
76
  end
66
77
 
67
78
  private
68
79
 
80
+ def root
81
+ parent.root
82
+ end
83
+
84
+ def inflector
85
+ parent.inflector
86
+ end
87
+
69
88
  # Runs when a slice file has been found at `config/slices/[slice_name].rb`, or a slice
70
89
  # directory at `slices/[slice_name]`. Attempts to require the slice class, if defined,
71
90
  # or generates a new slice class for the given slice name.
@@ -106,14 +125,55 @@ module Hanami
106
125
 
107
126
  # Slices require a root, so provide a sensible default based on the slice's parent
108
127
  slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s)
128
+
129
+ slice.config.slices = child_slice_names(slice_name, parent.config.slices)
109
130
  end
110
131
 
111
- def root
112
- parent.root
132
+ # Returns a filtered array of slice names based on the parent's `config.slices`
133
+ #
134
+ # This works with both singular slice names (e.g. `"admin"`) as well as dot-delimited nested
135
+ # slice names (e.g. `"admin.shop"`).
136
+ #
137
+ # It will consider only the base names of the slices (since in this case, a parent slice must be
138
+ # loaded in order for its children to be loaded).
139
+ #
140
+ # @example
141
+ # parent.config.slices # => ["admin.shop"]
142
+ # filter_slice_names(["admin", "main"]) # => ["admin"]
143
+ #
144
+ # parent.config.slices # => ["admin"]
145
+ # filter_slice_names(["admin", "main"]) # => ["admin"]
146
+ def filter_slice_names(slice_names)
147
+ slice_names = slice_names.map(&:to_s)
148
+
149
+ if parent.config.slices
150
+ slice_names & parent.config.slices.map { base_slice_name(_1) }
151
+ else
152
+ slice_names
153
+ end
113
154
  end
114
155
 
115
- def inflector
116
- parent.inflector
156
+ # Returns the base slice name from an (optionally) dot-delimited nested slice name.
157
+ #
158
+ # @example
159
+ # base_slice_name("admin") # => "admin"
160
+ # base_slice_name("admin.users") # => "admin"
161
+ def base_slice_name(name)
162
+ name.to_s.split(SLICE_DELIMITER).first
163
+ end
164
+
165
+ # Returns an array of slice names specific to the given child slice.
166
+ #
167
+ # @example
168
+ # child_local_slice_names("admin", ["main", "admin.users"]) # => ["users"]
169
+ def child_slice_names(parent_slice_name, slice_names)
170
+ slice_names
171
+ &.select { |name|
172
+ name.include?(SLICE_DELIMITER) && name.split(SLICE_DELIMITER)[0] == parent_slice_name.to_s
173
+ }
174
+ &.map { |name|
175
+ name.split(SLICE_DELIMITER)[1..].join(SLICE_DELIMITER) # which version of Ruby supports this?
176
+ }
117
177
  end
118
178
  end
119
179
  end
@@ -8,7 +8,7 @@ module Hanami
8
8
  module Version
9
9
  # @since 0.9.0
10
10
  # @api private
11
- VERSION = "2.0.0.beta1.1"
11
+ VERSION = "2.0.0.beta3"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
data/lib/hanami.rb CHANGED
@@ -9,6 +9,57 @@ module Hanami
9
9
  @_mutex = Mutex.new
10
10
  @_bundled = {}
11
11
 
12
+ # Finds and loads the Hanami app file (`config/app.rb`).
13
+ #
14
+ # Raises an exception if the app file cannot be found.
15
+ #
16
+ # @return [Hanami::App] the loaded app class
17
+ #
18
+ # @api public
19
+ # @since 2.0.0
20
+ def self.setup(raise_exception: true)
21
+ return app if app?
22
+
23
+ app_path = self.app_path
24
+
25
+ if app_path
26
+ require(app_path)
27
+ app
28
+ elsif raise_exception
29
+ raise(
30
+ AppLoadError,
31
+ "Could not locate your Hanami app file.\n\n" \
32
+ "Your app file should be at `config/app.rb` in your project's root directory."
33
+ )
34
+ end
35
+ end
36
+
37
+ # Finds and returns the absolute path for the Hanami app file (`config/app.rb`).
38
+ #
39
+ # Searches within the given directory, then searches upwards through parent directories until the
40
+ # app file can be found.
41
+ #
42
+ # @param dir [String] The directory from which to start searching. Defaults to the current
43
+ # directory.
44
+ #
45
+ # @return [String, nil] the app file path, or nil if not found.
46
+ #
47
+ # @api public
48
+ # @since 2.0.0
49
+ def self.app_path(dir = Dir.pwd)
50
+ dir = Pathname(dir).expand_path
51
+ path = dir.join(APP_PATH)
52
+
53
+ if path.file?
54
+ path.to_s
55
+ elsif !dir.root?
56
+ app_path(dir.parent)
57
+ end
58
+ end
59
+
60
+ APP_PATH = "config/app.rb"
61
+ private_constant :APP_PATH
62
+
12
63
  def self.app
13
64
  @_mutex.synchronize do
14
65
  unless defined?(@_app)
@@ -22,12 +73,12 @@ module Hanami
22
73
  end
23
74
 
24
75
  def self.app?
25
- defined?(@_app)
76
+ instance_variable_defined?(:@_app)
26
77
  end
27
78
 
28
79
  def self.app=(klass)
29
80
  @_mutex.synchronize do
30
- if defined?(@_app)
81
+ if instance_variable_defined?(:@_app)
31
82
  raise AppLoadError, "Hanami.app is already configured."
32
83
  end
33
84
 
@@ -14,12 +14,10 @@ RSpec.describe "App action / Routes", :app_integration do
14
14
  write "config/routes.rb", <<~RUBY
15
15
  module TestApp
16
16
  class Routes < Hanami::Routes
17
- define do
18
- root to: "home.index"
17
+ root to: "home.index"
19
18
 
20
- slice :admin, at: "/admin" do
21
- root to: "dashboard.index"
22
- end
19
+ slice :admin, at: "/admin" do
20
+ root to: "dashboard.index"
23
21
  end
24
22
  end
25
23
  end