hanami 2.0.0.alpha8 → 2.0.0.beta1

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 (224) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +442 -241
  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 +5 -5
  9. data/lib/hanami/configuration/actions/cookies.rb +2 -2
  10. data/lib/hanami/configuration/actions.rb +10 -4
  11. data/lib/hanami/configuration/logger.rb +4 -4
  12. data/lib/hanami/configuration/router.rb +2 -6
  13. data/lib/hanami/configuration/sessions.rb +1 -1
  14. data/lib/hanami/configuration/views.rb +9 -4
  15. data/lib/hanami/configuration.rb +118 -46
  16. data/lib/hanami/constants.rb +24 -2
  17. data/lib/hanami/errors.rb +1 -1
  18. data/lib/hanami/{application → extensions}/action/slice_configured_action.rb +9 -9
  19. data/lib/hanami/extensions/action.rb +79 -0
  20. data/lib/hanami/extensions/view/context.rb +106 -0
  21. data/lib/hanami/{application → extensions}/view/slice_configured_context.rb +10 -10
  22. data/lib/hanami/{application → extensions}/view/slice_configured_view.rb +12 -6
  23. data/lib/hanami/extensions/view.rb +33 -0
  24. data/lib/hanami/extensions.rb +10 -0
  25. data/lib/hanami/providers/inflector.rb +13 -0
  26. data/lib/hanami/providers/logger.rb +13 -0
  27. data/lib/hanami/providers/rack.rb +27 -0
  28. data/lib/hanami/providers/routes.rb +33 -0
  29. data/lib/hanami/providers/settings.rb +23 -0
  30. data/lib/hanami/rake_tasks.rb +61 -0
  31. data/lib/hanami/routes.rb +51 -0
  32. data/lib/hanami/server.rb +1 -1
  33. data/lib/hanami/settings/dotenv_store.rb +58 -0
  34. data/lib/hanami/settings.rb +90 -0
  35. data/lib/hanami/setup.rb +4 -2
  36. data/lib/hanami/{application → slice}/router.rb +18 -13
  37. data/lib/hanami/slice/routes_helper.rb +37 -0
  38. data/lib/hanami/{application → slice}/routing/middleware/stack.rb +43 -5
  39. data/lib/hanami/slice/routing/resolver.rb +97 -0
  40. data/lib/hanami/{application → slice}/view_name_inferrer.rb +3 -3
  41. data/lib/hanami/slice.rb +246 -73
  42. data/lib/hanami/slice_configurable.rb +4 -17
  43. data/lib/hanami/slice_name.rb +6 -6
  44. data/lib/hanami/slice_registrar.rb +119 -0
  45. data/lib/hanami/version.rb +1 -1
  46. data/lib/hanami/web/rack_logger.rb +1 -1
  47. data/lib/hanami.rb +34 -26
  48. data/spec/integration/application_middleware_stack_spec.rb +84 -0
  49. data/spec/integration/assets/cdn_spec.rb +48 -0
  50. data/spec/integration/assets/fingerprint_spec.rb +42 -0
  51. data/spec/integration/assets/helpers_spec.rb +50 -0
  52. data/spec/integration/assets/serve_spec.rb +70 -0
  53. data/spec/integration/assets/subresource_integrity_spec.rb +54 -0
  54. data/spec/integration/body_parsers_spec.rb +50 -0
  55. data/spec/integration/cli/assets/precompile_spec.rb +147 -0
  56. data/spec/integration/cli/assets_spec.rb +14 -0
  57. data/spec/integration/cli/console_spec.rb +105 -0
  58. data/spec/integration/cli/db/apply_spec.rb +74 -0
  59. data/spec/integration/cli/db/console_spec.rb +40 -0
  60. data/spec/integration/cli/db/create_spec.rb +50 -0
  61. data/spec/integration/cli/db/drop_spec.rb +54 -0
  62. data/spec/integration/cli/db/migrate_spec.rb +108 -0
  63. data/spec/integration/cli/db/prepare_spec.rb +36 -0
  64. data/spec/integration/cli/db/rollback_spec.rb +96 -0
  65. data/spec/integration/cli/db/version_spec.rb +38 -0
  66. data/spec/integration/cli/db_spec.rb +21 -0
  67. data/spec/integration/cli/destroy/action_spec.rb +143 -0
  68. data/spec/integration/cli/destroy/app_spec.rb +118 -0
  69. data/spec/integration/cli/destroy/mailer_spec.rb +74 -0
  70. data/spec/integration/cli/destroy/migration_spec.rb +70 -0
  71. data/spec/integration/cli/destroy/model_spec.rb +113 -0
  72. data/spec/integration/cli/destroy_spec.rb +18 -0
  73. data/spec/integration/cli/generate/action_spec.rb +469 -0
  74. data/spec/integration/cli/generate/app_spec.rb +215 -0
  75. data/spec/integration/cli/generate/mailer_spec.rb +189 -0
  76. data/spec/integration/cli/generate/migration_spec.rb +72 -0
  77. data/spec/integration/cli/generate/model_spec.rb +290 -0
  78. data/spec/integration/cli/generate/secret_spec.rb +56 -0
  79. data/spec/integration/cli/generate_spec.rb +19 -0
  80. data/spec/integration/cli/new/database_spec.rb +235 -0
  81. data/spec/integration/cli/new/hanami_head_spec.rb +27 -0
  82. data/spec/integration/cli/new/template_spec.rb +118 -0
  83. data/spec/integration/cli/new/test_spec.rb +274 -0
  84. data/spec/integration/cli/new_spec.rb +970 -0
  85. data/spec/integration/cli/plugins_spec.rb +39 -0
  86. data/spec/integration/cli/routes_spec.rb +49 -0
  87. data/spec/integration/cli/server_spec.rb +626 -0
  88. data/spec/integration/cli/version_spec.rb +85 -0
  89. data/spec/integration/early_hints_spec.rb +35 -0
  90. data/spec/integration/handle_exceptions_spec.rb +244 -0
  91. data/spec/integration/head_spec.rb +89 -0
  92. data/spec/integration/http_headers_spec.rb +29 -0
  93. data/spec/integration/mailer_spec.rb +32 -0
  94. data/spec/integration/middleware_spec.rb +81 -0
  95. data/spec/integration/mount_applications_spec.rb +88 -0
  96. data/spec/integration/project_initializers_spec.rb +40 -0
  97. data/spec/integration/rackup_spec.rb +35 -0
  98. data/spec/integration/rake/with_minitest_spec.rb +67 -0
  99. data/spec/integration/rake/with_rspec_spec.rb +69 -0
  100. data/spec/integration/routing_helpers_spec.rb +61 -0
  101. data/spec/integration/security/content_security_policy_spec.rb +46 -0
  102. data/spec/integration/security/csrf_protection_spec.rb +42 -0
  103. data/spec/integration/security/force_ssl_spec.rb +29 -0
  104. data/spec/integration/security/x_content_type_options_spec.rb +46 -0
  105. data/spec/integration/security/x_frame_options_spec.rb +46 -0
  106. data/spec/integration/security/x_xss_protection_spec.rb +46 -0
  107. data/spec/integration/send_file_spec.rb +51 -0
  108. data/spec/integration/sessions_spec.rb +247 -0
  109. data/spec/integration/static_middleware_spec.rb +21 -0
  110. data/spec/integration/streaming_spec.rb +41 -0
  111. data/spec/integration/unsafe_send_file_spec.rb +52 -0
  112. data/spec/isolation/hanami/application/already_configured_spec.rb +19 -0
  113. data/spec/isolation/hanami/application/inherit_anonymous_class_spec.rb +10 -0
  114. data/spec/isolation/hanami/application/inherit_concrete_class_spec.rb +14 -0
  115. data/spec/isolation/hanami/application/not_configured_spec.rb +9 -0
  116. data/spec/isolation/hanami/application/routes/configured_spec.rb +44 -0
  117. data/spec/isolation/hanami/application/routes/not_configured_spec.rb +16 -0
  118. data/spec/isolation/hanami/boot/success_spec.rb +50 -0
  119. data/spec/new_integration/action/configuration_spec.rb +26 -0
  120. data/spec/new_integration/action/cookies_spec.rb +58 -0
  121. data/spec/new_integration/action/csrf_protection_spec.rb +54 -0
  122. data/spec/new_integration/action/routes_spec.rb +73 -0
  123. data/spec/new_integration/action/sessions_spec.rb +50 -0
  124. data/spec/new_integration/action/view_integration_spec.rb +165 -0
  125. data/spec/new_integration/action/view_rendering/automatic_rendering_spec.rb +247 -0
  126. data/spec/new_integration/action/view_rendering/paired_view_inference_spec.rb +115 -0
  127. data/spec/new_integration/action/view_rendering_spec.rb +107 -0
  128. data/spec/new_integration/code_loading/loading_from_app_spec.rb +152 -0
  129. data/spec/new_integration/code_loading/loading_from_slice_spec.rb +165 -0
  130. data/spec/new_integration/container/application_routes_helper_spec.rb +48 -0
  131. data/spec/new_integration/container/auto_injection_spec.rb +53 -0
  132. data/spec/new_integration/container/auto_registration_spec.rb +86 -0
  133. data/spec/new_integration/container/autoloader_spec.rb +80 -0
  134. data/spec/new_integration/container/imports_spec.rb +253 -0
  135. data/spec/new_integration/container/prepare_container_spec.rb +123 -0
  136. data/spec/new_integration/container/shutdown_spec.rb +91 -0
  137. data/spec/new_integration/container/standard_bootable_components_spec.rb +124 -0
  138. data/spec/new_integration/rack_app/middleware_spec.rb +215 -0
  139. data/spec/new_integration/rack_app/non_booted_rack_app_spec.rb +105 -0
  140. data/spec/new_integration/rack_app/rack_app_spec.rb +524 -0
  141. data/spec/new_integration/settings_spec.rb +115 -0
  142. data/spec/new_integration/slices/external_slice_spec.rb +92 -0
  143. data/spec/new_integration/slices/slice_configuration_spec.rb +40 -0
  144. data/spec/new_integration/slices/slice_routing_spec.rb +226 -0
  145. data/spec/new_integration/slices/slice_settings_spec.rb +141 -0
  146. data/spec/new_integration/slices_spec.rb +101 -0
  147. data/spec/new_integration/view/configuration_spec.rb +49 -0
  148. data/spec/new_integration/view/context/assets_spec.rb +67 -0
  149. data/spec/new_integration/view/context/inflector_spec.rb +48 -0
  150. data/spec/new_integration/view/context/request_spec.rb +61 -0
  151. data/spec/new_integration/view/context/routes_spec.rb +86 -0
  152. data/spec/new_integration/view/context/settings_spec.rb +50 -0
  153. data/spec/new_integration/view/inflector_spec.rb +57 -0
  154. data/spec/new_integration/view/part_namespace_spec.rb +96 -0
  155. data/spec/new_integration/view/path_spec.rb +56 -0
  156. data/spec/new_integration/view/template_spec.rb +68 -0
  157. data/spec/new_integration/view/views_spec.rb +103 -0
  158. data/spec/spec_helper.rb +16 -0
  159. data/spec/support/app_integration.rb +91 -0
  160. data/spec/support/coverage.rb +1 -0
  161. data/spec/support/fixtures/hanami-plugin/Gemfile +8 -0
  162. data/spec/support/fixtures/hanami-plugin/README.md +35 -0
  163. data/spec/support/fixtures/hanami-plugin/Rakefile +4 -0
  164. data/spec/support/fixtures/hanami-plugin/bin/console +15 -0
  165. data/spec/support/fixtures/hanami-plugin/bin/setup +8 -0
  166. data/spec/support/fixtures/hanami-plugin/hanami-plugin.gemspec +28 -0
  167. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/cli.rb +19 -0
  168. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin/version.rb +7 -0
  169. data/spec/support/fixtures/hanami-plugin/lib/hanami/plugin.rb +8 -0
  170. data/spec/support/rspec.rb +27 -0
  171. data/spec/support/shared_examples/cli/generate/app.rb +494 -0
  172. data/spec/support/shared_examples/cli/generate/migration.rb +32 -0
  173. data/spec/support/shared_examples/cli/generate/model.rb +81 -0
  174. data/spec/support/shared_examples/cli/new.rb +97 -0
  175. data/spec/unit/hanami/configuration/actions/content_security_policy_spec.rb +102 -0
  176. data/spec/unit/hanami/configuration/actions/cookies_spec.rb +46 -0
  177. data/spec/unit/hanami/configuration/actions/csrf_protection_spec.rb +57 -0
  178. data/spec/unit/hanami/configuration/actions/default_values_spec.rb +52 -0
  179. data/spec/unit/hanami/configuration/actions/sessions_spec.rb +50 -0
  180. data/spec/unit/hanami/configuration/actions_spec.rb +78 -0
  181. data/spec/unit/hanami/configuration/base_url_spec.rb +25 -0
  182. data/spec/unit/hanami/configuration/inflector_spec.rb +35 -0
  183. data/spec/unit/hanami/configuration/logger_spec.rb +203 -0
  184. data/spec/unit/hanami/configuration/views_spec.rb +120 -0
  185. data/spec/unit/hanami/configuration_spec.rb +43 -0
  186. data/spec/unit/hanami/env_spec.rb +54 -0
  187. data/spec/unit/hanami/routes_spec.rb +25 -0
  188. data/spec/unit/hanami/settings/dotenv_store_spec.rb +119 -0
  189. data/spec/unit/hanami/settings_spec.rb +56 -0
  190. data/spec/unit/hanami/slice_configurable_spec.rb +104 -0
  191. data/spec/unit/hanami/slice_name_spec.rb +47 -0
  192. data/spec/unit/hanami/slice_spec.rb +17 -0
  193. data/spec/unit/hanami/version_spec.rb +7 -0
  194. data/spec/unit/hanami/web/rack_logger_spec.rb +78 -0
  195. metadata +353 -57
  196. data/lib/hanami/application/action.rb +0 -72
  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/view/context.rb +0 -95
  213. data/lib/hanami/application/view.rb +0 -24
  214. data/lib/hanami/application.rb +0 -273
  215. data/lib/hanami/cli/application/cli.rb +0 -40
  216. data/lib/hanami/cli/application/command.rb +0 -47
  217. data/lib/hanami/cli/application/commands/console.rb +0 -81
  218. data/lib/hanami/cli/application/commands.rb +0 -16
  219. data/lib/hanami/cli/base_command.rb +0 -48
  220. data/lib/hanami/cli/commands/command.rb +0 -171
  221. data/lib/hanami/cli/commands/server.rb +0 -88
  222. data/lib/hanami/cli/commands.rb +0 -65
  223. data/lib/hanami/configuration/middleware.rb +0 -20
  224. data/lib/hanami/configuration/source_dirs.rb +0 -42
