hanami 2.3.2 → 3.0.0.rc1

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -19
  3. data/LICENSE +20 -0
  4. data/README.md +18 -35
  5. data/hanami.gemspec +36 -37
  6. data/lib/hanami/config/db.rb +2 -0
  7. data/lib/hanami/config/i18n.rb +138 -0
  8. data/lib/hanami/config/logger.rb +15 -7
  9. data/lib/hanami/config/null_config.rb +1 -1
  10. data/lib/hanami/config/views.rb +17 -0
  11. data/lib/hanami/config.rb +66 -22
  12. data/lib/hanami/errors.rb +6 -0
  13. data/lib/hanami/extensions/action/slice_configured_action.rb +1 -1
  14. data/lib/hanami/extensions/action.rb +2 -2
  15. data/lib/hanami/extensions/mailer/slice_configured_mailer.rb +120 -0
  16. data/lib/hanami/extensions/mailer.rb +28 -0
  17. data/lib/hanami/extensions/operation/slice_configured_db_operation.rb +2 -0
  18. data/lib/hanami/extensions/view/context.rb +26 -4
  19. data/lib/hanami/extensions/view/part.rb +2 -0
  20. data/lib/hanami/extensions/view/slice_configured_context.rb +7 -0
  21. data/lib/hanami/extensions/view/slice_configured_part.rb +2 -0
  22. data/lib/hanami/extensions/view/slice_configured_view.rb +8 -8
  23. data/lib/hanami/extensions/view/standard_helpers.rb +4 -0
  24. data/lib/hanami/extensions.rb +6 -1
  25. data/lib/hanami/helpers/assets_helper.rb +0 -4
  26. data/lib/hanami/helpers/form_helper.rb +1 -1
  27. data/lib/hanami/helpers/i18n_helper.rb +176 -0
  28. data/lib/hanami/logger/rack_formatter.rb +73 -0
  29. data/lib/hanami/logger/sql_formatter.rb +80 -0
  30. data/lib/hanami/logger/sql_logger.rb +48 -0
  31. data/lib/hanami/middleware/render_errors.rb +2 -2
  32. data/lib/hanami/providers/db.rb +7 -2
  33. data/lib/hanami/providers/db_logging.rb +4 -7
  34. data/lib/hanami/providers/i18n/backend.rb +369 -0
  35. data/lib/hanami/providers/i18n/locale/en.yml +57 -0
  36. data/lib/hanami/providers/i18n.rb +114 -0
  37. data/lib/hanami/providers/mailers.rb +101 -0
  38. data/lib/hanami/routes.rb +1 -0
  39. data/lib/hanami/settings/composite_store.rb +53 -0
  40. data/lib/hanami/settings.rb +4 -4
  41. data/lib/hanami/slice/router.rb +15 -10
  42. data/lib/hanami/slice.rb +71 -11
  43. data/lib/hanami/slice_registrar.rb +2 -2
  44. data/lib/hanami/universal_logger.rb +250 -0
  45. data/lib/hanami/version.rb +1 -1
  46. data/lib/hanami/web/rack_logger.rb +2 -80
  47. data/lib/hanami/web/welcome.html.erb +443 -58
  48. data/lib/hanami.rb +4 -2
  49. metadata +28 -276
  50. data/CODE_OF_CONDUCT.md +0 -74
  51. data/FEATURES.md +0 -269
  52. data/LICENSE.md +0 -22
  53. data/spec/integration/action/cookies_spec.rb +0 -58
  54. data/spec/integration/action/csrf_protection_spec.rb +0 -54
  55. data/spec/integration/action/format_config_spec.rb +0 -129
  56. data/spec/integration/action/routes_spec.rb +0 -71
  57. data/spec/integration/action/sessions_spec.rb +0 -50
  58. data/spec/integration/action/slice_configuration_spec.rb +0 -284
  59. data/spec/integration/action/view_rendering/automatic_rendering_spec.rb +0 -247
  60. data/spec/integration/action/view_rendering/paired_view_inference_spec.rb +0 -115
  61. data/spec/integration/action/view_rendering/view_context_spec.rb +0 -221
  62. data/spec/integration/action/view_rendering_spec.rb +0 -89
  63. data/spec/integration/assets/assets_spec.rb +0 -155
  64. data/spec/integration/assets/cross_slice_assets_helpers_spec.rb +0 -129
  65. data/spec/integration/assets/serve_static_assets_spec.rb +0 -152
  66. data/spec/integration/code_loading/loading_from_app_spec.rb +0 -152
  67. data/spec/integration/code_loading/loading_from_lib_spec.rb +0 -242
  68. data/spec/integration/code_loading/loading_from_slice_spec.rb +0 -165
  69. data/spec/integration/container/application_routes_helper_spec.rb +0 -48
  70. data/spec/integration/container/auto_injection_spec.rb +0 -53
  71. data/spec/integration/container/auto_registration_spec.rb +0 -86
  72. data/spec/integration/container/autoloader_spec.rb +0 -82
  73. data/spec/integration/container/imports_spec.rb +0 -253
  74. data/spec/integration/container/prepare_container_spec.rb +0 -125
  75. data/spec/integration/container/provider_environment_spec.rb +0 -52
  76. data/spec/integration/container/provider_lifecycle_spec.rb +0 -61
  77. data/spec/integration/container/shutdown_spec.rb +0 -91
  78. data/spec/integration/container/standard_providers/rack_provider_spec.rb +0 -44
  79. data/spec/integration/container/standard_providers_spec.rb +0 -124
  80. data/spec/integration/db/auto_registration_spec.rb +0 -39
  81. data/spec/integration/db/commands_spec.rb +0 -80
  82. data/spec/integration/db/db_inflector_spec.rb +0 -57
  83. data/spec/integration/db/db_slices_spec.rb +0 -398
  84. data/spec/integration/db/db_spec.rb +0 -245
  85. data/spec/integration/db/gateways_spec.rb +0 -361
  86. data/spec/integration/db/logging_spec.rb +0 -301
  87. data/spec/integration/db/mappers_spec.rb +0 -84
  88. data/spec/integration/db/provider_config_spec.rb +0 -88
  89. data/spec/integration/db/provider_spec.rb +0 -35
  90. data/spec/integration/db/relations_spec.rb +0 -60
  91. data/spec/integration/db/repo_spec.rb +0 -300
  92. data/spec/integration/db/slices_importing_from_parent.rb +0 -130
  93. data/spec/integration/dotenv_loading_spec.rb +0 -138
  94. data/spec/integration/logging/exception_logging_spec.rb +0 -120
  95. data/spec/integration/logging/notifications_spec.rb +0 -68
  96. data/spec/integration/logging/request_logging_spec.rb +0 -202
  97. data/spec/integration/operations/extension_spec.rb +0 -122
  98. data/spec/integration/rack_app/body_parser_spec.rb +0 -108
  99. data/spec/integration/rack_app/method_override_spec.rb +0 -97
  100. data/spec/integration/rack_app/middleware_spec.rb +0 -720
  101. data/spec/integration/rack_app/non_booted_rack_app_spec.rb +0 -104
  102. data/spec/integration/rack_app/rack_app_spec.rb +0 -442
  103. data/spec/integration/rake_tasks_spec.rb +0 -107
  104. data/spec/integration/router/resource_routes_spec.rb +0 -281
  105. data/spec/integration/settings/access_in_slice_class_body_spec.rb +0 -83
  106. data/spec/integration/settings/access_to_constants_spec.rb +0 -46
  107. data/spec/integration/settings/loading_from_env_spec.rb +0 -188
  108. data/spec/integration/settings/settings_component_loading_spec.rb +0 -113
  109. data/spec/integration/settings/slice_registration_spec.rb +0 -145
  110. data/spec/integration/settings/using_types_spec.rb +0 -80
  111. data/spec/integration/setup_spec.rb +0 -165
  112. data/spec/integration/slices/external_slice_spec.rb +0 -91
  113. data/spec/integration/slices/slice_configuration_spec.rb +0 -42
  114. data/spec/integration/slices/slice_loading_spec.rb +0 -171
  115. data/spec/integration/slices/slice_registrations_spec.rb +0 -80
  116. data/spec/integration/slices/slice_routing_spec.rb +0 -219
  117. data/spec/integration/slices_spec.rb +0 -471
  118. data/spec/integration/view/config/default_context_spec.rb +0 -149
  119. data/spec/integration/view/config/inflector_spec.rb +0 -57
  120. data/spec/integration/view/config/part_class_spec.rb +0 -147
  121. data/spec/integration/view/config/part_namespace_spec.rb +0 -103
  122. data/spec/integration/view/config/paths_spec.rb +0 -119
  123. data/spec/integration/view/config/scope_class_spec.rb +0 -147
  124. data/spec/integration/view/config/scope_namespace_spec.rb +0 -103
  125. data/spec/integration/view/config/template_spec.rb +0 -38
  126. data/spec/integration/view/context/assets_spec.rb +0 -79
  127. data/spec/integration/view/context/inflector_spec.rb +0 -40
  128. data/spec/integration/view/context/request_spec.rb +0 -57
  129. data/spec/integration/view/context/routes_spec.rb +0 -84
  130. data/spec/integration/view/helpers/form_helper_spec.rb +0 -174
  131. data/spec/integration/view/helpers/part_helpers_spec.rb +0 -124
  132. data/spec/integration/view/helpers/scope_helpers_spec.rb +0 -84
  133. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +0 -162
  134. data/spec/integration/view/helpers/user_defined_helpers/scope_helpers_spec.rb +0 -119
  135. data/spec/integration/view/parts/default_rendering_spec.rb +0 -138
  136. data/spec/integration/view/slice_configuration_spec.rb +0 -289
  137. data/spec/integration/view/views_spec.rb +0 -103
  138. data/spec/integration/web/content_security_policy_nonce_spec.rb +0 -251
  139. data/spec/integration/web/render_detailed_errors_spec.rb +0 -107
  140. data/spec/integration/web/render_errors_spec.rb +0 -242
  141. data/spec/integration/web/welcome_view_spec.rb +0 -84
  142. data/spec/spec_helper.rb +0 -28
  143. data/spec/support/app_integration.rb +0 -157
  144. data/spec/support/coverage.rb +0 -1
  145. data/spec/support/matchers.rb +0 -32
  146. data/spec/support/rspec.rb +0 -27
  147. data/spec/unit/hanami/config/actions/content_security_policy_spec.rb +0 -96
  148. data/spec/unit/hanami/config/actions/cookies_spec.rb +0 -46
  149. data/spec/unit/hanami/config/actions/csrf_protection_spec.rb +0 -58
  150. data/spec/unit/hanami/config/actions/default_values_spec.rb +0 -43
  151. data/spec/unit/hanami/config/actions/sessions_spec.rb +0 -48
  152. data/spec/unit/hanami/config/actions_spec.rb +0 -52
  153. data/spec/unit/hanami/config/base_url_spec.rb +0 -25
  154. data/spec/unit/hanami/config/console_spec.rb +0 -22
  155. data/spec/unit/hanami/config/db_spec.rb +0 -38
  156. data/spec/unit/hanami/config/inflector_spec.rb +0 -35
  157. data/spec/unit/hanami/config/logger_spec.rb +0 -195
  158. data/spec/unit/hanami/config/render_detailed_errors_spec.rb +0 -25
  159. data/spec/unit/hanami/config/render_errors_spec.rb +0 -25
  160. data/spec/unit/hanami/config/router_spec.rb +0 -44
  161. data/spec/unit/hanami/config/slices_spec.rb +0 -34
  162. data/spec/unit/hanami/config/views_spec.rb +0 -80
  163. data/spec/unit/hanami/env_spec.rb +0 -37
  164. data/spec/unit/hanami/extensions/view/context_spec.rb +0 -59
  165. data/spec/unit/hanami/helpers/assets_helper/asset_url_spec.rb +0 -120
  166. data/spec/unit/hanami/helpers/assets_helper/audio_tag_spec.rb +0 -132
  167. data/spec/unit/hanami/helpers/assets_helper/favicon_tag_spec.rb +0 -87
  168. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +0 -92
  169. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +0 -143
  170. data/spec/unit/hanami/helpers/assets_helper/stylesheet_tag_spec.rb +0 -126
  171. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +0 -136
  172. data/spec/unit/hanami/helpers/form_helper_spec.rb +0 -2857
  173. data/spec/unit/hanami/port_spec.rb +0 -117
  174. data/spec/unit/hanami/providers/db/config/default_config_spec.rb +0 -100
  175. data/spec/unit/hanami/providers/db/config/gateway_spec.rb +0 -73
  176. data/spec/unit/hanami/providers/db/config_spec.rb +0 -143
  177. data/spec/unit/hanami/router/errors/not_allowed_error_spec.rb +0 -27
  178. data/spec/unit/hanami/router/errors/not_found_error_spec.rb +0 -22
  179. data/spec/unit/hanami/settings/env_store_spec.rb +0 -52
  180. data/spec/unit/hanami/settings_spec.rb +0 -111
  181. data/spec/unit/hanami/slice_configurable_spec.rb +0 -141
  182. data/spec/unit/hanami/slice_name_spec.rb +0 -47
  183. data/spec/unit/hanami/slice_spec.rb +0 -99
  184. data/spec/unit/hanami/web/rack_logger_spec.rb +0 -99
