hanami 2.3.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -19
  3. data/LICENSE +20 -0
  4. data/README.md +18 -35
  5. data/hanami.gemspec +36 -37
  6. data/lib/hanami/app.rb +12 -2
  7. data/lib/hanami/config/db.rb +2 -0
  8. data/lib/hanami/config/i18n.rb +138 -0
  9. data/lib/hanami/config/logger.rb +15 -7
  10. data/lib/hanami/config/null_config.rb +1 -1
  11. data/lib/hanami/config/views.rb +17 -0
  12. data/lib/hanami/config.rb +66 -22
  13. data/lib/hanami/errors.rb +6 -0
  14. data/lib/hanami/extensions/action/i18n_helper.rb +64 -0
  15. data/lib/hanami/extensions/action/name_inferrer.rb +34 -0
  16. data/lib/hanami/extensions/action/slice_configured_action.rb +31 -2
  17. data/lib/hanami/extensions/action.rb +20 -7
  18. data/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
  19. data/lib/hanami/extensions/mailer.rb +28 -0
  20. data/lib/hanami/extensions/operation/slice_configured_db_operation.rb +2 -0
  21. data/lib/hanami/extensions/view/context.rb +26 -4
  22. data/lib/hanami/extensions/view/part.rb +2 -0
  23. data/lib/hanami/extensions/view/slice_configured_context.rb +7 -0
  24. data/lib/hanami/extensions/view/slice_configured_part.rb +2 -0
  25. data/lib/hanami/extensions/view/slice_configured_view.rb +8 -8
  26. data/lib/hanami/extensions/view/standard_helpers.rb +4 -0
  27. data/lib/hanami/extensions.rb +6 -1
  28. data/lib/hanami/helpers/assets_helper.rb +0 -4
  29. data/lib/hanami/helpers/form_helper.rb +1 -1
  30. data/lib/hanami/helpers/i18n_helper.rb +241 -0
  31. data/lib/hanami/logger/rack_formatter.rb +73 -0
  32. data/lib/hanami/logger/sql_formatter.rb +80 -0
  33. data/lib/hanami/logger/sql_logger.rb +48 -0
  34. data/lib/hanami/middleware/render_errors.rb +2 -2
  35. data/lib/hanami/providers/db.rb +7 -2
  36. data/lib/hanami/providers/db_logging.rb +4 -7
  37. data/lib/hanami/providers/i18n/backend.rb +373 -0
  38. data/lib/hanami/providers/i18n/locale/en.yml +57 -0
  39. data/lib/hanami/providers/i18n.rb +114 -0
  40. data/lib/hanami/providers/logger.rb +23 -1
  41. data/lib/hanami/providers/mailers.rb +101 -0
  42. data/lib/hanami/routes.rb +1 -0
  43. data/lib/hanami/settings/composite_store.rb +53 -0
  44. data/lib/hanami/settings.rb +4 -4
  45. data/lib/hanami/slice/router.rb +15 -10
  46. data/lib/hanami/slice.rb +102 -11
  47. data/lib/hanami/slice_configurable.rb +1 -3
  48. data/lib/hanami/slice_registrar.rb +2 -2
  49. data/lib/hanami/universal_logger.rb +250 -0
  50. data/lib/hanami/version.rb +1 -1
  51. data/lib/hanami/web/rack_logger.rb +2 -80
  52. data/lib/hanami/web/welcome.html.erb +443 -58
  53. data/lib/hanami.rb +4 -2
  54. metadata +34 -280
  55. data/CODE_OF_CONDUCT.md +0 -74
  56. data/FEATURES.md +0 -269
  57. data/LICENSE.md +0 -22
  58. data/spec/integration/action/cookies_spec.rb +0 -58
  59. data/spec/integration/action/csrf_protection_spec.rb +0 -54
  60. data/spec/integration/action/format_config_spec.rb +0 -129
  61. data/spec/integration/action/routes_spec.rb +0 -71
  62. data/spec/integration/action/sessions_spec.rb +0 -50
  63. data/spec/integration/action/slice_configuration_spec.rb +0 -284
  64. data/spec/integration/action/view_rendering/automatic_rendering_spec.rb +0 -247
  65. data/spec/integration/action/view_rendering/paired_view_inference_spec.rb +0 -115
  66. data/spec/integration/action/view_rendering/view_context_spec.rb +0 -221
  67. data/spec/integration/action/view_rendering_spec.rb +0 -89
  68. data/spec/integration/assets/assets_spec.rb +0 -155
  69. data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -129
  70. data/spec/integration/assets/serve_static_assets_spec.rb +0 -152
  71. data/spec/integration/code_loading/loading_from_app_spec.rb +0 -152
  72. data/spec/integration/code_loading/loading_from_lib_spec.rb +0 -242
  73. data/spec/integration/code_loading/loading_from_slice_spec.rb +0 -165
  74. data/spec/integration/container/application_routes_helper_spec.rb +0 -48
  75. data/spec/integration/container/auto_injection_spec.rb +0 -53
  76. data/spec/integration/container/auto_registration_spec.rb +0 -86
  77. data/spec/integration/container/autoloader_spec.rb +0 -82
  78. data/spec/integration/container/imports_spec.rb +0 -253
  79. data/spec/integration/container/prepare_container_spec.rb +0 -125
  80. data/spec/integration/container/provider_environment_spec.rb +0 -52
  81. data/spec/integration/container/provider_lifecycle_spec.rb +0 -61
  82. data/spec/integration/container/shutdown_spec.rb +0 -91
  83. data/spec/integration/container/standard_providers/rack_provider_spec.rb +0 -44
  84. data/spec/integration/container/standard_providers_spec.rb +0 -124
  85. data/spec/integration/db/auto_registration_spec.rb +0 -39
  86. data/spec/integration/db/commands_spec.rb +0 -80
  87. data/spec/integration/db/db_inflector_spec.rb +0 -57
  88. data/spec/integration/db/db_slices_spec.rb +0 -398
  89. data/spec/integration/db/db_spec.rb +0 -245
  90. data/spec/integration/db/gateways_spec.rb +0 -361
  91. data/spec/integration/db/logging_spec.rb +0 -301
  92. data/spec/integration/db/mappers_spec.rb +0 -84
  93. data/spec/integration/db/provider_config_spec.rb +0 -88
  94. data/spec/integration/db/provider_spec.rb +0 -35
  95. data/spec/integration/db/relations_spec.rb +0 -60
  96. data/spec/integration/db/repo_spec.rb +0 -300
  97. data/spec/integration/db/slices_importing_from_parent.rb +0 -130
  98. data/spec/integration/dotenv_loading_spec.rb +0 -138
  99. data/spec/integration/logging/exception_logging_spec.rb +0 -120
  100. data/spec/integration/logging/notifications_spec.rb +0 -68
  101. data/spec/integration/logging/request_logging_spec.rb +0 -202
  102. data/spec/integration/operations/extension_spec.rb +0 -122
  103. data/spec/integration/rack_app/body_parser_spec.rb +0 -108
  104. data/spec/integration/rack_app/method_override_spec.rb +0 -97
  105. data/spec/integration/rack_app/middleware_spec.rb +0 -720
  106. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +0 -104
  107. data/spec/integration/rack_app/rack_app_spec.rb +0 -442
  108. data/spec/integration/rake_tasks_spec.rb +0 -107
  109. data/spec/integration/router/resource_routes_spec.rb +0 -281
  110. data/spec/integration/settings/access_in_slice_class_body_spec.rb +0 -83
  111. data/spec/integration/settings/access_to_constants_spec.rb +0 -46
  112. data/spec/integration/settings/loading_from_env_spec.rb +0 -188
  113. data/spec/integration/settings/settings_component_loading_spec.rb +0 -113
  114. data/spec/integration/settings/slice_registration_spec.rb +0 -145
  115. data/spec/integration/settings/using_types_spec.rb +0 -80
  116. data/spec/integration/setup_spec.rb +0 -165
  117. data/spec/integration/slices/external_slice_spec.rb +0 -91
  118. data/spec/integration/slices/slice_configuration_spec.rb +0 -42
  119. data/spec/integration/slices/slice_loading_spec.rb +0 -171
  120. data/spec/integration/slices/slice_registrations_spec.rb +0 -80
  121. data/spec/integration/slices/slice_routing_spec.rb +0 -219
  122. data/spec/integration/slices_spec.rb +0 -471
  123. data/spec/integration/view/config/default_context_spec.rb +0 -149
  124. data/spec/integration/view/config/inflector_spec.rb +0 -57
  125. data/spec/integration/view/config/part_class_spec.rb +0 -147
  126. data/spec/integration/view/config/part_namespace_spec.rb +0 -103
  127. data/spec/integration/view/config/paths_spec.rb +0 -119
  128. data/spec/integration/view/config/scope_class_spec.rb +0 -147
  129. data/spec/integration/view/config/scope_namespace_spec.rb +0 -103
  130. data/spec/integration/view/config/template_spec.rb +0 -38
  131. data/spec/integration/view/context/assets_spec.rb +0 -79
  132. data/spec/integration/view/context/inflector_spec.rb +0 -40
  133. data/spec/integration/view/context/request_spec.rb +0 -57
  134. data/spec/integration/view/context/routes_spec.rb +0 -84
  135. data/spec/integration/view/helpers/form_helper_spec.rb +0 -174
  136. data/spec/integration/view/helpers/part_helpers_spec.rb +0 -124
  137. data/spec/integration/view/helpers/scope_helpers_spec.rb +0 -84
  138. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +0 -162
  139. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +0 -119
  140. data/spec/integration/view/parts/default_rendering_spec.rb +0 -138
  141. data/spec/integration/view/slice_configuration_spec.rb +0 -289
  142. data/spec/integration/view/views_spec.rb +0 -103
  143. data/spec/integration/web/content_security_policy_nonce_spec.rb +0 -251
  144. data/spec/integration/web/render_detailed_errors_spec.rb +0 -107
  145. data/spec/integration/web/render_errors_spec.rb +0 -242
  146. data/spec/integration/web/welcome_view_spec.rb +0 -84
  147. data/spec/spec_helper.rb +0 -28
  148. data/spec/support/app_integration.rb +0 -157
  149. data/spec/support/coverage.rb +0 -1
  150. data/spec/support/matchers.rb +0 -32
  151. data/spec/support/rspec.rb +0 -27
  152. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +0 -96
  153. data/spec/unit/hanami/config/actions/cookies_spec.rb +0 -46
  154. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +0 -58
  155. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -43
  156. data/spec/unit/hanami/config/actions/sessions_spec.rb +0 -48
  157. data/spec/unit/hanami/config/actions_spec.rb +0 -52
  158. data/spec/unit/hanami/config/base_url_spec.rb +0 -25
  159. data/spec/unit/hanami/config/console_spec.rb +0 -22
  160. data/spec/unit/hanami/config/db_spec.rb +0 -38
  161. data/spec/unit/hanami/config/inflector_spec.rb +0 -35
  162. data/spec/unit/hanami/config/logger_spec.rb +0 -195
  163. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +0 -25
  164. data/spec/unit/hanami/config/render_errors_spec.rb +0 -25
  165. data/spec/unit/hanami/config/router_spec.rb +0 -44
  166. data/spec/unit/hanami/config/slices_spec.rb +0 -34
  167. data/spec/unit/hanami/config/views_spec.rb +0 -80
  168. data/spec/unit/hanami/env_spec.rb +0 -37
  169. data/spec/unit/hanami/extensions/view/context_spec.rb +0 -59
  170. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +0 -120
  171. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +0 -132
  172. data/spec/unit/hanami/helpers/assets_helper/favicon_tag_spec.rb +0 -87
  173. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +0 -92
  174. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +0 -143
  175. data/spec/unit/hanami/helpers/assets_helper/stylesheet_tag_spec.rb +0 -126
  176. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +0 -136
  177. data/spec/unit/hanami/helpers/form_helper_spec.rb +0 -2857
  178. data/spec/unit/hanami/port_spec.rb +0 -117
  179. data/spec/unit/hanami/providers/db/config/default_config_spec.rb +0 -100
  180. data/spec/unit/hanami/providers/db/config/gateway_spec.rb +0 -73
  181. data/spec/unit/hanami/providers/db/config_spec.rb +0 -143
  182. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +0 -27
  183. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +0 -22
  184. data/spec/unit/hanami/settings/env_store_spec.rb +0 -52
  185. data/spec/unit/hanami/settings_spec.rb +0 -111
  186. data/spec/unit/hanami/slice_configurable_spec.rb +0 -141
  187. data/spec/unit/hanami/slice_name_spec.rb +0 -47
  188. data/spec/unit/hanami/slice_spec.rb +0 -99
  189. data/spec/unit/hanami/web/rack_logger_spec.rb +0 -99
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Providers
5
+ # Registers the `"mailers.delivery_method"` component. This is an SMTP delivery method built
6
+ # from SMTP environment variables when present, otherwise the test delivery method.
7
+ #
8
+ # SMTP env vars may take a per-slice prefix derived from the slice name (e.g. an "admin" slice
9
+ # reads `ADMIN__SMTP_ADDRESS`, falling back to `SMTP_ADDRESS`). Register your own `:mailers`
10
+ # provider if you need to use another delivery method or different setup logic.
11
+ #
12
+ # In the test env, environment variables are ignored, and the test delivery method is always
13
+ # used, so the test suite can never send real email.
14
+ #
15
+ # In the production env, warns noisily when there is no SMTP configuration, before falling back
16
+ # to the test delivery method. This ensures an app whose mail setup is a work in progress can
17
+ # still boot.
18
+ #
19
+ # @api private
20
+ class Mailers < Hanami::Provider::Source
21
+ # Maps SMTP environment variable names to their compatible delivery option keys.
22
+ #
23
+ # Example values:
24
+ #
25
+ # SMTP_ADDRESS=smtp.example.com
26
+ # SMTP_PORT=587
27
+ # SMTP_USERNAME=postmaster@example.com
28
+ # SMTP_PASSWORD=s3cr3t
29
+ # SMTP_AUTHENTICATION=plain
30
+ SMTP_ENV_VARS = {
31
+ "SMTP_ADDRESS" => :address,
32
+ "SMTP_PORT" => :port,
33
+ "SMTP_USERNAME" => :user_name,
34
+ "SMTP_PASSWORD" => :password,
35
+ "SMTP_AUTHENTICATION" => :authentication
36
+ }.freeze
37
+
38
+ # Coercions applied to SMTP env var values, keyed by option. Options not listed here are
39
+ # passed through unchanged (as strings).
40
+ SMTP_COERCIONS = Hash.new(:itself.to_proc).update(
41
+ port: ->(value) { Integer(value) },
42
+ authentication: ->(value) { value.to_sym }
43
+ ).freeze
44
+
45
+ def start
46
+ require "hanami/mailer"
47
+
48
+ register "delivery_method", build_delivery_method
49
+ end
50
+
51
+ private
52
+
53
+ def build_delivery_method
54
+ return Hanami::Mailer::Delivery::Test.new if Hanami.env?(:test)
55
+
56
+ smtp_options = smtp_options_from_env
57
+
58
+ return Hanami::Mailer::Delivery::SMTP.new(**smtp_options) if smtp_options.key?(:address)
59
+
60
+ warn_missing_smtp if Hanami.env?(:production)
61
+
62
+ Hanami::Mailer::Delivery::Test.new
63
+ end
64
+
65
+ def smtp_options_from_env
66
+ SMTP_ENV_VARS.each_with_object({}) do |(var, option), options|
67
+ value = env_value(var)
68
+ next if value.nil?
69
+
70
+ coercion = SMTP_COERCIONS[option]
71
+ options[option] = coercion ? coercion.call(value) : value
72
+ end
73
+ end
74
+
75
+ # Reads an SMTP env var, preferring a per-slice prefixed name (e.g. `ADMIN__SMTP_ADDRESS`)
76
+ # and falling back to the unprefixed name shared across slices.
77
+ def env_value(var)
78
+ return ENV[var] if slice.app?
79
+
80
+ slice_prefixed_var = "#{slice.slice_name.name.gsub("/", "__").upcase}__#{var}"
81
+ ENV[slice_prefixed_var] || ENV[var]
82
+ end
83
+
84
+ def warn_missing_smtp
85
+ message = \
86
+ "No SMTP configuration found for #{slice.slice_name.name} in production; " \
87
+ "falling back to the test delivery method — mail will NOT be sent. " \
88
+ "Set SMTP_ADDRESS (and related SMTP_* variables), or register a custom :mailers provider."
89
+
90
+ slice.app["logger"].warn(message)
91
+ end
92
+ end
93
+
94
+ Dry::System.register_provider_source(
95
+ :mailers,
96
+ source: Mailers,
97
+ group: :hanami,
98
+ provider_options: {namespace: true}
99
+ )
100
+ end
101
+ end
data/lib/hanami/routes.rb CHANGED
@@ -137,6 +137,7 @@ module Hanami
137
137
  # @api private
