hanami 2.0.0.alpha7.1 → 2.0.0.beta1.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 (223) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +457 -237
  3. data/FEATURES.md +30 -9
  4. data/README.md +1 -3
  5. data/hanami.gemspec +21 -11
  6. data/lib/hanami/app.rb +141 -0
  7. data/lib/hanami/assets/application_configuration.rb +10 -4
  8. data/lib/hanami/configuration/actions/content_security_policy.rb +118 -0
  9. data/lib/hanami/configuration/actions/cookies.rb +29 -0
  10. data/lib/hanami/configuration/actions/sessions.rb +46 -0
  11. data/lib/hanami/configuration/actions.rb +23 -12
  12. data/lib/hanami/configuration/logger.rb +13 -10
  13. data/lib/hanami/configuration/router.rb +2 -6
  14. data/lib/hanami/configuration/sessions.rb +1 -1
  15. data/lib/hanami/configuration/views.rb +86 -0
  16. data/lib/hanami/configuration.rb +139 -82
  17. data/lib/hanami/constants.rb +30 -2
  18. data/lib/hanami/errors.rb +4 -1
  19. data/lib/hanami/extensions/action/slice_configured_action.rb +103 -0
  20. data/lib/hanami/extensions/action.rb +79 -0
  21. data/lib/hanami/extensions/view/context.rb +106 -0
  22. data/lib/hanami/extensions/view/slice_configured_context.rb +71 -0
  23. data/lib/hanami/extensions/view/slice_configured_view.rb +107 -0
  24. data/lib/hanami/extensions/view.rb +33 -0
  25. data/lib/hanami/extensions.rb +10 -0
  26. data/lib/hanami/providers/inflector.rb +13 -0
  27. data/lib/hanami/providers/logger.rb +13 -0
  28. data/lib/hanami/providers/rack.rb +27 -0
  29. data/lib/hanami/providers/routes.rb +33 -0
  30. data/lib/hanami/providers/settings.rb +23 -0
  31. data/lib/hanami/rake_tasks.rb +61 -0
  32. data/lib/hanami/routes.rb +51 -0
  33. data/lib/hanami/server.rb +1 -1
  34. data/lib/hanami/settings/dotenv_store.rb +58 -0
  35. data/lib/hanami/settings.rb +90 -0
  36. data/lib/hanami/setup.rb +4 -2
  37. data/lib/hanami/{application → slice}/router.rb +18 -13
  38. data/lib/hanami/slice/routes_helper.rb +37 -0
  39. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  40. data/lib/hanami/slice/routing/resolver.rb +97 -0
  41. data/lib/hanami/slice/view_name_inferrer.rb +63 -0
  42. data/lib/hanami/slice.rb +252 -82
  43. data/lib/hanami/slice_configurable.rb +62 -0
  44. data/lib/hanami/slice_name.rb +111 -0
  45. data/lib/hanami/slice_registrar.rb +119 -0
  46. data/lib/hanami/version.rb +1 -1
  47. data/lib/hanami/web/rack_logger.rb +1 -1
  48. data/lib/hanami.rb +34 -26
  49. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  50. data/spec/integration/assets/cdn_spec.rb +48 -0
  51. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  52. data/spec/integration/assets/helpers_spec.rb +50 -0
  53. data/spec/integration/assets/serve_spec.rb +70 -0
  54. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  55. data/spec/integration/body_parsers_spec.rb +50 -0
  56. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  57. data/spec/integration/cli/assets_spec.rb +14 -0
  58. data/spec/integration/cli/console_spec.rb +105 -0
  59. data/spec/integration/cli/db/apply_spec.rb +74 -0
  60. data/spec/integration/cli/db/console_spec.rb +40 -0
  61. data/spec/integration/cli/db/create_spec.rb +50 -0
  62. data/spec/integration/cli/db/drop_spec.rb +54 -0
  63. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  64. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  65. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  66. data/spec/integration/cli/db/version_spec.rb +38 -0
  67. data/spec/integration/cli/db_spec.rb +21 -0
  68. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  69. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  70. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  71. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  72. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  73. data/spec/integration/cli/destroy_spec.rb +18 -0
  74. data/spec/integration/cli/generate/action_spec.rb +469 -0
  75. data/spec/integration/cli/generate/app_spec.rb +215 -0
  76. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  77. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  78. data/spec/integration/cli/generate/model_spec.rb +290 -0
  79. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  80. data/spec/integration/cli/generate_spec.rb +19 -0
  81. data/spec/integration/cli/new/database_spec.rb +235 -0
  82. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  83. data/spec/integration/cli/new/template_spec.rb +118 -0
  84. data/spec/integration/cli/new/test_spec.rb +274 -0
  85. data/spec/integration/cli/new_spec.rb +970 -0
  86. data/spec/integration/cli/plugins_spec.rb +39 -0
  87. data/spec/integration/cli/routes_spec.rb +49 -0
  88. data/spec/integration/cli/server_spec.rb +626 -0
  89. data/spec/integration/cli/version_spec.rb +85 -0
  90. data/spec/integration/early_hints_spec.rb +35 -0
  91. data/spec/integration/handle_exceptions_spec.rb +244 -0
  92. data/spec/integration/head_spec.rb +89 -0
  93. data/spec/integration/http_headers_spec.rb +29 -0
  94. data/spec/integration/mailer_spec.rb +32 -0
  95. data/spec/integration/middleware_spec.rb +81 -0
  96. data/spec/integration/mount_applications_spec.rb +88 -0
  97. data/spec/integration/project_initializers_spec.rb +40 -0
  98. data/spec/integration/rackup_spec.rb +35 -0
  99. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  100. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  101. data/spec/integration/routing_helpers_spec.rb +61 -0
  102. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  103. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  104. data/spec/integration/security/force_ssl_spec.rb +29 -0
  105. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  106. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  107. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  108. data/spec/integration/send_file_spec.rb +51 -0
  109. data/spec/integration/sessions_spec.rb +247 -0
  110. data/spec/integration/static_middleware_spec.rb +21 -0
  111. data/spec/integration/streaming_spec.rb +41 -0
  112. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  113. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  114. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  115. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  116. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  117. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  118. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  119. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  120. data/spec/new_integration/action/configuration_spec.rb +26 -0
  121. data/spec/new_integration/action/cookies_spec.rb +58 -0
  122. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  123. data/spec/new_integration/action/routes_spec.rb +73 -0
  124. data/spec/new_integration/action/sessions_spec.rb +50 -0
  125. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  126. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  127. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  128. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  129. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  130. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  131. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  132. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  133. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  134. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  135. data/spec/new_integration/container/imports_spec.rb +253 -0
  136. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  137. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  138. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  139. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  140. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  141. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  142. data/spec/new_integration/settings_spec.rb +115 -0
  143. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  144. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  145. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  146. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  147. data/spec/new_integration/slices_spec.rb +101 -0
  148. data/spec/new_integration/view/configuration_spec.rb +49 -0
  149. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  150. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  151. data/spec/new_integration/view/context/request_spec.rb +61 -0
  152. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  153. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  154. data/spec/new_integration/view/inflector_spec.rb +57 -0
  155. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  156. data/spec/new_integration/view/path_spec.rb +56 -0
  157. data/spec/new_integration/view/template_spec.rb +68 -0
  158. data/spec/new_integration/view/views_spec.rb +103 -0
  159. data/spec/spec_helper.rb +16 -0
  160. data/spec/support/app_integration.rb +91 -0
  161. data/spec/support/coverage.rb +1 -0
  162. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  163. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  164. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  166. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  167. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  170. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  171. data/spec/support/rspec.rb +27 -0
  172. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  173. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  174. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  175. data/spec/support/shared_examples/cli/new.rb +97 -0
  176. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  177. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  178. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  179. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  180. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  181. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  182. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  183. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  184. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  185. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  186. data/spec/unit/hanami/configuration_spec.rb +43 -0
  187. data/spec/unit/hanami/env_spec.rb +54 -0
  188. data/spec/unit/hanami/routes_spec.rb +25 -0
  189. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  190. data/spec/unit/hanami/settings_spec.rb +56 -0
  191. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  192. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  193. data/spec/unit/hanami/slice_spec.rb +17 -0
  194. data/spec/unit/hanami/version_spec.rb +7 -0
  195. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  196. metadata +363 -55
  197. data/lib/hanami/application/container/providers/inflector.rb +0 -7
  198. data/lib/hanami/application/container/providers/logger.rb +0 -7
  199. data/lib/hanami/application/container/providers/rack_logger.rb +0 -15
  200. data/lib/hanami/application/container/providers/rack_monitor.rb +0 -12
  201. data/lib/hanami/application/container/providers/routes_helper.rb +0 -9
  202. data/lib/hanami/application/container/providers/settings.rb +0 -7
  203. data/lib/hanami/application/routes.rb +0 -55
  204. data/lib/hanami/application/routes_helper.rb +0 -34
  205. data/lib/hanami/application/routing/resolver/node.rb +0 -50
  206. data/lib/hanami/application/routing/resolver/trie.rb +0 -59
  207. data/lib/hanami/application/routing/resolver.rb +0 -87
  208. data/lib/hanami/application/routing/router.rb +0 -36
  209. data/lib/hanami/application/settings/dotenv_store.rb +0 -60
  210. data/lib/hanami/application/settings.rb +0 -93
  211. data/lib/hanami/application/slice_registrar.rb +0 -106
  212. data/lib/hanami/application.rb +0 -299
  213. data/lib/hanami/boot/source_dirs.rb +0 -44
  214. data/lib/hanami/cli/application/cli.rb +0 -40
  215. data/lib/hanami/cli/application/command.rb +0 -47
  216. data/lib/hanami/cli/application/commands/console.rb +0 -81
  217. data/lib/hanami/cli/application/commands.rb +0 -16
  218. data/lib/hanami/cli/base_command.rb +0 -48
  219. data/lib/hanami/cli/commands/command.rb +0 -171
  220. data/lib/hanami/cli/commands/server.rb +0 -88
  221. data/lib/hanami/cli/commands.rb +0 -65
  222. data/lib/hanami/configuration/middleware.rb +0 -20
  223. data/lib/hanami/configuration/source_dirs.rb +0 -42