data/lib/hanami/config.rb CHANGED
@@ -6,7 +6,7 @@ require "dry/configurable"
6
6
  require "dry/inflector"
7
7
 
8
8
  require_relative "constants"
9
- require_relative "config/console"
9
+ require_relative "universal_logger"
10
10
 
11
11
  module Hanami
12
12
  # Hanami app config
@@ -105,7 +105,9 @@ module Hanami
105
105
  # @!attribute [rw] no_auto_register_paths
106
106
  # Sets the paths to skip from container auto-registration.
107
107
  #
108
- # Defaults to `["entities"]`.
108
+ # Paths are relative to `/app` or a slice root.
109
+ #
110
+ # Defaults to `["db", "entities", "relations", "structs"]`.
109
111
  #
110
112
  # @return [Array<String>] array of relative paths
111
113
  #
@@ -118,6 +120,36 @@ module Hanami
118
120
  "structs"
119
121
  ]
120
122
 
123
+ # @!attribute [rw] no_memoize
124
+ # Sets the components that should not be memoized in the container.
125
+ #
126
+ # All auto-registered components are memoized by default, meaning each component is resolved
127
+ # only once, with the same instance returned on every subsequent resolution.
128
+ #
129
+ # Use this setting to opt specific components out of memoization. It accepts an array of key
130
+ # prefixes (strings) for the simple case, or a proc for full control. The proc receives a
131
+ # `Dry::System::Component` and should return `true` for components that should _not_ be
132
+ # memoized.
133
+ #
134
+ # Individual components can also opt out by adding a `# memoize: false` magic comment at the
135
+ # top of their source file.
136
+ #
137
+ # Defaults to `[]` (all components memoized).
138
+ #
139
+ # @example Opt out by key prefix
140
+ # config.no_memoize = ["workers", "jobs"]
141
+ #
142
+ # @example Opt out with a proc
143
+ # config.no_memoize = ->(component) {
144
+ # component.key.start_with?("workers")
145
+ # }
146
+ #
147
+ # @return [Array<String>, Proc]
148
+ #
149
+ # @api public
150
+ # @since 2.3.0
151
+ setting :no_memoize, default: []
152
+
121
153
  # @!attribute [rw] base_url