@@ -3,7 +3,7 @@
3
3
  require_relative "../constants"
4
4
 
5
5
  module Hanami
6
- class Application
6
+ class Slice
7
7
  # Infers a view name for automatically rendering within actions.
8
8
  #
9
9
  # @api private
@@ -28,8 +28,8 @@ module Hanami
28
28
  #
29
29
  # @return [Array<string>] array of paired view container keys
30
30
  def call(action_class_name:, slice:)
31
- action_key_base = slice.application.config.actions.name_inference_base
32
- view_key_base = slice.application.config.actions.view_name_inference_base
31
+ action_key_base = slice.config.actions.name_inference_base
32
+ view_key_base = slice.config.actions.view_name_inference_base
33
33
 
34
34
  action_name_key = action_name_key(action_class_name, slice, action_key_base)
35
35
 
data/lib/hanami/slice.rb CHANGED
@@ -1,29 +1,68 @@
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"
7
6
  require_relative "slice_name"
7
+ require_relative "slice_registrar"
8
8
 
9
9
  module Hanami
10
- # 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.
11
30
  #
12
31
  # @since 2.0.0
13
32
  class Slice
33
+ @_mutex = Mutex.new
34
+
14
35
  def self.inherited(subclass)
15
36
  super
16
37
 
17
38
  subclass.extend(ClassMethods)
