piko-lite-box 0.0.1

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 (99) hide show
  1. checksums.yaml +7 -0
  2. data/hanami-3.0.1/CHANGELOG.md +1701 -0
  3. data/hanami-3.0.1/LICENSE +20 -0
  4. data/hanami-3.0.1/README.md +89 -0
  5. data/hanami-3.0.1/hanami.gemspec +49 -0
  6. data/hanami-3.0.1/lib/hanami/app.rb +197 -0
  7. data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
  8. data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
  9. data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
  10. data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
  11. data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
  12. data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
  13. data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
  14. data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
  15. data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
  16. data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
  17. data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
  18. data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
  19. data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
  20. data/hanami-3.0.1/lib/hanami/config.rb +564 -0
  21. data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
  22. data/hanami-3.0.1/lib/hanami/env.rb +52 -0
  23. data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
  24. data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
  25. data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
  26. data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
  27. data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
  28. data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
  29. data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
  30. data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
  31. data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
  32. data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
  33. data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
  34. data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
  35. data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
  36. data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
  37. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
  38. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
  39. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
  40. data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
  41. data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
  42. data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
  43. data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
  44. data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
  45. data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
  46. data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
  47. data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
  48. data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
  49. data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
  50. data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
  51. data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
  52. data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
  53. data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
  54. data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
  55. data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
  56. data/hanami-3.0.1/lib/hanami/port.rb +45 -0
  57. data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
  58. data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
  59. data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
  60. data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
  61. data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
  62. data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
  63. data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
  64. data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
  65. data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
  66. data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
  67. data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
  68. data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
  69. data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
  70. data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
  71. data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
  72. data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
  73. data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
  74. data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
  75. data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
  76. data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
  77. data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
  78. data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
  79. data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
  80. data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
  81. data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
  82. data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
  83. data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
  84. data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
  85. data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
  86. data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
  87. data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
  88. data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
  89. data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
  90. data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
  91. data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
  92. data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
  93. data/hanami-3.0.1/lib/hanami/version.rb +45 -0
  94. data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
  95. data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
  96. data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
  97. data/hanami-3.0.1/lib/hanami.rb +276 -0
  98. data/piko-lite-box.gemspec +11 -0
  99. metadata +137 -0
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Action
6
+ # Action translation and localization helpers.
7
+ #
8
+ # These helpers are automatically available on `Hanami::Action` when the `i18n` gem is
9
+ # bundled.
10
+ #
11
+ # When relative translation keys (with a leading dot) are given, they are expanded against a
12
+ # the action's name. For example, `t(".not_found")` within `Main::Actions::Posts::Show`
13
+ # becomes `"posts.show.not_found"`.
14
+ #
15
+ # @example Basic translation in an action
16
+ # module Main
17
+ # module Actions
18
+ # module Posts
19
+ # class Create < Main::Action
20
+ # def handle(req, res)
21
+ # res.flash[:notice] = t("messages.post_created")
22
+ # res.redirect_to routes.path(:posts)
23
+ # end
24
+ # end
25
+ # end
26
+ # end
27
+ # end
28
+ #
29
+ # @example Relative key lookup
30
+ # # In Main::Actions::Posts::Show, this looks up "posts.show.not_found"
31
+ # t(".not_found")
32
+ #
33
+ # @see Hanami::Helpers::I18nHelper::Methods
34
+ #
35
+ # @api public
36
+ # @since 3.0.0
37
+ module I18nHelper
38
+ include Hanami::Helpers::I18nHelper::Methods
39
+
40
+ private
41
+
42
+ def _i18n
43
+ i18n
44
+ end
45
+
46
+ def _resolve_i18n_key(key)
47
+ return key unless key.to_s.start_with?(".")
48
+
49
+ key_base = self.class.config.i18n_key_base
50
+
51
+ unless key_base
52
+ raise(
53
+ ::I18n::ArgumentError,
54
+ "Cannot use relative translation key #{key.inspect} outside of a slice-configured action. " \
55
+ "Use an absolute key (without a leading dot) instead."
56
+ )
57
+ end
58
+
59
+ "#{key_base}#{key}"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Action
6
+ # Infers an action's name (e.g. `posts.show`) from its class name relative to its slice
7
+ # namespace.
8
+ #
9
+ # @api private
10
+ class NameInferrer
11
+ class << self
12
+ # @example
13
+ # NameInferrer.call(action_class_name: "Main::Actions::Posts::Show", slice: Main::Slice)
14
+ # # => "posts.show"
15
+ #
16
+ # @param action_class_name [String, nil] the action class name
17
+ # @param slice [Hanami::Slice] the slice the action belongs to
18
+ #
19
+ # @return [String, nil] the inferred name, or nil if `action_class_name` is nil
20
+ def call(action_class_name:, slice:)
21
+ return nil unless action_class_name
22
+
23
+ slice
24
+ .inflector
25
+ .underscore(action_class_name)
26
+ .sub(%r{^#{slice.slice_name.path}#{PATH_DELIMITER}}, "")
27
+ .sub(%r{^#{slice.config.actions.name_inference_base}#{PATH_DELIMITER}}, "")
28
+ .gsub("/", CONTAINER_KEY_DELIMITER)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module Action
6
+ require_relative "name_inferrer"
7
+
8
+ # Provides slice-specific configuration and behavior for any action class defined
9
+ # within a slice's module namespace.
10
+ #
11
+ # @api private
12
+ class SliceConfiguredAction < Module
13
+ attr_reader :slice
14
+
15
+ def initialize(slice)
16
+ super()
17
+ @slice = slice
18
+ end
19
+
20
+ def extended(action_class)
21
+ configure_action(action_class)
22
+ configure_action_i18n(action_class)
23
+ extend_behavior(action_class)
24
+ define_inherited
25
+ define_new
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class.name}[#{slice.name}]>"
30
+ end
31
+
32
+ private
33
+
34
+ # @see Hanami::Extensions::Action::InstanceMethods#initialize
35
+ def define_new
36
+ resolve_view = method(:resolve_paired_view)
37
+ view_context_class = method(:view_context_class)
38
+ resolve_routes = method(:resolve_routes)
39
+ resolve_rack_monitor = method(:resolve_rack_monitor)
40
+ resolve_i18n = method(:resolve_i18n)
41
+
42
+ define_method(:new) do |**kwargs|
43
+ super(
44
+ view: kwargs.fetch(:view) { resolve_view.(self) },
45
+ view_context_class: kwargs.fetch(:view_context_class) { view_context_class.() },
46
+ routes: kwargs.fetch(:routes) { resolve_routes.() },
47
+ rack_monitor: kwargs.fetch(:rack_monitor) { resolve_rack_monitor.() },
48
+ i18n: kwargs.fetch(:i18n) { resolve_i18n.() },
49
+ **kwargs,
50
+ )
51
+ end
52
+ end
53
+
54
+ # Defines an `inherited` hook on this module so each subclass of the slice-configured
55
+ # action gets its own `config.i18n_key_base`, inferred from its class name. Mirrors the
56
+ # approach used by `SliceConfiguredView` for `config.template`.
57
+ def define_inherited
58
+ configure_action_i18n = method(:configure_action_i18n)
59
+
60
+ define_method(:inherited) do |subclass|
61
+ super(subclass)
62
+ configure_action_i18n.call(subclass)
63
+ end
64
+ end
65
+
66
+ def configure_action(action_class)
67
+ action_class.settings.each do |setting|
68
+ # Configure the action from config on the slice, _unless it has already been configured
69
+ # by a parent slice_, and re-configuring it for this slice would make no change.
70
+ #
71
+ # In the case of most slices, its actions config is likely to be the same as its parent
72
+ # (since each slice copies its `config` from its parent), and if we re-apply the config
73
+ # here, then it may possibly overwrite config customisations explicitly made in parent
74
+ # action classes.
75
+ #
76
+ # For example, given an app-level base action class, with custom config:
77
+ #
78
+ # module MyApp
79
+ # class Action < Hanami::Action
80
+ # config.format :json
81
+ # end
82
+ # end
83
+ #
84
+ # And then an action in a slice inheriting from it:
85
+ #
86
+ # module MySlice
87
+ # module Actions
88
+ # class SomeAction < MyApp::Action
89
+ # end
90
+ # end
91
+ # end
92
+ #
93
+ # In this case, `SliceConfiguredAction` will be extended two times:
94
+ #
95
+ # 1. When `MyApp::Action` is defined
96
+ # 2. Again when `MySlice::Actions::SomeAction` is defined
97
+ #
98
+ # If we blindly re-configure all action settings each time `SliceConfiguredAction` is
99
+ # extended, then at the point of (2) above, we'd end up overwriting the custom
100
+ # `config.default_response_format` explicitly configured in the `MyApp::Action` base
101
+ # class, leaving `MySlice::Actions::SomeAction` with `config.default_response_format` of
102
+ # `:html` (the default at `Hanami.app.config.actions.default_response_format`), and not
103
+ # the `:json` value configured in its immediate superclass.
104
+ #
105
+ # This would be surprising behavior, and we want to avoid it.
106
+ slice_value = slice.config.actions.public_send(setting.name)
107
+ parent_value = slice.parent.config.actions.public_send(setting.name) if slice.parent
108
+
109
+ next if slice.parent && slice_value == parent_value
110
+
111
+ action_class.config.public_send(
112
+ :"#{setting.name}=",
113
+ setting.mutable? ? slice_value.dup : slice_value
114
+ )
115
+ end
116
+ end
117
+
118
+ def configure_action_i18n(action_class)
119
+ return unless action_class.config.respond_to?(:i18n_key_base=)
120
+
121
+ action_class.config.i18n_key_base = NameInferrer.call(
122
+ action_class_name: action_class.name, slice:
123
+ )
124
+ end
125
+
126
+ def extend_behavior(action_class)
127
+ if actions_config.sessions.enabled?
128
+ require "hanami/action/session"
129
+ action_class.include Hanami::Action::Session
130
+ end
131
+
132
+ if actions_config.csrf_protection
133
+ require "hanami/action/csrf_protection"
134
+ action_class.include Hanami::Action::CSRFProtection
135
+ end
136
+
137
+ if actions_config.cookies.enabled?
138
+ require "hanami/action/cookies"
139
+ action_class.include Hanami::Action::Cookies
140
+ end
141
+ end
142
+
143
+ def resolve_paired_view(action_class)
144
+ view_identifiers = actions_config.view_name_inferrer.call(
145
+ action_class_name: action_class.name,
146
+ slice: slice
147
+ )
148
+
149
+ view_identifiers.each do |identifier|
150
+ return slice[identifier] if slice.key?(identifier)
151
+ end
152
+
153
+ nil
154
+ end
155
+
156
+ def view_context_class
157
+ if Hanami.bundled?("hanami-view")
158
+ return Extensions::View::Context.context_class(slice)
159
+ end
160
+
161
+ # If hanami-view isn't bundled, try and find a possible third party context class with the
162
+ # same `Views::Context` name (but don't fall back to automatically defining one).
163
+ if slice.namespace.const_defined?(:Views)
164
+ views_namespace = slice.namespace.const_get(:Views)
165
+
166
+ if views_namespace.const_defined?(:Context)
167
+ views_namespace.const_get(:Context)
168
+ end
169
+ end
170
+ end
171
+
172
+ def resolve_routes
173
+ slice.app["routes"] if slice.app.key?("routes")
174
+ end
175
+
176
+ def resolve_rack_monitor
177
+ slice.app["rack.monitor"] if slice.app.key?("rack.monitor")
178
+ end
179
+
180
+ def resolve_i18n
181
+ slice["i18n"] if slice.key?("i18n")
182
+ end
183
+
184
+ def actions_config
185
+ slice.config.actions
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/action"
4
+ require_relative "action/slice_configured_action"
5
+
6
+ module Hanami
7
+ # @api private
8
+ module Extensions
9
+ # Integrated behavior for `Hanami::Action` classes within Hanami apps.
10
+ #
11
+ # @see InstanceMethods
12
+ # @see https://github.com/hanami/hanami-action
13
+ #
14
+ # @api public
15
+ # @since 2.0.0
16
+ module Action
17
+ # @api private
18
+ def self.included(action_class)
19
+ super
20
+
21
+ action_class.extend(Hanami::SliceConfigurable)
22
+ action_class.extend(ClassMethods)
23
+ action_class.prepend(InstanceMethods)
24
+ end
25
+
26
+ # Class methods for app-integrated actions.
27
+ #
28
+ # @since 2.0.0
29
+ module ClassMethods
30
+ # @api private
31
+ def configure_for_slice(slice)
32
+ extend SliceConfiguredAction.new(slice)
33
+ end
34
+ end
35
+
36
+ # Instance methods for app-integrated actions.
37
+ #
38
+ # @since 2.0.0
39
+ module InstanceMethods
40
+ # @api private
41
+ attr_reader :view
42
+
43
+ # @api private
44
+ attr_reader :view_context_class
45
+
46
+ # Returns the slice's {Hanami::Slice::RoutesHelper RoutesHelper}.
47
+ #
48
+ # @return [Hanami::Slice::RoutesHelper]
49
+ #
50
+ # @api public
51
+ # @since 2.0.0
52
+ attr_reader :routes
53
+
54
+ # Returns the slice's `Dry::Monitor::Rack::Middleware`.
55
+ #
56
+ # @return [Dry::Monitor::Rack::Middleware]
57
+ #
58
+ # @api public
59
+ # @since 2.0.0
60
+ attr_reader :rack_monitor
61
+
62
+ # Returns the slice's i18n backend.
63
+ #
64
+ # @return [Hanami::Providers::I18n::Backend]
65
+ #
66
+ # @api public
67
+ # @since x.x.x
68
+ attr_reader :i18n
69
+
70
+ # @overload def initialize(routes: nil, **kwargs)
71
+ # Returns a new `Hanami::Action` with app components injected as dependencies.
72
+ #
73
+ # These dependencies are injected automatically so that a call to `.new` (with no
74
+ # arguments) returns a fully integrated action.
75
+ #
76
+ # @param routes [Hanami::Slice::RoutesHelper]
77
+ #
78
+ # @api public
79
+ # @since 2.0.0
80
+ def initialize(view: nil, view_context_class: nil, rack_monitor: nil, routes: nil, i18n: nil, **kwargs)
81
+ @view = view
82
+ @view_context_class = view_context_class
83
+ @routes = routes
84
+ @rack_monitor = rack_monitor
85
+ @i18n = i18n
86
+
87
+ super(**kwargs)
88
+ end
89
+
90
+ private
91
+
92
+ # @api private
93
+ def build_response(**options)
94
+ options = options.merge(view_options: method(:view_options))
95
+ super(**options)
96
+ end
97
+
98
+ # @api private
99
+ def finish(req, res, halted)
100
+ res.render(view, **req.params) if !halted && auto_render?(res)
101
+ super
102
+ end
103
+
104
+ # @api private
105
+ def _handle_exception(request, _response, _exception)
106
+ super
107
+ rescue StandardError => exception
108
+ rack_monitor&.instrument(:error, exception: exception, env: request.env)
109
+
110
+ raise
111
+ end
112
+
113
+ # @api private
114
+ def view_options(request, response)
115
+ {context: view_context_class&.new(**view_context_options(request, response))}.compact
116
+ end
117
+
118
+ # @api private
119
+ def view_context_options(request, response)
120
+ {request: request}
121
+ end
122
+
123
+ # Returns true if a view should automatically be rendered onto the response body.
124
+ #
125
+ # This may be overridden to enable or disable automatic rendering.
126
+ #
127
+ # @param res [Hanami::Action::Response]
128
+ #
129
+ # @return [Boolean]
130
+ #
131
+ # @since 2.0.0
132
+ # @api public
133
+ def auto_render?(res)
134
+ view && res.body.empty?
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ Hanami::Action.include(Hanami::Extensions::Action)
142
+
143
+ if Hanami.bundled?("i18n")
144
+ require_relative "action/i18n_helper"
145
+ Hanami::Action.setting(:i18n_key_base)
146
+ Hanami::Action.include(Hanami::Extensions::Action::I18nHelper)
147
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami/db"
4
+
5
+ module Hanami
6
+ module Extensions
7
+ # @api private
8
+ # @since 2.2.0
9
+ module DB
10
+ # @api private
11
+ # @since 2.2.0
12
+ module Repo
13
+ def self.included(repo_class)
14
+ super
15
+
16
+ repo_class.extend(Hanami::SliceConfigurable)
17
+ repo_class.extend(ClassMethods)
18
+ end
19
+
20
+ # @api private
21
+ # @since 2.2.0
22
+ module ClassMethods
23
+ def configure_for_slice(slice)
24
+ extend SliceConfiguredRepo.new(slice)
25
+ end
26
+ end
27
+ end
28
+
29
+ # @api private
30
+ # @since 2.2.0
31
+ class SliceConfiguredRepo < Module
32
+ attr_reader :slice
33
+
34
+ def initialize(slice)
35
+ super()
36
+ @slice = slice
37
+ end
38
+
39
+ def extended(repo_class)
40
+ define_inherited
41
+ configure_repo(repo_class)
42
+ define_new
43
+ end
44
+
45
+ def inspect
46
+ "#<#{self.class.name}[#{slice.name}]>"
47
+ end
48
+
49
+ private
50
+
51
+ def define_inherited
52
+ root_for_repo_class = method(:root_for_repo_class)
53
+
54
+ define_method(:inherited) do |subclass|
55
+ super(subclass)
56
+
57
+ unless subclass.root
58
+ root = root_for_repo_class.(subclass)
59
+ subclass.root root if root
60
+ end
61
+ end
62
+ end
63
+
64
+ def configure_repo(repo_class)
65
+ repo_class.struct_namespace struct_namespace
66
+ end
67
+
68
+ def define_new
69
+ resolve_rom = method(:resolve_rom)
70
+
71
+ define_method(:new) do |**kwargs|
72
+ container = kwargs.delete(:container) || resolve_rom.()
73
+ super(container: container, **kwargs)
74
+ end
75
+ end
76
+
77
+ def resolve_rom
78
+ slice["db.rom"]
79
+ end
80
+
81
+ REPO_CLASS_NAME_REGEX = /^(?<name>.+)_(repo|repository)$/
82
+
83
+ def root_for_repo_class(repo_class)
84
+ repo_class_name = slice.inflector.demodulize(repo_class)
85
+ .then { slice.inflector.underscore(_1) }
86
+
87
+ repo_class_match = repo_class_name.match(REPO_CLASS_NAME_REGEX)
88
+ return unless repo_class_match
89
+
90
+ repo_class_match[:name]
91
+ .then { slice.inflector.pluralize(_1) }
92
+ .then(&:to_sym)
93
+ end
94
+
95
+ def struct_namespace
96
+ @struct_namespace ||=
97
+ if slice.namespace.const_defined?(:Structs)
98
+ slice.namespace.const_get(:Structs)
99
+ else
100
+ slice.namespace.const_set(:Structs, Module.new)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ Hanami::DB::Repo.include(Hanami::Extensions::DB::Repo)
@@ -0,0 +1,134 @@
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
+ define_inherited
29
+ end
30
+
31
+ def inspect
32
+ "#<#{self.class.name}[#{slice.name}]>"
33
+ end
34
+
35
+ private
36
+
37
+ def define_new
38
+ resolve_delivery_method = method(:resolve_delivery_method)
39
+
40
+ define_method(:new) do |**kwargs|
41
+ super(
42
+ delivery_method: kwargs.fetch(:delivery_method) { resolve_delivery_method.() },
43
+ **kwargs
44
+ )
45
+ end
46
+ end
47
+
48
+ # Reconfigures the template name for each subclass.
49
+ def define_inherited
50
+ template_name = method(:template_name)
51
+
52
+ define_method(:inherited) do |subclass|
53
+ super(subclass)
54
+
55
+ if Hanami.bundled?("hanami-view") && (template = template_name.(subclass))
56
+ subclass.config.template = template
57
+ end
58
+ end
59
+ end
60
+
61
+ def resolve_delivery_method
62
+ slice["mailers.delivery_method"] if slice.key?("mailers.delivery_method")
63
+ end
64
+
65
+ def configure_mailer(mailer_class)
66
+ return unless Hanami.bundled?("hanami-view")
67
+
68
+ # Build the mailer's view from a slice-configured view class, so it inherits the slice's
69
+ # context, parts, scopes, paths and helpers. The mailer only needs to supply its own
70
+ # template name.
71
+ mailer_class.config.view_class = mailer_view_class
72
+
73
+ if (template = template_name(mailer_class))
74
+ mailer_class.config.template = template
75
+ end
76
+ end
77
+
78
+ # Returns the view class that mailer views are built from, defining a `Mailers::View` within
79
+ # the slice if one is not already present.
80
+ #
81
+ # This mirrors how the view extension defines a `Views::Context`: because the class is
82
+ # defined within the slice's namespace, it is configured automatically (paths, context,
83
+ # parts, scopes, helpers) just like any other view in the slice. A user may define their own
84
+ # `<Slice>::Mailers::View` to customize mailer rendering; it is used when present.
85
+ def mailer_view_class
86
+ namespace = mailers_namespace
87
+
88
+ if namespace.const_defined?(:View, _inherit = false)
89
+ namespace.const_get(:View)
90
+ else
91
+ namespace.const_set(:View, define_mailer_view)
92
+ end
93
+ end
94
+
95
+ # Defines and configures a view class for the slice's mailers, inheriting from the slice's
96
+ # own base view class if present, otherwise from the plain `Hanami::View`.
97
+ def define_mailer_view
98
+ superclass =
99
+ begin
100
+ slice.inflector.constantize("#{slice.namespace.name}::View")
101
+ rescue NameError
102
+ Hanami::View
103
+ end
104
+
105
+ Class.new(superclass).tap { |klass|
106
+ # Call configure_for_slice explicitly, since this is an anonymous class at this point,
107
+ # so the slice cannot be inferred from its name.
108
+ klass.configure_for_slice(slice)
109
+ }
110
+ end
111
+
112
+ def mailers_namespace
113
+ if slice.namespace.const_defined?(:Mailers, _inherit = false)
114
+ slice.namespace.const_get(:Mailers)
115
+ else
116
+ slice.namespace.const_set(:Mailers, Module.new)
117
+ end
118
+ end
119
+
120
+ # Returns the template name for the mailer, retaining the `mailers/` segment from the
121
+ # mailer's namespace so templates resolve under `templates/mailers/` within the slice.
122
+ #
123
+ # For example, `App::Mailers::Welcome` will use template `"mailers/welcome"`.
124
+ def template_name(mailer_class)
125
+ return unless mailer_class.name
126
+
127
+ slice.inflector
128
+ .underscore(mailer_class.name)
129
+ .sub(/^#{slice.slice_name.path}\//, "")
130
+ end
131
+ end
132
+ end
133
+ end
134
+ 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)