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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 768139dff49ba3a91a5aadccc7ae8aa1257ea04ce4497ba98e74780b84fd59c5
4
- data.tar.gz: 06b9195a50933ee13ab2046e223f20e61535a2e909a4a26a00c0198af7d5c017
3
+ metadata.gz: 52462a1be45d338394f82872ac25bd8fc6e34f5b90991e74928342d0905d377d
4
+ data.tar.gz: 221af8ef1ba7993d6eb0678c0536513fca19f68b83bcf13d2fa5664747b83b98
5
5
  SHA512:
6
- metadata.gz: 430e49a492be7463d755be92c580861d155586bc8ef009eba96b28a39670b576bc8230cc8d8529bd3e2ecc5950814f19bc5bb8d1f0210ccd466cd8f23b0ece7e
7
- data.tar.gz: fb3c215103035f5c9af333792f5df2833952f0d7a8175d46d5f49683a824149be154430899f5aa485cd79c51183d4169b5d302b859b88239d7694da87707a01f
6
+ metadata.gz: f2e0f16b7bee12601ebe28645be73219dd97647d4dc2ddfa47af25ac974092afa8d4b4ec849e0f7866e266c6dd7f39ddf31aa19b857083fb1841f19b98affdbf
7
+ data.tar.gz: 1b2ad0280e15291bba6fdf5efaf75bb10555eba655087ea53a9c5c7e8a5522335e0e07e0f2bf81f2b2c4ce638c14a58bbe2d8c827a792a5d2fc17111245bb75a
data/CHANGELOG.md CHANGED
@@ -2,8 +2,89 @@
2
2
 
3
3
  The web, with simplicity.
4
4
 