18
39
 
19
- # Eagerly initialize any variables that may be accessed inside the subclass body
20
- subclass.instance_variable_set(:@application, Hanami.application)
21
- subclass.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
22
46
  end
23
47
 
24
48
  # rubocop:disable Metrics/ModuleLength
25
49
  module ClassMethods
26
- attr_reader :application, :container
50
+ attr_reader :parent, :container
51
+
52
+ def app
53
+ Hanami.app
54
+ end
55
+
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
64
+ end
65
+ alias_method :config, :configuration
27
66
 
28
67
  def slice_name
29
68
  @slice_name ||= SliceName.new(self, inflector: method(:inflector))
@@ -34,25 +73,20 @@ module Hanami
34
73
  end
35
74
 
36
75
  def root
37
- application.root.join(SLICES_DIR, slice_name.to_s)
76
+ configuration.root
38
77
  end
39
78
 
40
79
  def inflector
41
- application.inflector
80
+ configuration.inflector
42
81
  end
43
82
 
44
83
  def prepare(provider_name = nil)
45
- container.prepare(provider_name) and return self if provider_name
46
-
47
- return self if prepared?
48
-
49
- ensure_slice_name
50
- ensure_slice_consts
51
-
52
- prepare_all
53
-
54
- @prepared = true
55
- self
84
+ if provider_name
85
+ container.prepare(provider_name)
86
+ self
87
+ else
88
+ prepare_slice
89
+ end
56
90
  end