data/lib/hanami/slice.rb CHANGED
@@ -1,61 +1,92 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "dry/system/container"
4
- require "hanami/errors"
5
- require "pathname"
6
4
  require_relative "constants"
5
+ require_relative "errors"
6
+ require_relative "slice_name"
7
+ require_relative "slice_registrar"
7
8
 
8
9
  module Hanami
9
- # Distinct area of concern within an Hanami application
10
+ # A slice represents any distinct area of concern within an Hanami app.
11
+ #
12
+ # For smaller apps, a slice may encompass the whole app itself (see
13
+ # {Hanami::App}), whereas larger apps may consist of many slices.
14
+ #
15
+ # Each slice corresponds a single module namespace and a single root directory of source
16
+ # files for loading as components into its container.
17
+ #
18
+ # Each slice has its own configuration, and may optionally have its own settings,
19
+ # routes, as well as other nested slices.
20
+ #
21
+ # Slices expect an Hanami app to be defined (which itself is a slice). They will
22
+ # initialize their configuration as a copy of the app's, and will also configure
23
+ # certain components
24
+ #
25
+ # Slices must be _prepared_ and optionally _booted_ before they can be used (see
26
+ # {ClassMethods.prepare} and {ClassMethods.boot}). A prepared slice will lazily load its
27
+ # components and nested slices (useful for minimising initial load time), whereas a
28
+ # booted slice will eagerly load all its components and nested slices, then freeze its
29
+ # container.
10
30
  #
