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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/hanami.gemspec +1 -2
- data/lib/hanami/app.rb +76 -16
- data/lib/hanami/assets/{application_configuration.rb → app_configuration.rb} +1 -1
- data/lib/hanami/configuration/null_configuration.rb +2 -2
- data/lib/hanami/configuration.rb +36 -36
- data/lib/hanami/extensions/action/slice_configured_action.rb +44 -1
- data/lib/hanami/extensions/view/slice_configured_view.rb +47 -7
- data/lib/hanami/providers/rack.rb +2 -0
- data/lib/hanami/providers/settings.rb +81 -6
- data/lib/hanami/routes.rb +48 -21
- data/lib/hanami/settings/env_store.rb +32 -0
- data/lib/hanami/settings.rb +8 -12
- data/lib/hanami/setup.rb +1 -6
- data/lib/hanami/slice/routing/middleware/stack.rb +26 -5
- data/lib/hanami/slice.rb +38 -45
- data/lib/hanami/slice_configurable.rb +14 -1
- data/lib/hanami/slice_registrar.rb +65 -5
- data/lib/hanami/version.rb +1 -1
- data/lib/hanami.rb +53 -2
- data/spec/{new_integration → integration}/action/cookies_spec.rb +0 -0
- data/spec/{new_integration → integration}/action/csrf_protection_spec.rb +0 -0
- data/spec/{new_integration → integration}/action/routes_spec.rb +3 -5
- data/spec/{new_integration → integration}/action/sessions_spec.rb +0 -0
- data/spec/integration/action/slice_configuration_spec.rb +287 -0
- data/spec/{new_integration → integration}/action/view_integration_spec.rb +0 -0
- data/spec/{new_integration → integration}/action/view_rendering/automatic_rendering_spec.rb +0 -0
- data/spec/{new_integration → integration}/action/view_rendering/paired_view_inference_spec.rb +0 -0
- data/spec/{new_integration → integration}/action/view_rendering_spec.rb +0 -0
- data/spec/{new_integration → integration}/code_loading/loading_from_app_spec.rb +0 -0
- data/spec/integration/code_loading/loading_from_lib_spec.rb +208 -0
- data/spec/{new_integration → integration}/code_loading/loading_from_slice_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/application_routes_helper_spec.rb +1 -3
- data/spec/{new_integration → integration}/container/auto_injection_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/auto_registration_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/autoloader_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/imports_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/prepare_container_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/shutdown_spec.rb +0 -0
- data/spec/{new_integration → integration}/container/standard_bootable_components_spec.rb +0 -0
- data/spec/integration/dotenv_loading_spec.rb +137 -0
- data/spec/{new_integration → integration}/rack_app/middleware_spec.rb +9 -15
- data/spec/{new_integration → integration}/rack_app/non_booted_rack_app_spec.rb +3 -5
- data/spec/{new_integration → integration}/rack_app/rack_app_spec.rb +28 -48
- data/spec/integration/settings/access_to_constants_spec.rb +169 -0
- data/spec/integration/settings/loading_from_env_spec.rb +187 -0
- data/spec/integration/settings/settings_component_loading_spec.rb +113 -0
- data/spec/integration/settings/using_types_spec.rb +87 -0
- data/spec/integration/setup_spec.rb +165 -0
- data/spec/{new_integration → integration}/slices/external_slice_spec.rb +2 -4
- data/spec/{new_integration → integration}/slices/slice_configuration_spec.rb +0 -0
- data/spec/integration/slices/slice_loading_spec.rb +171 -0
- data/spec/{new_integration → integration}/slices/slice_routing_spec.rb +5 -13
- data/spec/{new_integration → integration}/slices/slice_settings_spec.rb +0 -0
- data/spec/{new_integration → integration}/slices_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/context/assets_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/context/inflector_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/context/request_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/context/routes_spec.rb +1 -3
- data/spec/{new_integration → integration}/view/context/settings_spec.rb +5 -1
- data/spec/{new_integration → integration}/view/inflector_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/part_namespace_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/path_spec.rb +0 -0
- data/spec/integration/view/slice_configuration_spec.rb +289 -0
- data/spec/{new_integration → integration}/view/template_spec.rb +0 -0
- data/spec/{new_integration → integration}/view/views_spec.rb +0 -0
- data/spec/support/app_integration.rb +4 -5
- data/spec/unit/hanami/configuration/actions_spec.rb +4 -15
- data/spec/unit/hanami/configuration/router_spec.rb +45 -0
- data/spec/unit/hanami/configuration/slices_spec.rb +34 -0
- data/spec/unit/hanami/configuration/views_spec.rb +4 -15
- data/spec/unit/hanami/settings/env_store_spec.rb +52 -0
- data/spec/unit/hanami/slice_configurable_spec.rb +2 -2
- data/spec/unit/hanami/version_spec.rb +1 -1
- metadata +105 -250
- data/lib/hanami/server.rb +0 -29
- data/lib/hanami/settings/dotenv_store.rb +0 -58
- data/spec/integration/application_middleware_stack_spec.rb +0 -84
- data/spec/integration/assets/cdn_spec.rb +0 -48
- data/spec/integration/assets/fingerprint_spec.rb +0 -42
- data/spec/integration/assets/helpers_spec.rb +0 -50
- data/spec/integration/assets/serve_spec.rb +0 -70
- data/spec/integration/assets/subresource_integrity_spec.rb +0 -54
- data/spec/integration/body_parsers_spec.rb +0 -50
- data/spec/integration/cli/assets/precompile_spec.rb +0 -147
- data/spec/integration/cli/assets_spec.rb +0 -14
- data/spec/integration/cli/console_spec.rb +0 -105
- data/spec/integration/cli/db/apply_spec.rb +0 -74
- data/spec/integration/cli/db/console_spec.rb +0 -40
- data/spec/integration/cli/db/create_spec.rb +0 -50
- data/spec/integration/cli/db/drop_spec.rb +0 -54
- data/spec/integration/cli/db/migrate_spec.rb +0 -108
- data/spec/integration/cli/db/prepare_spec.rb +0 -36
- data/spec/integration/cli/db/rollback_spec.rb +0 -96
- data/spec/integration/cli/db/version_spec.rb +0 -38
- data/spec/integration/cli/db_spec.rb +0 -21
- data/spec/integration/cli/destroy/action_spec.rb +0 -143
- data/spec/integration/cli/destroy/app_spec.rb +0 -118
- data/spec/integration/cli/destroy/mailer_spec.rb +0 -74
- data/spec/integration/cli/destroy/migration_spec.rb +0 -70
- data/spec/integration/cli/destroy/model_spec.rb +0 -113
- data/spec/integration/cli/destroy_spec.rb +0 -18
- data/spec/integration/cli/generate/action_spec.rb +0 -469
- data/spec/integration/cli/generate/app_spec.rb +0 -215
- data/spec/integration/cli/generate/mailer_spec.rb +0 -189
- data/spec/integration/cli/generate/migration_spec.rb +0 -72
- data/spec/integration/cli/generate/model_spec.rb +0 -290
- data/spec/integration/cli/generate/secret_spec.rb +0 -56
- data/spec/integration/cli/generate_spec.rb +0 -19
- data/spec/integration/cli/new/database_spec.rb +0 -235
- data/spec/integration/cli/new/hanami_head_spec.rb +0 -27
- data/spec/integration/cli/new/template_spec.rb +0 -118
- data/spec/integration/cli/new/test_spec.rb +0 -274
- data/spec/integration/cli/new_spec.rb +0 -970
- data/spec/integration/cli/plugins_spec.rb +0 -39
- data/spec/integration/cli/routes_spec.rb +0 -49
- data/spec/integration/cli/server_spec.rb +0 -626
- data/spec/integration/cli/version_spec.rb +0 -85
- data/spec/integration/early_hints_spec.rb +0 -35
- data/spec/integration/handle_exceptions_spec.rb +0 -244
- data/spec/integration/head_spec.rb +0 -89
- data/spec/integration/http_headers_spec.rb +0 -29
- data/spec/integration/mailer_spec.rb +0 -32
- data/spec/integration/middleware_spec.rb +0 -81
- data/spec/integration/mount_applications_spec.rb +0 -88
- data/spec/integration/project_initializers_spec.rb +0 -40
- data/spec/integration/rackup_spec.rb +0 -35
- data/spec/integration/rake/with_minitest_spec.rb +0 -67
- data/spec/integration/rake/with_rspec_spec.rb +0 -69
- data/spec/integration/routing_helpers_spec.rb +0 -61
- data/spec/integration/security/content_security_policy_spec.rb +0 -46
- data/spec/integration/security/csrf_protection_spec.rb +0 -42
- data/spec/integration/security/force_ssl_spec.rb +0 -29
- data/spec/integration/security/x_content_type_options_spec.rb +0 -46
- data/spec/integration/security/x_frame_options_spec.rb +0 -46
- data/spec/integration/security/x_xss_protection_spec.rb +0 -46
- data/spec/integration/send_file_spec.rb +0 -51
- data/spec/integration/sessions_spec.rb +0 -247
- data/spec/integration/static_middleware_spec.rb +0 -21
- data/spec/integration/streaming_spec.rb +0 -41
- data/spec/integration/unsafe_send_file_spec.rb +0 -52
- data/spec/new_integration/action/configuration_spec.rb +0 -26
- data/spec/new_integration/settings_spec.rb +0 -115
- data/spec/new_integration/view/configuration_spec.rb +0 -49
- data/spec/support/fixtures/hanami-plugin/Gemfile +0 -8
- data/spec/support/fixtures/hanami-plugin/README.md +0 -35
- data/spec/support/fixtures/hanami-plugin/Rakefile +0 -4
- data/spec/support/fixtures/hanami-plugin/bin/console +0 -15
- data/spec/support/fixtures/hanami-plugin/bin/setup +0 -8
- data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +0 -28
- data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +0 -19
- data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +0 -7
- data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +0 -8
- data/spec/unit/hanami/routes_spec.rb +0 -25
- 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
|
-
#
|
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
|
-
@
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
data/lib/hanami/settings.rb
CHANGED
@@ -22,26 +22,22 @@ module Hanami
|
|
22
22
|
# end
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
# Settings are defined with
|
26
|
-
#
|
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
|
-
#
|
31
|
-
#
|
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
|
-
#
|
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
@@ -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
|
-
#
|
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
|
-
#
|
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
|
252
|
-
# Load settings
|
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:
|
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.
|
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:
|
336
|
+
register_provider(:routes, source: Providers::Routes.for_slice(self))
|
332
337
|
end
|
333
338
|
|
334
|
-
|
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
|
-
#
|
342
|
-
#
|
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
|
-
|
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.
|
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
|
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
|
-
|
112
|
-
|
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
|
-
|
116
|
-
|
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
|
data/lib/hanami/version.rb
CHANGED
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
|
-
|
76
|
+
instance_variable_defined?(:@_app)
|
26
77
|
end
|
27
78
|
|
28
79
|
def self.app=(klass)
|
29
80
|
@_mutex.synchronize do
|
30
|
-
if
|
81
|
+
if instance_variable_defined?(:@_app)
|
31
82
|
raise AppLoadError, "Hanami.app is already configured."
|
32
83
|
end
|
33
84
|
|
File without changes
|
File without changes
|
@@ -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
|
-
|
18
|
-
root to: "home.index"
|
17
|
+
root to: "home.index"
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
19
|
+
slice :admin, at: "/admin" do
|
20
|
+
root to: "dashboard.index"
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
File without changes
|