57
91
 
58
92
  def prepare_container(&block)
@@ -62,7 +96,10 @@ module Hanami
62
96
  def boot
63
97
  return self if booted?
64
98
 
99
+ prepare
100
+
65
101
  container.finalize!
102
+ slices.each(&:boot)
66
103
 
67
104
  @booted = true
68
105
 
@@ -70,6 +107,7 @@ module Hanami
70
107
  end
71
108
 
72
109
  def shutdown
110
+ slices.each(&:shutdown)
73
111
  container.shutdown!
74
112
  self
75
113
  end
@@ -82,6 +120,14 @@ module Hanami
82
120
  !!@booted
83
121
  end
84
122
 
123
+ def slices
124
+ @slices ||= SliceRegistrar.new(self)
125
+ end
126
+
127
+ def register_slice(...)
128
+ slices.register(...)
129
+ end
130
+
85
131
  def register(...)
86
132
  container.register(...)
87
133
  end
@@ -118,12 +164,12 @@ module Hanami
118
164
  # TODO: This should be handled via dry-system (see dry-rb/dry-system#228)
119
165
  raise "Cannot import after booting" if booted?
120
166
 
121
- application = self.application
167
+ slice = self
122
168
 
123
169
  container.after(:configure) do
124
170
  if from.is_a?(Symbol) || from.is_a?(String)
