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.
- checksums.yaml +7 -0
- data/hanami-3.0.1/CHANGELOG.md +1701 -0
- data/hanami-3.0.1/LICENSE +20 -0
- data/hanami-3.0.1/README.md +89 -0
- data/hanami-3.0.1/hanami.gemspec +49 -0
- data/hanami-3.0.1/lib/hanami/app.rb +197 -0
- data/hanami-3.0.1/lib/hanami/boot.rb +5 -0
- data/hanami-3.0.1/lib/hanami/config/actions/content_security_policy.rb +140 -0
- data/hanami-3.0.1/lib/hanami/config/actions/cookies.rb +57 -0
- data/hanami-3.0.1/lib/hanami/config/actions/sessions.rb +83 -0
- data/hanami-3.0.1/lib/hanami/config/actions.rb +189 -0
- data/hanami-3.0.1/lib/hanami/config/assets.rb +84 -0
- data/hanami-3.0.1/lib/hanami/config/console.rb +79 -0
- data/hanami-3.0.1/lib/hanami/config/db.rb +35 -0
- data/hanami-3.0.1/lib/hanami/config/i18n.rb +138 -0
- data/hanami-3.0.1/lib/hanami/config/logger.rb +207 -0
- data/hanami-3.0.1/lib/hanami/config/null_config.rb +17 -0
- data/hanami-3.0.1/lib/hanami/config/router.rb +48 -0
- data/hanami-3.0.1/lib/hanami/config/views.rb +103 -0
- data/hanami-3.0.1/lib/hanami/config.rb +564 -0
- data/hanami-3.0.1/lib/hanami/constants.rb +62 -0
- data/hanami-3.0.1/lib/hanami/env.rb +52 -0
- data/hanami-3.0.1/lib/hanami/errors.rb +41 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/i18n_helper.rb +64 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/name_inferrer.rb +34 -0
- data/hanami-3.0.1/lib/hanami/extensions/action/slice_configured_action.rb +190 -0
- data/hanami-3.0.1/lib/hanami/extensions/action.rb +147 -0
- data/hanami-3.0.1/lib/hanami/extensions/db/repo.rb +108 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer/slice_configured_mailer.rb +134 -0
- data/hanami-3.0.1/lib/hanami/extensions/mailer.rb +28 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation/slice_configured_db_operation.rb +90 -0
- data/hanami-3.0.1/lib/hanami/extensions/operation.rb +33 -0
- data/hanami-3.0.1/lib/hanami/extensions/router/errors.rb +58 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/context.rb +298 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/part.rb +92 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/scope.rb +36 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_context.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_helpers.rb +55 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_part.rb +73 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/slice_configured_view.rb +243 -0
- data/hanami-3.0.1/lib/hanami/extensions/view/standard_helpers.rb +26 -0
- data/hanami-3.0.1/lib/hanami/extensions/view.rb +38 -0
- data/hanami-3.0.1/lib/hanami/extensions.rb +30 -0
- data/hanami-3.0.1/lib/hanami/helpers/assets_helper.rb +804 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/form_builder.rb +1389 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper/values.rb +75 -0
- data/hanami-3.0.1/lib/hanami/helpers/form_helper.rb +213 -0
- data/hanami-3.0.1/lib/hanami/helpers/i18n_helper.rb +241 -0
- data/hanami-3.0.1/lib/hanami/logger/rack_formatter.rb +73 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_formatter.rb +80 -0
- data/hanami-3.0.1/lib/hanami/logger/sql_logger.rb +48 -0
- data/hanami-3.0.1/lib/hanami/middleware/assets.rb +21 -0
- data/hanami-3.0.1/lib/hanami/middleware/content_security_policy_nonce.rb +53 -0
- data/hanami-3.0.1/lib/hanami/middleware/public_errors_app.rb +75 -0
- data/hanami-3.0.1/lib/hanami/middleware/render_errors.rb +90 -0
- data/hanami-3.0.1/lib/hanami/port.rb +45 -0
- data/hanami-3.0.1/lib/hanami/prepare.rb +5 -0
- data/hanami-3.0.1/lib/hanami/provider/source.rb +16 -0
- data/hanami-3.0.1/lib/hanami/provider_registrar.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/assets.rb +28 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapter.rb +75 -0
- data/hanami-3.0.1/lib/hanami/providers/db/adapters.rb +66 -0
- data/hanami-3.0.1/lib/hanami/providers/db/config.rb +44 -0
- data/hanami-3.0.1/lib/hanami/providers/db/gateway.rb +87 -0
- data/hanami-3.0.1/lib/hanami/providers/db/sql_adapter.rb +100 -0
- data/hanami-3.0.1/lib/hanami/providers/db.rb +325 -0
- data/hanami-3.0.1/lib/hanami/providers/db_logging.rb +19 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/backend.rb +373 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n/locale/en.yml +57 -0
- data/hanami-3.0.1/lib/hanami/providers/i18n.rb +114 -0
- data/hanami-3.0.1/lib/hanami/providers/inflector.rb +17 -0
- data/hanami-3.0.1/lib/hanami/providers/logger.rb +41 -0
- data/hanami-3.0.1/lib/hanami/providers/mailers.rb +101 -0
- data/hanami-3.0.1/lib/hanami/providers/rack.rb +47 -0
- data/hanami-3.0.1/lib/hanami/providers/relations.rb +31 -0
- data/hanami-3.0.1/lib/hanami/providers/routes.rb +29 -0
- data/hanami-3.0.1/lib/hanami/rake_tasks.rb +54 -0
- data/hanami-3.0.1/lib/hanami/routes.rb +146 -0
- data/hanami-3.0.1/lib/hanami/settings/composite_store.rb +53 -0
- data/hanami-3.0.1/lib/hanami/settings/env_store.rb +32 -0
- data/hanami-3.0.1/lib/hanami/settings.rb +234 -0
- data/hanami-3.0.1/lib/hanami/setup.rb +6 -0
- data/hanami-3.0.1/lib/hanami/slice/router.rb +280 -0
- data/hanami-3.0.1/lib/hanami/slice/routes_helper.rb +37 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/middleware/stack.rb +216 -0
- data/hanami-3.0.1/lib/hanami/slice/routing/resolver.rb +88 -0
- data/hanami-3.0.1/lib/hanami/slice/view_name_inferrer.rb +63 -0
- data/hanami-3.0.1/lib/hanami/slice.rb +1239 -0
- data/hanami-3.0.1/lib/hanami/slice_configurable.rb +72 -0
- data/hanami-3.0.1/lib/hanami/slice_name.rb +111 -0
- data/hanami-3.0.1/lib/hanami/slice_registrar.rb +215 -0
- data/hanami-3.0.1/lib/hanami/universal_logger.rb +250 -0
- data/hanami-3.0.1/lib/hanami/version.rb +45 -0
- data/hanami-3.0.1/lib/hanami/web/rack_logger.rb +126 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.html.erb +584 -0
- data/hanami-3.0.1/lib/hanami/web/welcome.rb +46 -0
- data/hanami-3.0.1/lib/hanami.rb +276 -0
- data/piko-lite-box.gemspec +11 -0
- 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)
|