138
138
  def method_missing(name, *args, **kwargs, &block)
139
139
  return super unless respond_to?(name)
140
+
140
141
  definitions << [name, args, kwargs, block]
141
142
  self
142
143
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/constants"
4
+
5
+ module Hanami
6
+ class Settings
7
+ # A settings store that chains multiple stores with fallback resolution.
8
+ #
9
+ # Each store is tried in order. The first store to return a value wins.
10
+ # Stores must implement `#fetch` with the same signature as `Hash#fetch`.
11
+ #
12
+ # @example
13
+ # # config/app.rb
14
+ # config.settings_store = Hanami::Settings::CompositeStore.new(
15
+ # Hanami::Settings::EnvStore.new,
16
+ # MyCustomStore.new
17
+ # )
18
+ #
19
+ # @api public
20
+ # @since 3.0.0
21
+ class CompositeStore
22
+ # @api private
23
+ Undefined = Dry::Core::Constants::Undefined
24
+
25
+ # @param stores [Array<#fetch>] ordered list of stores to query
26
+ def initialize(*stores)
27
+ @stores = stores
28
+ end
29
+
30
+ # Fetches a value by trying each store in order.
31
+ #
32
+ # @param name [String, Symbol] the setting name
33
+ # @param args [Array] optional default value
34
+ # @yield [name] optional block for default value
35
+ # @return [Object] the setting value
36
+ # @raise [KeyError] if no store has the key and no default is given
37
+ #
38
+ # @api public
39
+ # @since 3.0.0
40
+ def fetch(name, *args, &block)
41
+ @stores.each do |store|
42
+ value = store.fetch(name, Undefined)
43
+ return value unless value.equal?(Undefined)
44
+ end
45
+
46
+ return args.first unless args.empty?
47
+ return yield(name) if block
48
+
49
+ raise KeyError, "key not found: #{name.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -140,8 +140,8 @@ module Hanami
140
140
 