125
171
  slice_name = from
126
- from = application.slices[from.to_sym].container
172
+ from = slice.parent.slices[from.to_sym].container
127
173
  end
128
174
 
129
175
  as = kwargs[:as] || slice_name
@@ -132,8 +178,55 @@ module Hanami
132
178
  end
133
179
  end
134
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
+
135
207
  private
136
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
+
137
230
  def ensure_slice_name
138
231
  unless name
139
232
  raise SliceLoadError, "Slice must have a class name before it can be prepared"
@@ -149,15 +242,25 @@ module Hanami
149
242
  end
150
243
  end
151
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
+
152
251
  def prepare_all
252
+ # Load settings first, to fail early in case of missing/unexpected values
253
+ settings
254
+
255
+ prepare_container_consts
153
256
  prepare_container_plugins
154
257
  prepare_container_base_config
155
258
  prepare_container_component_dirs
156
- prepare_autoloader
157
259
  prepare_container_imports
158
- prepare_container_consts
159
- instance_exec(container, &@prepare_container_block) if @prepare_container_block
160
- container.configured!
260
+ prepare_container_providers
261
+ prepare_autoloader
262
+
263
+ prepare_slices
161
264
  end
162
265
 
163
266
  def prepare_container_plugins
@@ -165,7 +268,7 @@ module Hanami
165
268
 