11
31
  # @since 2.0.0
12
32
  class Slice
13
- def self.inherited(klass)
33
+ @_mutex = Mutex.new
34
+
35
+ def self.inherited(subclass)
14
36
  super
15
37
 
16
- klass.extend(ClassMethods)
38
+ subclass.extend(ClassMethods)
17
39
 
18
- # Eagerly initialize any variables that may be accessed inside the subclass body
19
- klass.instance_variable_set(:@application, Hanami.application)
20
- klass.instance_variable_set(:@container, Class.new(Dry::System::Container))
40
+ @_mutex.synchronize do
41
+ subclass.class_eval do
42
+ @_mutex = Mutex.new
43
+ @container = Class.new(Dry::System::Container)
44
+ end
45
+ end
21
46
  end
22
47
 
23
48
  # rubocop:disable Metrics/ModuleLength
24
49
  module ClassMethods
25
- attr_reader :application, :container
50
+ attr_reader :parent, :container
26
51
 
27
- def slice_name
28
- inflector.underscore(name.split(MODULE_DELIMITER)[-2]).to_sym
52
+ def app
53
+ Hanami.app
29
54
  end
30
55
 
31
- def namespace
32
- inflector.constantize(name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER))
56
+ # A slice's configuration is copied from the app configuration at time of
57
+ # first access. The app should have its configuration completed before
58
+ # slices are loaded.
59
+ def configuration
60
+ @configuration ||= app.configuration.dup.tap do |config|
61
+ # Remove specific values from app that will not apply to this slice
62
+ config.root = nil
63
+ end
33
64
  end