141
141
  begin
142
142
  require slice_settings_require_path
143
- rescue LoadError => e
144
- raise e unless e.path == slice_settings_require_path
143
+ rescue LoadError => exception
144
+ raise exception unless exception.path == slice_settings_require_path
145
145
  end
146
146
  end
147
147
  end
@@ -167,8 +167,8 @@ module Hanami
167
167
  else
168
168
  public_send("#{name}=", value)
169
169
  end
170
- rescue => e # rubocop:disable Style/RescueStandardError
171
- errs[name] = e
170
+ rescue => exception # rubocop:disable Style/RescueStandardError
171
+ errs[name] = exception
172
172
  end
173
173
 
174
174
  raise InvalidSettingsError, errors if errors.any?
@@ -23,7 +23,14 @@ module Hanami
23
23
  attr_reader :path_prefix
24
24
 
25
25
  # @api private
26
- def initialize(routes:, inflector:, middleware_stack: Routing::Middleware::Stack.new, prefix: ::Hanami::Router::DEFAULT_PREFIX, **kwargs, &blk)
26
+ def initialize(
27
+ routes:,
28
+ inflector:,
29
+ middleware_stack: Routing::Middleware::Stack.new,
30
+ prefix: ::Hanami::Router::DEFAULT_PREFIX,
31
+ **kwargs,
32
+ &blk
33
+ )
27
34
  @path_prefix = Hanami::Router::Prefix.new(prefix)