166
269
  container.use(
167
270
  :zeitwerk,
168
- loader: application.autoloader,
271
+ loader: app.autoloader,
169
272
  run_setup: false,
170
273
  eager_load: false
171
274
  )
@@ -176,74 +279,144 @@ module Hanami
176
279
  container.config.root = root
177
280
  container.config.provider_dirs = [File.join("config", "providers")]
178
281
 
179
- container.config.env = application.configuration.env
180
- container.config.inflector = application.configuration.inflector
282
+ container.config.env = configuration.env
283
+ container.config.inflector = configuration.inflector
181
284
  end
182
285
 
183
- def prepare_container_component_dirs # rubocop:disable Metrics/AbcSize
184
- return unless root&.directory?
286
+ def prepare_container_component_dirs
287
+ return unless root.directory?
185
288
 
186
- # Add component dirs for each configured component path
187
- application.configuration.source_dirs.component_dirs.each do |component_dir|
188
- 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
189
291
 
190
- 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
191
297
 
192
- if component_dir.path == LIB_DIR
193
- # Expect component files in the root of the lib/ component dir to define
194
- # classes inside the slice's namespace.
195
- #
196
- # e.g. "lib/foo.rb" should define SliceNamespace::Foo, to be registered as
197
- # "foo"
198
- component_dir.namespaces.delete_root
199
- component_dir.namespaces.add_root(key: nil, const: slice_name.name)
200
- else
201
- # Expect component files in the root of non-lib/ component dirs to define
202
- # classes inside a namespace matching that dir.
203
- #
204
- # e.g. "actions/foo.rb" should define SliceNamespace::Actions::Foo, to be
205
- # 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
206
316
 
207
- dir_namespace_path = File.join(slice_name.name, 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
208
324
 
209
- component_dir.namespaces.delete_root
210
- component_dir.namespaces.add_root(const: dir_namespace_path, key: component_dir.path)
211
- 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
212
333
 
213
- 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))
214
337
  end
215
338
  end
216
339
 
217
- def prepare_autoloader # rubocop:disable Metrics/AbcSize
218
- 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
219
347
 
220
- # Pass configured autoload dirs to the autoloader
221
- application.configuration.source_dirs.autoload_paths.each do |autoload_path|
222
- 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
223
352
 
224
- dir_namespace_path = File.join(slice_name.name, 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)
225
361
 
226
- autoloader_namespace = begin
227
- inflector.constantize(inflector.camelize(dir_namespace_path))
228
- rescue NameError
229
- 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
230
367
  end
368
+ end
231
369
 
232
- container.config.autoloader.push_dir(
233
- container.root.join(autoload_path),
234
- namespace: autoloader_namespace
235
- )
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
236
375
  end
237
376
  end