65
+ alias_method :config, :configuration
34
66
 
35
- def namespace_path
36
- inflector.underscore(namespace)
67
+ def slice_name
68
+ @slice_name ||= SliceName.new(self, inflector: method(:inflector))
69
+ end
70
+
71
+ def namespace
72
+ slice_name.namespace
37
73
  end
38
74
 
39
75
  def root
40
- application.root.join(SLICES_DIR, slice_name.to_s)
76
+ configuration.root
41
77
  end
42
78
 
43
79
  def inflector
44
- application.inflector
80
+ configuration.inflector
45
81
  end
46
82
 
47
83
  def prepare(provider_name = nil)
48
- container.prepare(provider_name) and return self if provider_name
49
-
50
- return self if prepared?
51
-
52
- ensure_slice_name
53
- ensure_slice_consts
54
-
55
- prepare_all
56
-
57
- @prepared = true
58
- self
84
+ if provider_name
85
+ container.prepare(provider_name)
86
+ self
87
+ else
88
+ prepare_slice
89
+ end
59
90
  end
60
91
 
61
92
  def prepare_container(&block)
@@ -65,7 +96,10 @@ module Hanami
65
96
  def boot
66
97
  return self if booted?
67
98
 
99
+ prepare
100
+
68
101
  container.finalize!
102
+ slices.each(&:boot)
69
103
 
70
104
  @booted = true
71
105
 
@@ -73,6 +107,7 @@ module Hanami
73
107
  end
74
108
 
75
109
  def shutdown
110
+ slices.each(&:shutdown)
76
111
  container.shutdown!
77
112
  self
78
113
  end
@@ -85,6 +120,14 @@ module Hanami
85
120
  !!@booted
86
121
  end
87
122
 
123
+ def slices
124
+ @slices ||= SliceRegistrar.new(self)
125
+ end
126
+
127
+ def register_slice(...)
128
+ slices.register(...)
129
+ end
130
+
88
131
  def register(...)
89
132
  container.register(...)
90
133
  end
@@ -121,12 +164,12 @@ module Hanami
121
164
  # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
122
165
  raise "Cannot import after booting" if booted?