28
35
  @inflector = inflector
29
36
  @middleware_stack = middleware_stack
@@ -233,22 +240,20 @@ module Hanami
233
240
 
234
241
  def route_suffix(suffix)
235
242
  return suffix.sub(LEADING_ID_REGEX, "") if suffix && singular?
243
+
236
244
  suffix
237
245
  end
238
246
  LEADING_ID_REGEX = %r{\A/:id}
239
247
 
240
248
  def key_path_base
241
- @key_path_base ||=
242
- if @options[:to]
243
- @options[:to]
249
+ @key_path_base ||= @options[:to] || begin
250
+ if @resource_scope.any?
251
+ prefix = @resource_scope.join(CONTAINER_KEY_DELIMITER)
252
+ "#{prefix}#{CONTAINER_KEY_DELIMITER}#{@name}"
244
253
  else
245
- @name.to_s.then { |name|
246
- next name unless @resource_scope.any?
247
-
248
- prefix = @resource_scope.join(CONTAINER_KEY_DELIMITER)
249
- "#{prefix}#{CONTAINER_KEY_DELIMITER}#{name}"
250
- }
254
+ @name.to_s
251
255
  end
256
+ end
252
257
  end
253
258
 
254
259
  def route_name(action, prefix)
data/lib/hanami/slice.rb CHANGED
@@ -46,7 +46,6 @@ module Hanami
46
46
  end