238
377
 
239
- def prepare_container_imports
240
- 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
241
396
  end
242
397
 
243
- def prepare_container_consts
244
- namespace.const_set :Container, container
245
- 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
246
417
  end
418
+
419
+ # rubocop:enable Metrics/AbcSize
247
420
  end
248
421
  # rubocop:enable Metrics/ModuleLength
249
422
  end
@@ -30,21 +30,16 @@ module Hanami
30
30
 
31
31
  inherited_mod = Module.new do
32
32
  define_method(:inherited) do |subclass|
33
- unless Hanami.application?
34
- raise ComponentLoadError, "Class #{klass} must be defined within an Hanami application"
33
+ unless Hanami.app?
34
+ raise ComponentLoadError, "Class #{klass} must be defined within an Hanami app"
35
35
  end
36
36
 
37
37
  super(subclass)
38
38
 
39
- subclass.instance_variable_set(:@configured_for_slices, configured_for_slices.dup)
40
-
41
39
  slice = slice_for.(subclass)
42
40
  return unless slice
43
41
 
44
- unless subclass.configured_for_slice?(slice)
45
- subclass.configure_for_slice(slice)
46
- subclass.configured_for_slices << slice
47
- end
42
+ subclass.configure_for_slice(slice)
48
43
  end
49
44
  end
50
45
 
@@ -56,20 +51,12 @@ module Hanami
56
51
  def slice_for(klass)
57
52
  return unless klass.name
58
53
 
59
- slices = Hanami.application.slices.to_a + [Hanami.application]
54
+ slices = Hanami.app.slices.to_a + [Hanami.app]
60
55
 
61
56
  slices.detect { |slice| klass.name.include?(slice.namespace.to_s) }
62
57
  end
63
58
  end
64
59
 
65
60
  def configure_for_slice(slice); end
66
-
67
- def configured_for_slice?(slice)
68
- configured_for_slices.include?(slice)
69
- end
70
-
71
- def configured_for_slices
72
- @configured_for_slices ||= []
73
- end
74
61
  end
75
62
  end
@@ -3,24 +3,24 @@
3
3
  require_relative "constants"
4
4
 
5
5
  module Hanami
6
- # Represents the name of an {Application} or {Slice}.
6
+ # Represents the name of an {App} or {Slice}.
7
7
  #
8
- # @see Application::ClassMethods#application_name
9
8
  # @see Slice::ClassMethods#slice_name
9
+ # @see App::ClassMethods#app_name
10
10
  #
11
11
  # @api public
12
12
  # @since 2.0.0
13
13
  class SliceName
14
- # Returns a new SliceName for the slice or application.
14
+ # Returns a new SliceName for the slice or app.
15
15
  #
16
16
  # You must provide an inflector for the manipulation of the name into various formats.
17
17
  # This should be given in the form of a Proc that returns the inflector when called.
18
18
  # The reason for this is that the inflector may be replaced by the user during the
19
- # application configuration phase, so the proc should ensure that the current instance
19
+ # app configuration phase, so the proc should ensure that the current instance
20
20
  # of the inflector is returned whenever needed.
21
21
  #
22
- # @param slice [#name] the slice or application object
23
- # @param inflector [Proc] Proc returning the application's inflector when called
22
+ # @param slice [#name] the slice or app object
23
+ # @param inflector [Proc] Proc returning the app's inflector when called
24
24
  #
25
25
  # @api private