123
166
 
124
- application = self.application
167
+ slice = self
125
168
 
126
169
  container.after(:configure) do
127
170
  if from.is_a?(Symbol) || from.is_a?(String)
128
171
  slice_name = from
129
- from = application.slices[from.to_sym].container
172
+ from = slice.parent.slices[from.to_sym].container
130
173
  end
131
174
 
132
175
  as = kwargs[:as] || slice_name
@@ -135,8 +178,55 @@ module Hanami
135
178
  end
136
179
  end
137
180
 
181
+ def settings
182
+ @settings ||= load_settings
183
+ end
184
+
185
+ def routes
186
+ @routes ||= load_routes
187
+ end
188
+
189
+ def router(inspector: nil)
190
+ raise SliceLoadError, "#{self} must be prepared before loading the router" unless prepared?
191
+
192
+ @_mutex.synchronize do
193
+ @_router ||= load_router(inspector: inspector)
194
+ end
195
+ end
196
+
197
+ def rack_app
198
+ return unless router
199
+
200
+ @rack_app ||= router.to_rack_app
201
+ end
202
+
203
+ def call(...)
204
+ rack_app.call(...)
205
+ end
206
+
138
207
  private
139
208
 
209
+ # rubocop:disable Metrics/AbcSize
210
+
211
+ def prepare_slice
212
+ return self if prepared?
213
+
214
+ configuration.finalize!
215
+
216
+ ensure_slice_name
217
+ ensure_slice_consts
218
+ ensure_root
219
+
220
+ prepare_all
221
+
222
+ instance_exec(container, &@prepare_container_block) if @prepare_container_block
223
+ container.configured!
224
+
225
+ @prepared = true
226
+
227
+ self
228
+ end
229
+
140
230
  def ensure_slice_name
141
231
  unless name
142
232
  raise SliceLoadError, "Slice must have a class name before it can be prepared"
@@ -152,15 +242,25 @@ module Hanami
152
242
  end
153
243
  end
154
244
 
245
+ def ensure_root
246
+ unless configuration.root
247
+ raise SliceLoadError, "Slice must have a `config.root` before it can be prepared"
248
+ end
249
+ end
250
+
155
251
  def prepare_all
252
+ # Load settings first, to fail early in case of missing/unexpected values
253
+ settings
254
+
255
+ prepare_container_consts
156
256
  prepare_container_plugins
157
257
  prepare_container_base_config
158
258
  prepare_container_component_dirs
159
- prepare_autoloader
160
259
  prepare_container_imports
161
- prepare_container_consts
162
- instance_exec(container, &@prepare_container_block) if @prepare_container_block
163
- container.configured!
260
+ prepare_container_providers
261
+ prepare_autoloader
262
+
263
+ prepare_slices
164
264
  end
165
265
 
166
266
  def prepare_container_plugins
@@ -168,85 +268,155 @@ module Hanami
168
268
 
169
269
  container.use(
170
270
  :zeitwerk,
171
- loader: application.autoloader,
271
+ loader: app.autoloader,
172
272
  run_setup: false,
173
273
  eager_load: false
174
274
  )
175
275
  end
176
276
 
177
277
  def prepare_container_base_config
178
- container.config.name = slice_name
278
+ container.config.name = slice_name.to_sym
179
279
  container.config.root = root
180
280
  container.config.provider_dirs = [File.join("config", "providers")]
181
281
 
182
- container.config.env = application.configuration.env
183
- container.config.inflector = application.configuration.inflector
282
+ container.config.env = configuration.env
283
+ container.config.inflector = configuration.inflector
184
284
  end
185
285
 
186
- def prepare_container_component_dirs # rubocop:disable Metrics/AbcSize
187
- return unless root&.directory?
286
+ def prepare_container_component_dirs
287
+ return unless root.directory?
188
288
 