47
47
  end
48
48
 
49
- # rubocop:disable Metrics/ModuleLength
50
49
  module ClassMethods
51
50
  # Returns the slice's parent.
52
51
  #
@@ -395,6 +394,26 @@ module Hanami
395
394
  @slices ||= SliceRegistrar.new(self)
396
395
  end
397
396
 
397
+ # Returns an array of this slice and all of its nested slices.
398
+ #
399
+ # The nested slices are returned first (with their more specific namespaces ahead of their
400
+ # parents'), and this slice is returned last. This ordering means a class can be matched to
401
+ # its most specific slice by detecting the first slice whose namespace it belongs to. It is
402
+ # useful when you need to operate across every container, such as in test support code.
403
+ #
404
+ # @example
405
+ # Hanami.app.with_slices # => [Main::Nested::Slice, Main::Slice, Admin::Slice, MyApp::App]
406
+ #
407
+ # @return [Array<Hanami::Slice>] this slice and all of its (nested) slices, this slice last
408
+ #
409
+ # @see #slices
410
+ #
411
+ # @api public
412
+ # @since 3.0.0
413
+ def with_slices
414
+ slices.with_nested + [self]
415
+ end
416
+
398
417
  # @overload register_slice(name, &block)
399
418
  # Registers a nested slice with the given name.