5
+ ## v2.0.0.beta3 - 2022-09-21
6
+
7
+ ### Changed
8
+
9
+ - [Piotr Solnica] Simplify routes definition, by removing `routes` block from `config/routes.rb`
10
+
11
+ ### Added
12
+
13
+ ## v2.0.0.beta2 - 2022-08-16
14
+
15
+ ### Added
16
+
17
+ - [Tim Riley] Added `config.slices` setting to specify a list of slices to load. This may also be set via a comma-separated string in the `HANAMI_SLICES` env var. [#1189]
18
+
19
+ This is useful when only certain parts of your application are required for particular workloads. Specifying those slices directly improves boot time and minimizes memory usage, as well as ensuring clean boundaries between your slices.
20
+
21
+ For example, given an app with both `blog`, `shop` and `admin` slices, you can specify only the first two to load:
22
+
23
+ ```ruby
24
+ # config/app.rb
25
+
26
+ module AppName
27
+ class App < Hanami::App
28
+ config.slices = %w[blog shop]
29
+ end
30
+ end
31
+ ```
32
+
33
+ Or by setting `HANAMI_SLICES=blog,shop` in the env.
34
+
35
+ You can also specify arbitrarily nested slices using dot delimiters. For example, if you have an `admin` slice with nested `shop` and `blog` slices, you can specify `config.slices = %w[admin.shop]` to only load the admin slice and its nested shop slice.
36
+
37
+ - [Tim Riley] Added `App.prepare_load_path`, which need only be called if you explicitly change your app's `config.root`. The `$LOAD_PATH` is otherwise still prepared automatically for the default `root` of `Dir.pwd`. [#1188]
38
+ - [Marc Busqué, Tim Riley] Added `Hanami.setup` to find and load your Hanami app file (expected at `config/app.rb`). This will search in the current directory, and will search upwards through parent directories until the app file is found. [#1197]
39
+
40
+ This behavior allows `Hanami.setup` (and its counterpart convenience require, `require "hanami/setup"`) to work even when called
41
+ from a nested directory within your Hanami app.
42
+
43
+ Also added `Hanami.app_path`, returning the absolute path of the app file, if found.
44
+
45
+ ### Changed
46
+
47
+ - [Tim Riley] Allow access to autoloaded constants from within `config/settings.rb` [#1186]
48
+
49
+ New Hanami apps generate a types module at `lib/[app_name]/types.rb`. Now you can access it within your settings class to type check your settings, e.g.
50
+
51
+ ```ruby
52
+ # config/settings.rb
53
+
54
+ module AppName
55
+ class Settings < Hanami::Settings
56
+ # By default, AppName::Types is defined at `lib/[app_name]/types.rb`, and can be autoloaded here
57
+ setting :some_flag, constructor: Types::Params::Bool
58
+ end
59
+ end
60
+ ```
61
+
62
+ For settings defined within a slice, you can access types (or any constant) defined within the slice:
63
+
64
+ ```ruby
65
+ # slices/slice_name/config/settings.rb
66
+
67
+ module SliceName
68
+ class Settings < Hanami::Settings
69
+ # SliceName::Types is expected to be defined at `slices/[slice_name]/types.rb`
70
+ setting :some_slice_flag, constructor: Types::Params::bool
71
+ end
72
+ end
73
+ ```
74
+
75
+ - [Tim Riley] Every slice has a distinct `Zeitwerk::Loader` instance at `Slice.autoloader`. This change enabled the autoloading within settings described above. [#1186]
76
+ - [Tim Riley] `Slice.settings` reader method has been removed. Access settings via `App["settings"]` or `Slice["settings"]` (after the slice has been prepared) instead. [#1186]
77
+ - [Tim Riley] dry-types is no longer a dependency specified in the gemspec. This is made available to generated app via a line in their `Gemfile` instead. [#1186]
78
+
79
+ ### Fixed
80
+
81
+ - [Tim Riley] `config` explicitly set in `Hanami::Action` or `Hanami::View` base classes (either app- or slice-level) is no longer overridden by slice-level configuration [#1193]
82
+ - [Nikita Shilnikov] Load dry-monitor rack extension explicitly to avoid autoloading errors [#1198] [#1199]
83
+
5
84
  ## v2.0.0.beta1.1 - 2022-07-22
6
85
 
86
+ ### Changed
87
+
7
88
  - [Andrew Croome] Specified 2.0.0.beta dependencies for hanami-cli and hanami-utils (to prefer these over previously installed alpha versions) [#1187]
8
89
 
9
90
  ## v2.0.0.beta1 - 2022-07-20
@@ -57,6 +138,7 @@ The web, with simplicity.
57
138
  - [Luca Guidi] `Hanami.application` -> `Hanami.app`
58
139
  - [Luca Guidi] `config/application.rb` -> `config/app.rb`
59
140
  - [Luca Guidi] Remove `::Application::` namespace in public API class names (e.g. `Hanami::Application::Routes` -> `Hanami::Routes`) [#1172]
141
+ - [Tim Riley] Removed `Hanami::Configuration#source_dirs` due to the new autoloading and auto-registration features [#1174]
60
142
  - [Tim Riley] Removed `Hanami::Configuration#settings_path`, `#settings_class_name` [#1175]
61
143
  - [Tim Riley] Removed `Hanami::Configuration::Router#routes_path`, and `#routes_class_name` [#1175]
62
144
  - [Tim Riley] Make `Hanami::App` to inherit from `Hanami::Slice` [#1162]
data/hanami.gemspec CHANGED
@@ -34,10 +34,9 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "bundler", ">= 1.16", "< 3"
35
35
  spec.add_dependency "dry-configurable", "~> 0.15"
36
36
  spec.add_dependency "dry-core", "~> 0.7"
37
- spec.add_dependency "dry-types", "~> 1.5"
38
37
  spec.add_dependency "dry-inflector", "~> 0.2", ">= 0.2.1"
39
38
  spec.add_dependency "dry-system", "~> 0.25", ">= 0.25.0"
40
- spec.add_dependency "dry-monitor", "~> 0.6", ">= 0.6.0"
39
+ spec.add_dependency "dry-monitor", "~> 0.6", ">= 0.6.3"
41
40
  spec.add_dependency "hanami-cli", "~> 2.0.beta"
42
41
  spec.add_dependency "hanami-utils", "~> 2.0.beta"
43
42
  spec.add_dependency "zeitwerk", "~> 2.4"
data/lib/hanami/app.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "zeitwerk"
4
3
  require_relative "configuration"
5
4
  require_relative "constants"
6
5
  require_relative "slice"
@@ -31,29 +30,97 @@ module Hanami
31
30
  @_mutex.synchronize do
32
31
  subclass.class_eval do
33
32
  @configuration = Hanami::Configuration.new(app_name: slice_name, env: Hanami.env)
34
- @autoloader = Zeitwerk::Loader.new
35
33
 
36
- prepare_base_load_path
34
+ # Prepare the load path (based on the default root of `Dir.pwd`) as early as
35
+ # possible, so you can make a `require` inside the body of an `App` subclass,
36
+ # which may be useful for certain kinds of app configuration.
37
+ prepare_load_path
38
+
39
+ load_dotenv
37
40
  end
38
41
  end
39
42
  end
40
43
 
41
44
  # App class interface
42
45
  module ClassMethods
43
- attr_reader :autoloader, :configuration
46
+ attr_reader :configuration
44
47
 
45
48
  def app_name
46
49
  slice_name
47
50
  end
48
51
 
52
+ # Prepares the $LOAD_PATH based on the app's configured root, prepending the `lib/`
53
+ # directory if it exists. If the lib directory is already added, this will do
54
+ # nothing.
55
+ #
56
+ # In ordinary circumstances, you should never have to call this method: this method
57
+ # is called immediately upon subclassing {Hanami::App}, as a convenicence to put
58
+ # lib/ (under the default root of `Dir.pwd`) on the load path automatically. This is
59
+ # helpful if you need to require files inside the subclass body for performing
60
+ # certain app configuration steps.
61
+ #
62
+ # If you change your app's `config.root` and you need to require files from its
63
+ # `lib/` directory within your {App} subclass body, you should call
64
+ # {.prepare_load_path} explicitly after setting the new root.
65
+ #
66
+ # Otherwise, this method is called again as part of the app {.prepare} step, so if
67
+ # you've changed your app's root and do _not_ need to require files within your {App}
68
+ # subclass body, then you don't need to call this method.
69
+ #
70
+ # @example
71
+ # module MyApp
72
+ # class App < Hanami::App
73
+ # config.root = Pathname(__dir__).join("../src")
74
+ # prepare_load_path
75
+ #
76
+ # # You can make requires for your files here
77
+ # end
78
+ # end
79
+ #
80
+ # @return [self]
81
+ #
82
+ # @api public
83
+ # @since 2.0.0
84
+ def prepare_load_path
85
+ if (lib_path = root.join(LIB_DIR)).directory?
86
+ path = lib_path.realpath.to_s
87
+ $LOAD_PATH.prepend(path) unless $LOAD_PATH.include?(path)
88
+ end
89
+
90
+ self
91
+ end
92
+
49
93
  private
50
94
 
51
- def prepare_base_load_path
52
- base_path = root.join(LIB_DIR)
53
- $LOAD_PATH.unshift(base_path) unless $LOAD_PATH.include?(base_path)
95
+ # Uses [dotenv](https://github.com/bkeepers/dotenv) (if available) to populate `ENV` from
96
+ # various `.env` files.
97
+ #
98
+ # For a given `HANAMI_ENV` environment, the `.env` files are looked up in the following order:
99
+ #
100
+ # - .env.{environment}.local
101
+ # - .env.local (unless the environment is `test`)
102
+ # - .env.{environment}
103
+ # - .env
104
+ #
105
+ # If dotenv is unavailable, the method exits and does nothing.
106
+ def load_dotenv
107
+ return unless Hanami.bundled?("dotenv")
108
+
109
+ hanami_env = Hanami.env
110
+ dotenv_files = [
111
+ ".env.#{hanami_env}.local",
112
+ (".env.local" unless hanami_env == :test),
113
+ ".env.#{hanami_env}",
114
+ ".env"
115
+ ].compact
116
+
117
+ require "dotenv"
118
+ Dotenv.load(*dotenv_files)
54
119
  end
55
120
 
56
121
  def prepare_all
122
+ prepare_load_path
123
+
57
124
  # Make app-wide notifications available as early as possible
58
125
  container.use(:notifications)
59
126
 
@@ -65,16 +132,9 @@ module Hanami
65
132
  # standard steps have been skipped via the empty method overrides below.
66
133
  prepare_app_component_dirs
67
134
  prepare_app_providers
68
-
69
- # The autoloader must be setup after the container is configured, which is the
70
- # point at which any component dirs from other slices are added to the autoloader
71
- app = self
72
- container.after(:configure) do
73
- app.send(:prepare_app_autoloader)
74
- end
75
135
  end
76
136
 
77
- # Skip standard slice prepare steps that do not apply to the app slice
137
+ # Skip standard slice prepare steps that do not apply to the app
78
138
  def prepare_container_component_dirs; end
79
139
  def prepare_container_imports; end
80
140
 
@@ -122,7 +182,7 @@ module Hanami
122
182
  register_provider(:rack, source: Hanami::Providers::Rack, namespace: true)
123
183
  end
124
184
 
125
- def prepare_app_autoloader
185
+ def prepare_autoloader
126
186
  # Component dirs are automatically pushed to the autoloader by dry-system's
127
187
  # zeitwerk plugin. This method adds other dirs that are not otherwise configured
128
188
  # as component dirs.
@@ -7,7 +7,7 @@ module Hanami
7
7
  module Assets
8
8
  # @since 2.0.0
9
9
  # @api public
10
- class ApplicationConfiguration
10
+ class AppConfiguration
11
11
  include Dry::Configurable
12
12
 
13
13
  # @since 2.0.0
@@ -5,8 +5,8 @@ require "dry/configurable"
5
5
  module Hanami
6
6
  class Configuration
7
7
  # NullConfiguration can serve as a fallback configuration object when out-of-gem
8
- # configuration objects are not available (specifically, when the hanami-controller or
9
- # hanami-view gems are not loaded)
8
+ # configuration objects are not available (specifically, when the
9
+ # hanami-controller, hanami-router or hanami-view gems are not loaded)
10
10
  class NullConfiguration
11
11
  include Dry::Configurable
12
12
  end
@@ -9,9 +9,8 @@ require "pathname"
9
9
 
10
10
  require_relative "constants"
11
11
  require_relative "configuration/logger"
12
- require_relative "configuration/router"
13
12
  require_relative "configuration/sessions"
14
- require_relative "settings/dotenv_store"
13
+ require_relative "settings/env_store"
15
14
  require_relative "slice/routing/middleware/stack"
16
15
 
17
16
  module Hanami
@@ -27,18 +26,18 @@ module Hanami
27
26
 
28
27
  setting :inflector, default: Dry::Inflector.new
29
28
 
30
- setting :settings_store, default: Hanami::Settings::DotenvStore
31
-
32
- setting :slices do
33
- setting :shared_component_keys, default: %w[
34
- inflector
35
- logger
36
- notifications
37
- rack.monitor
38
- routes
39
- settings
40
- ]
41
- end
29
+ setting :settings_store, default: Hanami::Settings::EnvStore.new
30
+
31
+ setting :shared_app_component_keys, default: %w[
32
+ inflector
33
+ logger
34
+ notifications
35
+ rack.monitor
36
+ routes
37
+ settings
38
+ ]
39
+
40
+ setting :slices
42
41
 
43
42
  setting :base_url, default: "http://0.0.0.0:2300", constructor: ->(url) { URI(url) }
44
43
 
@@ -100,27 +99,28 @@ module Hanami
100
99
  @environments = DEFAULT_ENVIRONMENTS.clone
101
100
  @env = env
102
101
 
103
- # Some default setting values must be assigned at initialize-time to ensure they
104
- # have appropriate values for the current app
102
+ # Apply default values that are only knowable at initialize-time (vs require-time)
105
103
  self.root = Dir.pwd
106
- self.settings_store = Hanami::Settings::DotenvStore.new.with_dotenv_loaded
104
+ load_from_env
107
105
 
108
106
  config.logger = Configuration::Logger.new(env: env, app_name: app_name)
109
107
 
110
- @assets = load_dependent_config("hanami/assets/app_configuration") {
111
- Hanami::Assets::AppConfiguration.new
112
- }
108
+ # TODO: Make assets configuration dependent
109
+ require "hanami/assets/app_configuration"
110
+ @assets = Hanami::Assets::AppConfiguration.new
113
111
 
114
- @actions = load_dependent_config("hanami/action") {
112
+ @actions = load_dependent_config("hanami-controller") {
115
113
  require_relative "configuration/actions"
116
114
  Actions.new
117
115
  }
118
116
 
119
- @middleware = Slice::Routing::Middleware::Stack.new
120
-
121
- @router = Router.new(self)
117
+ @router = load_dependent_config("hanami-router") {
118
+ require_relative "configuration/router"
119
+ @middleware = Slice::Routing::Middleware::Stack.new
120
+ Router.new(self)
121
+ }
122
122
 
123
- @views = load_dependent_config("hanami/view") {
123
+ @views = load_dependent_config("hanami-view") {
124
124
  require_relative "configuration/views"
125
125
  Views.new
126
126
  }
@@ -199,7 +199,10 @@ module Hanami
199
199
 
200
200
  private
201
201
 
202
- # @api private
202
+ def load_from_env
203
+ self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
204
+ end
205
+
203
206
  def apply_env_config(env = self.env)
204
207
  environments[env].each do |block|
205
208
  instance_eval(&block)
@@ -207,17 +210,15 @@ module Hanami
207
210
  end
208
211
 
209
212
  # @api private
210
- def load_dependent_config(require_path, &block)
211
- require require_path
212
- yield
213
- rescue LoadError => e
214
- raise e unless e.path == require_path
215
-
216
- require_relative "configuration/null_configuration"
217
- NullConfiguration.new
213
+ def load_dependent_config(gem_name)
214
+ if Hanami.bundled?(gem_name)
215
+ yield
216
+ else
217
+ require_relative "configuration/null_configuration"
218
+ NullConfiguration.new
219
+ end
218
220
  end
219
221
 
220
- # @api private
221
222
  def method_missing(name, *args, &block)
222
223
  if config.respond_to?(name)
223
224
  config.public_send(name, *args, &block)
@@ -226,7 +227,6 @@ module Hanami
226
227
  end
227
228
  end
228
229
 
229
- # @api private
230
230
  def respond_to_missing?(name, _incude_all = false)
231
231
  config.respond_to?(name) || super
232
232
  end
@@ -48,7 +48,50 @@ module Hanami
48
48
 
49
49
  def configure_action(action_class)
50
50
  action_class.config.settings.each do |setting|
51
- action_class.config.public_send :"#{setting}=", actions_config.public_send(:"#{setting}")
51
+ # Configure the action from config on the slice, _unless it has already been configured
52
+ # by a parent slice_, and re-configuring it for this slice would make no change.
53
+ #
54
+ # In the case of most slices, its actions config is likely to be the same as its parent
55
+ # (since each slice copies its `config` from its parent), and if we re-apply the config
56
+ # here, then it may possibly overwrite config customisations explicitly made in parent
57
+ # action classes.
58
+ #
59
+ # For example, given an app-level base action class, with custom config:
60
+ #
61
+ # module MyApp
62
+ # class Action < Hanami::Action
63
+ # config.default_response_format = :json
64
+ # end
65
+ # end
66
+ #
67
+ # And then an action in a slice inheriting from it:
68
+ #
69
+ # module MySlice
70
+ # module Actions
71
+ # class SomeAction < MyApp::Action
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ # In this case, `SliceConfiguredAction` will be extended two times:
77
+ #
78
+ # 1. When `MyApp::Action` is defined
79
+ # 2. Again when `MySlice::Actions::SomeAction` is defined
80
+ #
81
+ # If we blindly re-configure all action settings each time `SliceConfiguredAction` is
82
+ # extended, then at the point of (2) above, we'd end up overwriting the custom
83
+ # `config.default_response_format` explicitly configured in the `MyApp::Action` base
84
+ # class, leaving `MySlice::Actions::SomeAction` with `config.default_response_format` of
85
+ # `:html` (the default at `Hanami.app.config.actions.default_response_format`), and not
86
+ # the `:json` value configured in its immediate superclass.
87
+ #
88
+ # This would be surprising behavior, and we want to avoid it.
89
+ slice_value = slice.config.actions.public_send(:"#{setting}")
90
+ parent_value = slice.parent.config.actions.public_send(:"#{setting}") if slice.parent
91
+
92
+ next if slice.parent && slice_value == parent_value
93
+
94
+ action_class.config.public_send(:"#{setting}=", slice_value)
52
95
  end
53
96
  end
54
97
 
@@ -32,12 +32,52 @@ module Hanami
32
32
  # rubocop:disable Metrics/AbcSize
33
33
  def configure_view(view_class)
34
34
  view_class.settings.each do |setting|
35
- if slice.config.views.respond_to?(:"#{setting}")
36
- view_class.config.public_send(
37
- :"#{setting}=",
38
- slice.config.views.public_send(:"#{setting}")
39
- )
40
- end
35
+ next unless slice.config.views.respond_to?(:"#{setting}")
36
+
37
+ # Configure the view from config on the slice, _unless it has already been configured by
38
+ # a parent slice_, and re-configuring it for this slice would make no change.
39
+ #
40
+ # In the case of most slices, its views config is likely to be the same as its parent
41
+ # (since each slice copies its `config` from its parent), and if we re-apply the config
42
+ # here, then it may possibly overwrite config customisations explicitly made in parent
43
+ # view classes.
44
+ #
45
+ # For example, given an app-level base view class, with custom config:
46
+ #
47
+ # module MyApp
48
+ # class View < Hanami::View
49
+ # config.layout = "custom_layout"
50
+ # end
51
+ # end
52
+ #
53
+ # And then a view in a slice inheriting from it:
54
+ #
55
+ # module MySlice
56
+ # module Views
57
+ # class SomeView < MyApp::View
58
+ # end
59
+ # end
60
+ # end
61
+ #
62
+ # In this case, `SliceConfiguredView` will be extended two times:
63
+ #
64
+ # 1. When `MyApp::View` is defined
65
+ # 2. Again when `MySlice::Views::SomeView` is defined
66
+ #
67
+ # If we blindly re-configure all view settings each time `SliceConfiguredView` is
68
+ # extended, then at the point of (2) above, we'd end up overwriting the custom
69
+ # `config.layout` explicitly configured in the `MyApp::View` base class, leaving
70
+ # `MySlice::Views::SomeView` with `config.layout` of `"app"` (the default as specified
71
+ # at `Hanami.app.config.views.layout`), and not the `"custom_layout"` value configured
72
+ # in its immediate superclass.
73
+ #
74
+ # This would be surprising behavior, and we want to avoid it.
75
+ slice_value = slice.config.views.public_send(:"#{setting}")
76
+ parent_value = slice.parent.config.views.public_send(:"#{setting}") if slice.parent
77
+
78
+ next if slice.parent && slice_value == parent_value
79
+
80
+ view_class.config.public_send(:"#{setting}=", slice_value)
41
81
  end
42
82
 
43
83
  view_class.config.inflector = inflector
@@ -86,7 +126,7 @@ module Hanami
86
126
 
87
127
  begin
88
128
  inflector.constantize(inflector.camelize(path))
89
- rescue NameError => exception
129
+ rescue NameError # rubocop: disable Lint/SuppressedException
90
130
  end
91
131
  end
92
132
 
@@ -8,6 +8,8 @@ module Hanami
8
8
  def prepare
9
9
  require "dry/monitor"
10
10
  require "hanami/web/rack_logger"
11
+
12
+ Dry::Monitor.load_extensions(:rack)
11
13
  end
12
14
 
13
15
  def start
@@ -1,22 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/provider/source"
4
+ require_relative "../constants"
5
+ require_relative "../errors"
4
6
 
5
7
  module Hanami
6
8
  module Providers
9
+ # The settings provider loads and registers the "settings" component in app and slice
10
+ # containers.
11
+ #
12
+ # To register this provider with a slice container, you should use
13
+ # {.register_with_slice}, which will register the provider only if settings are
14
+ # defined for the slice.
15
+ #
16
+ # @see Slice::ClassMethods.prepare_container_providers
17
+ #
18
+ # @api private
19
+ # @since 2.0.0
7
20
  class Settings < Dry::System::Provider::Source
8
- def self.for_slice(slice)
9
- Class.new(self) do |klass|
10
- klass.instance_variable_set(:@slice, slice)
21
+ class << self
22
+ # Registers the provider with the slice's container, but only if settings are
23
+ # defined for the slice.
24
+ def register_with_slice(slice)
25
+ return unless settings_defined?(slice)
26
+
27
+ slice.register_provider(:settings, source: with_slice(slice))
28
+ end
29
+
30
+ # Creates a new subclass of the provider for the given slice.
31
+ #
32
+ # You must do this before registering the provider with a container. The provider
33
+ # uses the slice to locate the settings definition based on the slice's config.
34
+ def with_slice(slice)
35
+ Class.new(self) do |klass|
36
+ klass.instance_variable_set(:@slice, slice)
37
+ end
38
+ end
39
+
40
+ # Returns the slice for the provider
41
+ def slice
42
+ unless @slice
43
+ raise SliceLoadError, "a slice must be given to #{self} via `.with_slice(slice)`"
44
+ end
45
+
46
+ @slice
47
+ end
48
+
49
+ private
50
+
51
+ # Returns true if settings are defined for the slice.
52
+ #
53
+ # Settings are considered defined if a `Settings` class is already defined in the
54
+ # slice namespace, or a `config/settings.rb` exists under the slice root.
55
+ def settings_defined?(slice)
56
+ slice.namespace.const_defined?(SETTINGS_CLASS_NAME) ||
57
+ slice.root.join("#{SETTINGS_PATH}#{RB_EXT}").file?
11
58
  end
12
59
  end
13
60
 
14
- def self.slice
15
- @slice || Hanami.app
61
+ def prepare
62
+ require_slice_settings unless slice_settings_class?
16
63
  end
17
64
 
18
65
  def start
19
- register :settings, self.class.slice.settings
66
+ settings = slice_settings_class.new(slice.config.settings_store)
67
+
68
+ register :settings, settings
69
+ end
70
+
71
+ private
72
+
73
+ def slice
74
+ self.class.slice
75
+ end
76
+
77
+ def slice_settings_class?
78
+ slice.namespace.const_defined?(SETTINGS_CLASS_NAME)
79
+ end
80
+
81
+ def slice_settings_class
82
+ slice.namespace.const_get(SETTINGS_CLASS_NAME)
83
+ end
84
+
85
+ def require_slice_settings
86
+ require "hanami/settings"
87
+
88
+ slice_settings_require_path = File.join(slice.root, SETTINGS_PATH)
89
+
90
+ begin
91
+ require slice_settings_require_path
92
+ rescue LoadError => e
93
+ raise e unless e.path == slice_settings_require_path
94
+ end
20
95
  end
21
96
  end
22
97
  end