hanami 2.0.0.alpha7.1 → 2.0.0.beta1.1

Sign up to get free protection for your applications and to get access to all the features.
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