400
419
  #
@@ -797,12 +816,24 @@ module Hanami
797
816
  #
798
817
  # @return [Array] the three-element Rack response array
799
818
  #
819
+ # @raise [Hanami::NoRoutesDefinedError] if the hanami-router gem is missing or
820
+ # no routes are defined.
821
+ #
800
822
  # @see #rack_app
801
823
  #
802
824
  # @api public
803
825
  # @since 2.0.0
804
826
  def call(...)
805
- rack_app.call(...)
827
+ if rack_app
828
+ rack_app.call(...)
829
+ else
830
+ error_message = if Hanami.bundled?("hanami-router")
831
+ "Could not handle this rack request because no routes are defined"
832
+ else
833
+ "Could not handle this rack request because the hanami router gem is missing, please add it"
834
+ end
835
+ raise NoRoutesDefinedError, error_message
836
+ end
806
837
  end
807
838
 
808
839
  private
@@ -893,10 +924,27 @@ module Hanami
893
924
  container.config.provider_dirs = [File.join("config", "providers")]
894
925
  container.config.registrations_dir = File.join("config", "registrations")
895
926
 
927
+ container.config.component_dirs.memoize = memoize_policy
928
+
896
929
  container.config.env = config.env
897
930
  container.config.inflector = config.inflector