189
- # Add component dirs for each configured component path
190
- application.configuration.source_dirs.component_dirs.each do |component_dir|
191
- next unless root.join(component_dir.path).directory?
289
+ # Component files in both the root and `lib/` define classes in the slice's
290
+ # namespace
192
291
 
193
- component_dir = component_dir.dup
292
+ if root.join(LIB_DIR)&.directory?
293
+ container.config.component_dirs.add(LIB_DIR) do |dir|
294
+ dir.namespaces.add_root(key: nil, const: slice_name.name)
295
+ end
296
+ end
194
297
 
195
- if component_dir.path == LIB_DIR
196
- # Expect component files in the root of the lib/ component dir to define
197
- # classes inside the slice's namespace.
198
- #
199
- # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
200
- # "foo"
201
- component_dir.namespaces.delete_root
202
- component_dir.namespaces.add_root(key: nil, const: namespace_path)
203
- else
204
- # Expect component files in the root of non-lib/ component dirs to define
205
- # classes inside a namespace matching that dir.
206
- #
207
- # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
208
- # registered as "actions.foo"
298
+ # When auto-registering components in the root, ignore files in `config/` (this is
299
+ # for framework config only), `lib/` (these will be auto-registered as above), as
300
+ # well as the configured no_auto_register_paths
301
+ no_auto_register_paths = ([LIB_DIR, CONFIG_DIR] + configuration.no_auto_register_paths)
302
+ .map { |path|
303
+ path.end_with?(File::SEPARATOR) ? path : "#{path}#{File::SEPARATOR}"
304
+ }
305
+
306
+ # TODO: Change `""` (signifying the root) once dry-rb/dry-system#238 is resolved
307
+ container.config.component_dirs.add("") do |dir|
308
+ dir.namespaces.add_root(key: nil, const: slice_name.name)
309
+ dir.auto_register = -> component {
310
+ relative_path = component.file_path.relative_path_from(root).to_s
311
+ !relative_path.start_with?(*no_auto_register_paths)
312
+
313
+ }
314
+ end
315
+ end
209
316
 
210
- dir_namespace_path = File.join(namespace_path, component_dir.path)
317
+ def prepare_container_imports
318
+ import(
319
+ keys: config.slices.shared_component_keys,
320
+ from: app.container,
321
+ as: nil
322
+ )
323
+ end
211
324
 
212
- component_dir.namespaces.delete_root
213
- component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
214
- end
325
+ def prepare_container_providers
326
+ # Check here for the `routes` definition only, not `router` itself, because the
327
+ # `router` requires the slice to be prepared before it can be loaded, and at this
328
+ # point we're still in the process of preparing.
329
+ if routes
330
+ require_relative "providers/routes"
331
+ register_provider(:routes, source: Hanami::Providers::Routes.for_slice(self))
332
+ end
215
333
 
216
- container.config.component_dirs.add(component_dir)
334
+ if settings
335
+ require_relative "providers/settings"
336
+ register_provider(:settings, source: Hanami::Providers::Settings.for_slice(self))
217
337
  end
218
338
  end
219
339
 
220
- def prepare_autoloader # rubocop:disable Metrics/AbcSize
221
- return unless root&.directory?
340
+ def prepare_autoloader
341
+ # Everything in the slice directory can be autoloaded _except_ `config/`, which is
342
+ # where we keep files loaded specially by the framework as part of slice setup.
343
+ if root.join(CONFIG_DIR)&.directory?
344
+ container.config.autoloader.ignore(root.join(CONFIG_DIR))
345
+ end
346
+ end
222
347
 
223
- # Pass configured autoload dirs to the autoloader
224
- application.configuration.source_dirs.autoload_paths.each do |autoload_path|
225
- next unless root.join(autoload_path).directory?
348
+ def prepare_container_consts
349
+ namespace.const_set :Container, container
350
+ namespace.const_set :Deps, container.injector
351
+ end
226
352
 