26
26
  def initialize(slice, inflector:)
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "constants"
4
+ require_relative "slice"
5
+
6
+ module Hanami
7
+ # @api private
8
+ class SliceRegistrar
9
+ attr_reader :parent, :slices
10
+ private :parent, :slices
11
+
12
+ def initialize(parent)
13
+ @parent = parent
14
+ @slices = {}
15
+ end
16
+
17
+ def register(name, slice_class = nil, &block)
18
+ if slices.key?(name.to_sym)
19
+ raise SliceLoadError, "Slice '#{name}' is already registered"
20
+ end
21
+
22
+ # TODO: raise error unless name meets format (i.e. single level depth only)
23
+
24
+ slice = slice_class || build_slice(name, &block)
25
+
26
+ configure_slice(name, slice)
27
+
28
+ slices[name.to_sym] = slice
29
+ end
30
+
31
+ def [](name)
32
+ slices.fetch(name) do
33
+ raise SliceLoadError, "Slice '#{name}' not found"
34
+ end
35
+ end
36
+
37
+ def freeze
38
+ slices.freeze
39
+ super
40
+ end
41
+
42
+ def load_slices
43
+ return self unless root
44
+
45
+ slice_configs = Dir[root.join(CONFIG_DIR, SLICES_DIR, "*#{RB_EXT}")]
46
+ .map { |file| File.basename(file, RB_EXT) }
47
+
48
+ slice_dirs = Dir[File.join(root, SLICES_DIR, "*")]
49
+ .select { |path| File.directory?(path) }
50
+ .map { |path| File.basename(path) }
51
+
52
+ (slice_dirs + slice_configs).uniq.sort.each do |slice_name|
53
+ load_slice(slice_name)
54
+ end
55
+
56
+ self
57
+ end
58
+
59
+ def each(&block)
60
+ slices.each_value(&block)
61
+ end
62
+
63
+ def to_a
64
+ slices.values
65
+ end
66
+
67
+ private
68
+
69
+ # Runs when a slice file has been found at `config/slices/[slice_name].rb`, or a slice
70
+ # directory at `slices/[slice_name]`. Attempts to require the slice class, if defined,
71
+ # or generates a new slice class for the given slice name.
72
+ def load_slice(slice_name)
73
+ slice_const_name = inflector.camelize(slice_name)
74
+ slice_require_path = root.join(CONFIG_DIR, SLICES_DIR, slice_name).to_s
75
+
76
+ begin
77
+ require(slice_require_path)
78
+ rescue LoadError => e
79
+ raise e unless e.path == slice_require_path
80
+ end
81
+
82
+ slice_class =
83
+ begin
84
+ inflector.constantize("#{slice_const_name}::Slice")
85
+ rescue NameError => e
86
+ raise e unless e.name.to_s == slice_const_name || e.name.to_s == :Slice
87
+ end
88
+
89
+ register(slice_name, slice_class)
90
+ end
91
+
92
+ def build_slice(slice_name, &block)
93
+ slice_module =
94
+ begin
95
+ slice_module_name = inflector.camelize(slice_name.to_s)
96
+ inflector.constantize(slice_module_name)
97
+ rescue NameError
98
+ Object.const_set(inflector.camelize(slice_module_name), Module.new)
99
+ end
100
+
101
+ slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
102
+ end
103
+
104
+ def configure_slice(slice_name, slice)
105
+ slice.instance_variable_set(:@parent, parent)
106
+
107
+ # Slices require a root, so provide a sensible default based on the slice's parent
108
+ slice.config.root ||= root.join(SLICES_DIR, slice_name.to_s)
109
+ end
110
+
111
+ def root
112
+ parent.root
113
+ end
114
+
115
+ def inflector
116
+ parent.inflector
117
+ end
118
+ end
119
+ end
@@ -8,7 +8,7 @@ module Hanami
8
8
  module Version
9
9
  # @since 0.9.0
10
10
  # @api private
11
- VERSION = "2.0.0.alpha8"
11
+ VERSION = "2.0.0.beta1"
12
12
 
13
13
  # @since 0.9.0
14
14
  # @api private
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Hanami
4
4
  module Web
5
- # Rack logger for Hanami applications
5
+ # Rack logger for Hanami apps
6
6
  class RackLogger
7
7
  REQUEST_METHOD = "REQUEST_METHOD"
8
8
  private_constant :REQUEST_METHOD