122
154
  # Sets the base URL for app's web server.
123
155
  #
@@ -182,7 +214,7 @@ module Hanami
182
214
  # @since 2.1.0
183
215
  setting :render_error_responses, default: Hash.new(:internal_server_error).merge!(
184
216
  "Hanami::Router::NotAllowedError" => :not_found,
185
- "Hanami::Router::NotFoundError" => :not_found,
217
+ "Hanami::Router::NotFoundError" => :not_found
186
218
  )
187
219
 
188
220
  # @!attribute [rw] console
@@ -220,12 +252,12 @@ module Hanami
220
252
  # @since 2.0.0
221
253
  attr_reader :env
222
254
 
223
- # Returns the app's actions config, or a null config if hanami-controller is not bundled.
255
+ # Returns the app's actions config, or a null config if hanami-action is not bundled.
224
256
  #
225
- # @example When hanami-controller is bundled
257
+ # @example When hanami-action is bundled
226
258
  # config.actions.default_request_format # => :html
227
259
  #
228
- # @example When hanami-controller is not bundled
260
+ # @example When hanami-action is not bundled
229
261
  # config.actions.default_request_format # => NoMethodError
230
262
  #
231
263
  # @return [Hanami::Config::Actions, Hanami::Config::NullConfig]
@@ -248,6 +280,20 @@ module Hanami
248
280
  # @since 2.2.0