898
931
  end
899
932
 
933
+ def memoize_policy
934
+ # Do not memoize components in the test env, so they can be stubbed if required.
935
+ return false if config.env == :test
936
+
937
+ no_memoize = config.no_memoize
938
+
939
+ if no_memoize.respond_to?(:call)
940
+ ->(component) { !no_memoize.call(component) }
941
+ elsif no_memoize.is_a?(Array) && no_memoize.any?
942
+ ->(component) { !component.key.start_with?(*no_memoize) }
943
+ else
944
+ true
945
+ end
946
+ end
947
+
900
948
  def prepare_container_component_dirs
901
949
  return unless root.directory?
902
950
 
@@ -935,6 +983,7 @@ module Hanami
935
983
  )
936
984
  end
937
985
 
986
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
938
987
  def prepare_container_providers
939
988
  # Check here for the `routes` definition only, not `router` itself, because the
940
989
  # `router` requires the slice to be prepared before it can be loaded, and at this
@@ -956,19 +1005,39 @@ module Hanami
956
1005
 
957
1006
  if register_db_provider?
958
1007
  # Only register providers if the user hasn't provided their own
959
- if !container.providers[:db]
1008
+ unless container.providers[:db]
960
1009
  register_provider(:db, namespace: true, source: Providers::DB)
961
1010
  end
962
1011
 
963
- if !container.providers[:relations]
1012
+ unless container.providers[:relations]
964
1013
  register_provider(:relations, namespace: true, source: Providers::Relations)
965
1014
  end
966
1015
  end
967
1016
  end
1017
+
1018
+ if Hanami.bundled?("i18n")
1019
+ require_relative "providers/i18n"
1020
+
1021
+ if register_i18n_provider? && !container.providers[:i18n]
1022
+ register_provider(:i18n, source: Providers::I18n)
1023
+ end
1024
+ end
1025
+
1026
+ if Hanami.bundled?("hanami-mailer")
1027
+ # Explicit require here to ensure the provider source registers itself, to allow the
1028
+ # user to configure it within their own concrete provider file.
1029
+ require_relative "providers/mailers"
1030
+
1031
+ # Only register the provider if the user hasn't provided their own.
1032
+ if register_mailers_provider? && !container.providers[:mailers]
1033
+ register_provider(:mailers, namespace: true, source: Providers::Mailers)
1034
+ end
1035
+ end
968
1036
  end
1037
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
969
1038
 
970
1039
  def prepare_autoloader
971
- autoloader.tag = "hanami.slices.#{slice_name.to_s}"
1040
+ autoloader.tag = "hanami.slices.#{slice_name}"
972
1041
 
973
1042
  # Component dirs are automatically pushed to the autoloader by dry-system's zeitwerk plugin.
974
1043
  # This method adds other dirs that are not otherwise configured as component dirs.
@@ -1009,19 +1078,20 @@ module Hanami
1009
1078
  begin