227
- dir_namespace_path = File.join(namespace_path, autoload_path)
353
+ def prepare_slices
354
+ slices.load_slices.each(&:prepare)
355
+ slices.freeze
356
+ end
357
+
358
+ def load_settings
359
+ if root.directory?
360
+ settings_require_path = File.join(root, SETTINGS_PATH)
228
361
 
229
- autoloader_namespace = begin
230
- inflector.constantize(inflector.camelize(dir_namespace_path))
231
- rescue NameError
232
- namespace.const_set(inflector.camelize(autoload_path), Module.new)
362
+ begin
363
+ require_relative "./settings"
364
+ require settings_require_path
365
+ rescue LoadError => e
366
+ raise e unless e.path == settings_require_path
233
367
  end
368
+ end
234
369
 
235
- container.config.autoloader.push_dir(
236
- container.root.join(autoload_path),
237
- namespace: autoloader_namespace
238
- )
370
+ begin
371
+ settings_class = namespace.const_get(SETTINGS_CLASS_NAME)
372
+ settings_class.new(configuration.settings_store)
373
+ rescue NameError => e
374
+ raise e unless e.name == SETTINGS_CLASS_NAME.to_sym
239
375
  end
240
376
  end
241
377
 
242
- def prepare_container_imports
243
- container.import from: application.container, as: :application
378
+ def load_routes
379
+ if root.directory?
380
+ routes_require_path = File.join(root, ROUTES_PATH)
381
+
382
+ begin
383
+ require_relative "./routes"
384
+ require routes_require_path
385
+ rescue LoadError => e
386
+ raise e unless e.path == routes_require_path
387
+ end
388
+ end
389
+
390
+ begin
391
+ routes_class = namespace.const_get(ROUTES_CLASS_NAME)
392
+ routes_class.routes
393
+ rescue NameError => e
394
+ raise e unless e.name == ROUTES_CLASS_NAME.to_sym
395
+ end
244
396
  end
245
397
 
246
- def prepare_container_consts
247
- namespace.const_set :Container, container
248
- namespace.const_set :Deps, container.injector
398
+ def load_router(inspector:)
399
+ return unless routes
400
+
401
+ require_relative "slice/router"
402
+
403
+ config = configuration
404
+ rack_monitor = self["rack.monitor"]
405
+
406
+ Slice::Router.new(
407
+ inspector: inspector,
408
+ routes: routes,
409
+ resolver: configuration.router.resolver.new(slice: self),
410
+ **configuration.router.options
411
+ ) do
412
+ use(rack_monitor)
413
+ use(*config.sessions.middleware) if config.sessions.enabled?
414
+
415
+ middleware_stack.update(config.middleware_stack)
416
+ end
249
417
  end
418
+
419
+ # rubocop:enable Metrics/AbcSize
250
420
  end
251
421
  # rubocop:enable Metrics/ModuleLength