249
281
  attr_reader :db
250
282
 
283
+ # Returns the app's i18n config, or a null config if i18n is not bundled.
284
+ #
285
+ # @example When i18n is bundled
286
+ # config.i18n.default_locale # => :en
287
+ #
288
+ # @example When i18n is not bundled
289
+ # config.i18n.default_locale # => NoMethodError
290
+ #
291
+ # @return [Hanami::Config::I18n, Hanami::Config::NullConfig]
292
+ #
293
+ # @api public
294
+ # @since 2.2.0
295
+ attr_reader :i18n
296
+
251
297
  # Returns the app's middleware stack, or nil if hanami-router is not bundled.
252
298
  #
253
299
  # Use this to configure middleware that should apply to all routes.
@@ -320,7 +366,7 @@ module Hanami
320
366
  self.render_detailed_errors = (env == :development)
321
367
  load_from_env
322
368
 
323
- @actions = load_dependent_config("hanami-controller") {
369
+ @actions = load_dependent_config("hanami-action") {
324
370
  require_relative "config/actions"
325
371
  Actions.new
326
372
  }
@@ -332,6 +378,11 @@ module Hanami
332
378
 
333
379
  @db = load_dependent_config("hanami-db") { DB.new }
334
380
 
381
+ @i18n = load_dependent_config("i18n") {
382
+ require_relative "config/i18n"
383
+ I18n.new
384
+ }
385
+
335
386
  @logger = Config::Logger.new(env: env, app_name: app_name)
336
387
 
337
388
  @middleware = load_dependent_config("hanami-router") {
@@ -362,6 +413,7 @@ module Hanami
362
413
  @actions = source.actions.dup
363
414
  @assets = source.assets.dup
364
415
  @db = source.db.dup
416
+ @i18n = source.i18n.dup
365
417
  @logger = source.logger.dup
366
418
  @middleware = source.middleware.dup
367
419
  @router = source.router.dup.tap do |router|
@@ -385,8 +437,6 @@ module Hanami
385
437
  logger.finalize!
386
438
  router.finalize!
387
439
 
388
- use_body_parser_middleware
389
-
390
440
  super
391
441
  end
392
442
 
@@ -434,13 +484,19 @@ module Hanami
434
484
  # Sets the app's logger instance.
435
485
  #
436
486
  # This entirely replaces the default `Dry::Logger::Dispatcher` instance that would have been
487
+ # configured via {#logger}.
488
+ #
489
+ # The provided logger will be wrapped with {Hanami::UniversalLogger} if it doesn't support both
490
+ # keyword arguments and `#tagged` logging, ensuring compatibility with Hanami's logging
491
+ # interface.
437
492
  #
438
493
  # @see #logger_instance
494
+ # @see Hanami::UniversalLogger
439
495
  #
440
496
  # @api public
441
497
  # @since 2.0.0
442
498
  def logger=(logger_instance)
443
- @logger_instance = logger_instance
499
+ @logger_instance = Hanami::UniversalLogger[logger_instance]
444
500
  end
445
501
 
446
502
  # rubocop:enable Style/TrivialAccessors
@@ -484,18 +540,6 @@ module Hanami
484
540
  self.slices = ENV["HANAMI_SLICES"]&.split(",")&.map(&:strip)
485
541
  end
486
542
 
487
- DEFAULT_MIDDLEWARE_PARSERS = {
488
- form: ["multipart/form-data"],
489
- json: ["application/json", "application/vnd.api+json"]
490
- }.freeze
491
- private_constant :DEFAULT_MIDDLEWARE_PARSERS
492
-
493
- def use_body_parser_middleware
494
- return unless Hanami.bundled?("hanami-router") && Hanami.bundled?("hanami-controller")
495
-
496
- middleware.use(:body_parser, [DEFAULT_MIDDLEWARE_PARSERS])
497
- end
498
-
499
543
  def load_dependent_config(gem_name)
500
544
  if Hanami.bundled?(gem_name)
501
545
  yield
data/lib/hanami/errors.rb CHANGED
@@ -25,6 +25,12 @@ module Hanami
25
25
  # @since 2.0.0
26
26
  ComponentLoadError = Class.new(Error)
27
27
 
28
+ # Error raised when there are no routes defined.
29
+ #
30
+ # @api public
31
+ # @since 2.4.0
32
+ NoRoutesDefinedError = Class.new(Error)
33
+
28
34
  # Error raised when unsupported middleware configuration is given.
29
35
  #
30
36
  # @see Hanami::Slice::Routing::Middleware::Stack#use
@@ -118,7 +118,7 @@ module Hanami
118
118
  def resolve_paired_view(action_class)
119
119
  view_identifiers = actions_config.view_name_inferrer.call(
120
120
  action_class_name: action_class.name,
121
- slice: slice,
121
+ slice: slice
122
122
  )
123
123
 
124
124
  view_identifiers.each do |identifier|
@@ -9,7 +9,7 @@ module Hanami
9
9
  # Integrated behavior for `Hanami::Action` classes within Hanami apps.
10
10
  #
11
11
  # @see InstanceMethods
12
- # @see https://github.com/hanami/controller
12
+ # @see https://github.com/hanami/hanami-action
13
13
  #
14
14
  # @api public
15
15
  # @since 2.0.0
@@ -109,7 +109,7 @@ module Hanami
109
109
  end
110
110
 
111
111
  # @api private
112
- def view_context_options(request, response) # rubocop:disable Lint:UnusedMethodArgument
112
+ def view_context_options(request, response)
113
113
  {request: request}
114
114
  end
115
115
 
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Mailer
6
+ # Provides slice-specific configuration and behavior for any mailer class defined within a
7
+ # slice's module namespace.
8
+ #
9
+ # This injects the slice's `"mailers.delivery_method"` component into mailer instances, and
10
+ # points the mailer's view at a slice-configured view class so that mailer templates behave
11
+ # like regular Hanami view templates — sharing the slice's view context, parts, scopes and
12
+ # helpers (including i18n). The only behavior not available to mailer views is
13
+ # request-related state (`request`/`session`/`flash`/`csrf_token`), since mailers are not
14
+ # rendered from a request.
15
+ #
16
+ # @api private
17
+ class SliceConfiguredMailer < Module
18
+ attr_reader :slice
19
+
20
+ def initialize(slice)
21
+ super()
22
+ @slice = slice
23
+ end
24
+
25
+ def extended(mailer_class)
26
+ configure_mailer(mailer_class)
27
+ define_new
28
+ end
29
+
30
+ def inspect
31
+ "#<#{self.class.name}[#{slice.name}]>"
32
+ end
33
+
34
+ private
35
+
36
+ def define_new
37
+ resolve_delivery_method = method(:resolve_delivery_method)
38
+
39
+ define_method(:new) do |**kwargs|
40
+ super(
41
+ delivery_method: kwargs.fetch(:delivery_method) { resolve_delivery_method.() },
42
+ **kwargs
43
+ )
44
+ end
45
+ end
46
+
47
+ def resolve_delivery_method
48
+ slice["mailers.delivery_method"] if slice.key?("mailers.delivery_method")
49
+ end
50
+
51
+ def configure_mailer(mailer_class)
52
+ return unless Hanami.bundled?("hanami-view")
53
+
54
+ # Build the mailer's view from a slice-configured view class, so it inherits the slice's
55
+ # context, parts, scopes, paths and helpers. The mailer only needs to supply its own
56
+ # template name.
57
+ mailer_class.config.view_class = mailer_view_class
58
+
59
+ if (template = template_name(mailer_class))
60
+ mailer_class.config.template = template
61
+ end
62
+ end
63
+
64
+ # Returns the view class that mailer views are built from, defining a `Mailers::View` within
65
+ # the slice if one is not already present.
66
+ #
67
+ # This mirrors how the view extension defines a `Views::Context`: because the class is
68
+ # defined within the slice's namespace, it is configured automatically (paths, context,
69
+ # parts, scopes, helpers) just like any other view in the slice. A user may define their own
70
+ # `<Slice>::Mailers::View` to customize mailer rendering; it is used when present.
71
+ def mailer_view_class
72
+ namespace = mailers_namespace
73
+
74
+ if namespace.const_defined?(:View, _inherit = false)
75
+ namespace.const_get(:View)
76
+ else
77
+ namespace.const_set(:View, define_mailer_view)
78
+ end
79
+ end
80
+
81
+ # Defines and configures a view class for the slice's mailers, inheriting from the slice's
82
+ # own base view class if present, otherwise from the plain `Hanami::View`.
83
+ def define_mailer_view
84
+ superclass =
85
+ begin
86
+ slice.inflector.constantize("#{slice.namespace.name}::View")
87
+ rescue NameError
88
+ Hanami::View
89
+ end
90
+
91
+ Class.new(superclass).tap { |klass|
92
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
93
+ # so the slice cannot be inferred from its name.
94
+ klass.configure_for_slice(slice)
95
+ }
96
+ end
97
+
98
+ def mailers_namespace
99
+ if slice.namespace.const_defined?(:Mailers, _inherit = false)
100
+ slice.namespace.const_get(:Mailers)
101
+ else
102
+ slice.namespace.const_set(:Mailers, Module.new)
103
+ end
104
+ end
105
+
106
+ # Returns the template name for the mailer, retaining the `mailers/` segment from the
107
+ # mailer's namespace so templates resolve under `templates/mailers/` within the slice.
108
+ #
109
+ # For example, `App::Mailers::Welcome` will use template `"mailers/welcome"`.
110
+ def template_name(mailer_class)
111
+ return unless mailer_class.name
112
+
113
+ slice.inflector
114
+ .underscore(mailer_class.name)
115
+ .sub(/^#{slice.slice_name.path}\//, "")
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/mailer"
4
+ require_relative "mailer/slice_configured_mailer"
5
+
6
+ module Hanami
7
+ module Extensions
8
+ # Integrated behavior for `Hanami::Mailer` classes within Hanami apps.
9
+ #
10
+ # @api private
11
+ module Mailer
12
+ def self.included(mailer_class)
13
+ super
14
+
15
+ mailer_class.extend(Hanami::SliceConfigurable)
16
+ mailer_class.extend(ClassMethods)
17
+ end
18
+
19
+ module ClassMethods
20
+ def configure_for_slice(slice)
21
+ extend SliceConfiguredMailer.new(slice)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ Hanami::Mailer.include(Hanami::Extensions::Mailer)
@@ -51,6 +51,7 @@ module Hanami
51
51
  attr_reader :rom
52
52
 
53
53
  def initialize(rom:, **)
54
+ super()
54
55
  @rom = rom
55
56
  end
56
57
  end
@@ -59,6 +60,7 @@ module Hanami
59
60
  attr_reader :slice
60
61
 
61
62
  def initialize(slice)
63
+ super()
62
64
  @slice = slice
63
65
  end
64
66
 
@@ -25,6 +25,8 @@ module Hanami
25
25
  end
26
26
 
27
27
  views_namespace.const_set(:Context, Class.new(context_superclass(slice)).tap { |klass|
28
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
29
+ # so the slice cannot be inferred from its name.
28
30
  klass.configure_for_slice(slice)
29
31
  })
30
32
  end
@@ -40,8 +42,8 @@ module Hanami
40
42
  slice.inflector.constantize(
41
43
  slice.inflector.camelize("#{slice.app.slice_name.name}/views/context")
42
44
  )
43
- rescue NameError => e
44
- raise e unless %i[Views Context].include?(e.name)
45
+ rescue NameError => exception
46
+ raise exception unless %i[Views Context].include?(exception.name)
45
47
 
46
48
  Hanami::View::Context
47
49
  end
@@ -95,17 +97,19 @@ module Hanami
95
97
  #
96
98
  # @api private
97
99
  # @since 2.1.0
98
- def initialize( # rubocop:disable Metrics/ParameterLists
100
+ def initialize(
99
101
  inflector: nil,
100
102
  routes: nil,
101
103
  assets: nil,
102
104
  request: nil,
105
+ i18n: nil,
103
106
  **args
104
107
  )
105
108
  @inflector = inflector
106
109
  @routes = routes
107
110
  @assets = assets
108
111
  @request = request
112
+ @i18n = i18n
109
113
 
110
114
  @content_for = {}
111
115
 
@@ -157,7 +161,9 @@ module Hanami
157
161
  # @since 2.1.0
158
162
  def request
159
163
  unless @request
160
- raise Hanami::ComponentLoadError, "Request not available. Only views rendered from Hanami::Action instances have a request."
164
+ raise Hanami::ComponentLoadError, <<~STR
165
+ Request not available. Only views rendered from Hanami::Action instances have a request.
166
+ STR
161
167
  end
162
168
 
163
169
  @request
@@ -190,6 +196,22 @@ module Hanami
190
196
  @routes
191
197
  end
192
198
 
199
+ # Returns the slice's i18n backend.
200
+ #
201
+ # @return [Hanami::Providers::I18n::Backend] the i18n backend
202
+ #
203
+ # @raise [Hanami::ComponentLoadError] if the i18n gem is not bundled
204
+ #
205
+ # @api public
206
+ # @since x.x.x
207
+ def i18n
208
+ unless @i18n
209
+ raise Hanami::ComponentLoadError, "the i18n gem is required to access translations"
210
+ end
211
+
212
+ @i18n
213
+ end
214
+
193
215
  # @overload content_for(key, value = nil, &block)
194
216
  # Stores a string or block of template markup for later use.
195
217
  #
@@ -27,6 +27,8 @@ module Hanami
27
27
  extend SliceConfiguredPart.new(slice)
28
28
 
29
29
  const_set :PartHelpers, Class.new(PartHelpers) { |klass|
30
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
31
+ # so the slice cannot be inferred from its name.
30
32
  klass.configure_for_slice(slice)
31
33
  }
32
34
  end
@@ -39,15 +39,18 @@ module Hanami
39
39
  # - the configured inflector as `inflector`
40
40
  # - "routes" from the app container as `routes`
41
41
  # - "assets" from the app container as `assets`
42
+ # - "i18n" from the slice container as `i18n`
42
43
  def define_new
43
44
  inflector = slice.inflector
44
45
  resolve_routes = method(:resolve_routes)
45
46
  resolve_assets = method(:resolve_assets)
47
+ resolve_i18n = method(:resolve_i18n)
46
48
 
47
49
  define_method :new do |**kwargs|
48
50
  kwargs[:inflector] ||= inflector
49
51
  kwargs[:routes] ||= resolve_routes.()
50
52
  kwargs[:assets] ||= resolve_assets.()
53
+ kwargs[:i18n] ||= resolve_i18n.()
51
54
 
52
55
  super(**kwargs)
53
56
  end
@@ -60,6 +63,10 @@ module Hanami
60
63
  def resolve_assets
61
64
  slice["assets"] if slice.key?("assets")
62
65
  end
66
+
67
+ def resolve_i18n
68
+ slice["i18n"] if slice.key?("i18n")
69
+ end
63
70
  end
64
71
  end
65
72
  end
@@ -57,6 +57,8 @@ module Hanami
57
57
  define_method(:new) do |**args|
58
58
  return super(**args) if args.key?(:rendering)
59
59
 
60
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
61
+ # so the slice cannot be inferred from its name.
60
62
  slice_rendering = Class.new(Hanami::View)
61
63
  .configure_for_slice(slice)
62
64
  .new
@@ -49,12 +49,12 @@ module Hanami
49
49
 
50
50
  begin
51
51
  slice.app.namespace.const_get(:View, false)
52
- rescue NameError => e
53
- raise unless e.name == :View
52
+ rescue NameError => exception
53
+ raise unless exception.name == :View
54
54
  end
55
55
  end
56
56
 
57
- # rubocop:disable Metrics/AbcSize
57
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
58
58
  def configure_view(view_class)
59
59
  view_class.settings.each do |setting|
60
60
  next unless slice.config.views.respond_to?(setting.name)
@@ -134,7 +134,7 @@ module Hanami
134
134
  view_class.config.scope_namespace = scope_namespace
135
135
  end
136
136
  end
137
- # rubocop:enable Metrics/AbcSize
137
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
138
138
 
139
139
  def define_inherited
140
140
  template_name = method(:template_name)
@@ -185,8 +185,8 @@ module Hanami
185
185
  views_namespace.const_get(:Part)
186
186
  else
187
187
  views_namespace.const_set(:Part, Class.new(part_superclass).tap { |klass|
188
- # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
189
- # called via `.inherited`, because the class is anonymous at this point
188
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
189
+ # so the slice cannot be inferred from its name.
190
190
  klass.configure_for_slice(slice)
191
191
  })
192
192
  end
@@ -208,8 +208,8 @@ module Hanami
208
208
  views_namespace.const_get(:Scope)
209
209
  else
210
210
  views_namespace.const_set(:Scope, Class.new(scope_superclass).tap { |klass|
211
- # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
212
- # called via `.inherited`, since the class is anonymous at this point
211
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
212
+ # so the slice cannot be inferred from its name.
213
213
  klass.configure_for_slice(slice)
214
214
  })
215
215
  end
@@ -16,6 +16,10 @@ module Hanami
16
16
  if Hanami.bundled?("hanami-assets")
17
17
  include Helpers::AssetsHelper
18
18
  end
19
+
20
+ if Hanami.bundled?("i18n")
21
+ include Helpers::I18nHelper
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -4,7 +4,7 @@ if Hanami.bundled?("hanami-db")
4
4
  require_relative "extensions/db/repo"
5
5
  end
6
6
 
7
- if Hanami.bundled?("hanami-controller")
7
+ if Hanami.bundled?("hanami-action")
8
8
  require_relative "extensions/action"
9
9
  end
10
10
 
@@ -20,6 +20,11 @@ if Hanami.bundled?("hanami-router")
20
20
  require_relative "extensions/router/errors"
21
21
  end
22
22
 
23
+ if Hanami.bundled?("hanami-mailer")
24
+ require "hanami/mailer"
25
+ require_relative "extensions/mailer"
26
+ end
27
+
23
28
  if Hanami.bundled?("dry-operation")
24
29
  require_relative "extensions/operation"
25
30
  end
@@ -4,8 +4,6 @@ require "uri"
4
4
  require "hanami/view"
5
5
  require_relative "../constants"
6
6
 
7
- # rubocop:disable Metrics/ModuleLength
8
-
9
7
  module Hanami
10
8
  module Helpers
11
9
  # HTML assets helpers
@@ -804,5 +802,3 @@ module Hanami
804
802
  end
805
803
  end
806
804
  end
807
-
808
- # rubocop:enable Metrics/ModuleLength
@@ -32,7 +32,7 @@ module Hanami
32
32
 
33
33
  # CSRF Token session key
34
34
  #
35
- # This name of this key is shared with the hanami and hanami-controller gems.
35
+ # This name of this key is shared with the hanami and hanami-action gems.
36
36
  #
37
37
  # @since 2.1.0
38
38
  # @api private