1010
1079
  require_relative "./routes"
1011
1080
  require routes_require_path
1012
- rescue LoadError => e
1013
- raise e unless e.path == routes_require_path
1081
+ rescue LoadError => exception
1082
+ raise exception unless exception.path == routes_require_path
1014
1083
  end
1015
1084
  end
1016
1085
 
1017
1086
  begin
1018
1087
  routes_class = namespace.const_get(ROUTES_CLASS_NAME)
1019
1088
  routes_class.routes
1020
- rescue NameError => e
1021
- raise e unless e.name == ROUTES_CLASS_NAME.to_sym
1089
+ rescue NameError => exception
1090
+ raise exception unless exception.name == ROUTES_CLASS_NAME.to_sym
1022
1091
  end
1023
1092
  end
1024
1093
 
1094
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1025
1095
  def load_router(inspector:)
1026
1096
  return unless routes
1027
1097
 
@@ -1065,7 +1135,7 @@ module Hanami
1065
1135
  use(Hanami::Webconsole::Middleware, config)
1066
1136
  end
1067
1137
 
1068
- if Hanami.bundled?("hanami-controller")
1138
+ if Hanami.bundled?("hanami-action")
1069
1139
  if config.actions.method_override
1070
1140
  require "rack/method_override"
1071
1141
  use(Rack::MethodOverride)
@@ -1088,6 +1158,7 @@ module Hanami
1088
1158
  middleware_stack.update(config.middleware_stack)
1089
1159
  end
1090
1160
  end
1161
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
1091
1162
 
1092
1163
  def render_errors?
1093
1164
  config.render_errors
@@ -1111,6 +1182,27 @@ module Hanami
1111
1182
  source_path.join("assets").directory?
1112
1183
  end
1113
1184
 
1185
+ # Ensures an i18n provider is available in every slice.
1186
+ #
1187
+ # For the app, this will always be a standalone provider. For slices, this will be a
1188
+ # standalone provider unless the slice is configured to share the app's "i18n" component.
1189
+ def register_i18n_provider?
1190
+ return true if self == app
1191
+
1192
+ !config.shared_app_component_keys.include?("i18n")
1193
+ end
1194
+
1195
+ # Ensures a mailers provider is available in every slice.
1196
+ #
1197
+ # For the app, this will always be a standalone provider. For slices, this will be a
1198
+ # standalone provider unless the slice is configured to share the app's
1199
+ # "mailers.delivery_method" component.
1200
+ def register_mailers_provider?
1201
+ return true if self == app
1202
+
1203
+ !config.shared_app_component_keys.include?("mailers.delivery_method")
1204
+ end
1205
+
1114
1206
  def register_db_provider?
1115
1207
  concrete_db_provider? ||
1116
1208
  db_config_dir? ||
@@ -1143,6 +1235,5 @@ module Hanami
1143
1235
 
1144
1236
  # rubocop:enable Metrics/AbcSize
1145
1237
  end
1146
- # rubocop:enable Metrics/ModuleLength
1147
1238
  end
1148
1239
  end
@@ -57,9 +57,7 @@ module Hanami
57
57
  def slice_for(klass)
58
58
  return unless klass.name
59
59
 
60
- slices = Hanami.app.slices.with_nested + [Hanami.app]
61
-
62
- slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
60
+ Hanami.app.with_slices.detect { |slice| klass.name.start_with?("#{slice.namespace}#{MODULE_DELIMITER}") }
63
61
  end
64
62
  end
65
63
 
@@ -108,8 +108,8 @@ module Hanami
108
108
  slice_class =
109
109
  begin
110
110
  inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
111
- rescue NameError => e
112
- raise e unless e.name.to_s == inflector.camelize(slice_name) || e.name.to_s == :Slice
111
+ rescue NameError => exception
112
+ raise exception unless exception.name.to_s == inflector.camelize(slice_name) || exception.name.to_s == :Slice
113
113
  end
114
114
 
115
115
  register(slice_name, slice_class)