252
422
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "errors"
4
+
5
+ module Hanami
6
+ # Calls `configure_for_slice(slice)` on the extended class whenever it is first
7
+ # subclassed within a module namespace corresponding to a slice.
8
+ #
9
+ # @example
10
+ # class BaseClass
11
+ # extend Hanami::SliceConfigurable
12
+ # end
13
+ #
14
+ # # slices/main/lib/my_class.rb
15
+ # module Main
16
+ # class MyClass < BaseClass
17
+ # # Will be called with `Main::Slice`
18
+ # def self.configure_for_slice(slice)
19
+ # # ...
20
+ # end
21
+ # end
22
+ # end
23
+ #
24
+ # @api private
25
+ # @since 2.0.0
26
+ module SliceConfigurable
27
+ class << self
28
+ def extended(klass)
29
+ slice_for = method(:slice_for)
30
+
31
+ inherited_mod = Module.new do
32
+ define_method(:inherited) do |subclass|
33
+ unless Hanami.app?
34
+ raise ComponentLoadError, "Class #{klass} must be defined within an Hanami app"
35
+ end
36
+
37
+ super(subclass)
38
+
39
+ slice = slice_for.(subclass)
40
+ return unless slice
41
+
42
+ subclass.configure_for_slice(slice)
43
+ end
44
+ end
45
+
46
+ klass.singleton_class.prepend(inherited_mod)
47
+ end
48
+
49
+ private
50
+
51
+ def slice_for(klass)
52
+ return unless klass.name
53
+
54
+ slices = Hanami.app.slices.to_a + [Hanami.app]
55
+
56
+ slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
57
+ end
58
+ end
59
+
60
+ def configure_for_slice(slice); end
61
+ end
62
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+
5
+ module Hanami
6
+ # Represents the name of an {App} or {Slice}.
7
+ #
8
+ # @see Slice::ClassMethods#slice_name
9
+ # @see App::ClassMethods#app_name
10
+ #
11
+ # @api public
12
+ # @since 2.0.0
13
+ class SliceName
14
+ # Returns a new SliceName for the slice or app.
15
+ #
16
+ # You must provide an inflector for the manipulation of the name into various formats.
17
+ # This should be given in the form of a Proc that returns the inflector when called.
18
+ # The reason for this is that the inflector may be replaced by the user during the
19
+ # app configuration phase, so the proc should ensure that the current instance
20
+ # of the inflector is returned whenever needed.
21
+ #
22
+ # @param slice [#name] the slice or app object
23
+ # @param inflector [Proc] Proc returning the app's inflector when called
24
+ #
25
+ # @api private
26
+ def initialize(slice, inflector:)
27
+ @slice = slice
28
+ @inflector = inflector
29
+ end
30
+
31
+ # Returns the name of the slice as a downcased, underscored string.
32
+ #
33
+ # This is considered the canonical name of the slice.
34
+ #
35
+ # @example
36
+ # slice_name.name # => "main"
37
+ #
38
+ # @return [String] the slice name
39
+ #
40
+ # @api public
41
+ # @since 2.0.0
42
+ def name
43
+ inflector.underscore(namespace_name)
44
+ end
45
+
46
+ # @api public
47
+ # @since 2.0.0
48
+ alias_method :path, :name
49
+
50
+ # Returns the name of the slice's module namespace.
51
+ #
52
+ # @example
53
+ # slice_name.namespace_name # => "Main"
54
+ #
55
+ # @return [String] the namespace name
56
+ #
57
+ # @api public
58
+ # @since 2.0.0
59
+ def namespace_name
60
+ slice_name.split(MODULE_DELIMITER)[0..-2].join(MODULE_DELIMITER)
61
+ end
62
+
63
+ # Returns the constant for the slice's module namespace.
64
+ #
65
+ # @example
66
+ # slice_name.namespace_const # => Main
67
+ #
68
+ # @return [Module] the namespace module constant
69
+ #
70
+ # @api public
71
+ # @since 2.0.0
72
+ def namespace_const
73
+ inflector.constantize(namespace_name)
74
+ end
75
+
76
+ # @api public
77
+ # @since 2.0.0
78
+ alias_method :namespace, :namespace_const
79
+
80
+ # @api public
81
+ # @since 2.0.0
82
+ alias_method :to_s, :name
83
+
84
+ # Returns the name of a slice as a downcased, underscored symbol.
85
+ #
86
+ # @example
87
+ # slice_name.name # => :main
88
+ #
89
+ # @return [Symbol] the slice name
90
+ #
91
+ # @see name, to_s
92
+ #
93
+ # @api public
94
+ # @since 2.0.0
95
+ def to_sym
96
+ name.to_sym
97
+ end
98
+
99
+ private
100
+
101
+ def slice_name
102
+ @slice.name
103
+ end
104
+
105
+ # The inflector is callable to allow for it to be configured/replaced after this
106
+ # object has been initialized
107
+ def inflector
108
+ @inflector.()
109
+ end
110
+ end
111
+ end