hanami 2.1.0.beta2.1 → 2.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/hanami.gemspec +1 -1
  4. data/lib/hanami/config/actions.rb +14 -0
  5. data/lib/hanami/extensions/action/slice_configured_action.rb +5 -5
  6. data/lib/hanami/extensions/action.rb +4 -4
  7. data/lib/hanami/extensions/view/context.rb +0 -11
  8. data/lib/hanami/extensions/view/part.rb +51 -2
  9. data/lib/hanami/extensions/view/slice_configured_part.rb +72 -0
  10. data/lib/hanami/extensions/view/slice_configured_view.rb +2 -2
  11. data/lib/hanami/helpers/assets_helper.rb +6 -38
  12. data/lib/hanami/routes.rb +33 -2
  13. data/lib/hanami/slice.rb +12 -2
  14. data/lib/hanami/slice_registrar.rb +48 -23
  15. data/lib/hanami/version.rb +1 -1
  16. data/lib/hanami/web/rack_logger.rb +70 -2
  17. data/lib/hanami/web/welcome.html.erb +203 -0
  18. data/lib/hanami/web/welcome.rb +46 -0
  19. data/spec/integration/assets/assets_spec.rb +14 -3
  20. data/spec/integration/logging/request_logging_spec.rb +65 -7
  21. data/spec/integration/rack_app/method_override_spec.rb +97 -0
  22. data/spec/integration/slices_spec.rb +275 -5
  23. data/spec/integration/view/context/assets_spec.rb +0 -8
  24. data/spec/integration/view/context/inflector_spec.rb +0 -8
  25. data/spec/integration/view/context/settings_spec.rb +0 -8
  26. data/spec/integration/view/helpers/part_helpers_spec.rb +2 -2
  27. data/spec/integration/view/helpers/user_defined_helpers/part_helpers_spec.rb +10 -10
  28. data/spec/integration/view/parts/default_rendering_spec.rb +138 -0
  29. data/spec/integration/web/welcome_view_spec.rb +84 -0
  30. data/spec/support/app_integration.rb +22 -4
  31. data/spec/unit/hanami/helpers/assets_helper/{audio_spec.rb → audio_tag_spec.rb} +10 -14
  32. data/spec/unit/hanami/helpers/assets_helper/{favicon_spec.rb → favicon_tag_spec.rb} +7 -11
  33. data/spec/unit/hanami/helpers/assets_helper/{image_spec.rb → image_tag_spec.rb} +8 -12
  34. data/spec/unit/hanami/helpers/assets_helper/{javascript_spec.rb → javascript_tag_spec.rb} +14 -18
  35. data/spec/unit/hanami/helpers/assets_helper/{stylesheet_spec.rb → stylesheet_tag_spec.rb} +12 -16
  36. data/spec/unit/hanami/helpers/assets_helper/{video_spec.rb → video_tag_spec.rb} +11 -11
  37. data/spec/unit/hanami/version_spec.rb +1 -1
  38. metadata +23 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 219f0622f399b1e3f94c77c5d7556b6bdd26122181929b444eb8fca436e9d0d2
4
- data.tar.gz: 3dc2468f5561eb8d70fa62d3f5b7f755f096ab764cfbfd43fca93d6001061908
3
+ metadata.gz: 3ba29263713547809e78df051e0138f202d947ce6e9cf2bc9d06c5bf79438cb7
4
+ data.tar.gz: 0677ae36e85bbb92ed1bdde4ae48e92ac062ccf89f6d77b86b85578fd8975a06
5
5
  SHA512:
6
- metadata.gz: f38c4b54000182e7ab4fb63be367131c6580df35519f97d5fd171de016b4ec96590c5f7c0822044421e0efec64813a492d4e106a803ea6028ba6d6d105db2ee6
7
- data.tar.gz: 8648d6b907fbc4a600a4561184631b2868a577a2280c6bade34af1726d3bd6458045268c8517956e52bc4e6b2a6dfc78b0bee938e254563cac3fbbae91afc6d2
6
+ metadata.gz: f24d67421d88e019d3fdf1d282fd55cff3a303e59fa7195fc8b152a6b85d1990aafe1cf3c583eeb2b561cfcae4b76e4120ef9863f12cee424c8372526cfb178a
7
+ data.tar.gz: 8e9ab011e12426e7d2e03ed784858309f7ca33fa8aa84d23ae84cd7d9435563998f38cb9d53322e2631c017334465cec10e0b2f65a58d8dcb0290c360dff5b1c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  The web, with simplicity.
4
4
 
5
+ ## v2.1.0.rc1 - 2023-11-01
6
+
7
+ ### Added
8
+
9
+ - [Aaron Moodie & Tim Riley] Render a welcome page when no routes are defined (#1353)
10
+ - [Luca Guidi] Allow _Method Override_ by default, by mounting `Rack::MethodOverride` middleware. (#1344)
11
+
12
+ ### Fixed
13
+
14
+ - [Luca Guidi] Ensure compatibility with Ruby Logger from stdlib (#1352)
15
+
16
+ ### Changed
17
+
18
+ - [Philip Arndt] Add support for a slice's class definition file to exist inside either `config/slices/[slice_name].rb` or `slices/[slice_name]/config/slice.rb`, in that order of precedence, so that a slice can provide its own definition which the app, or the slice's parent slice if that exists, can override if needed.
19
+ - [Tim Riley] Use `helpers` prefix to access helper methods from view parts (e.g. `helpers.format_number`)
20
+ - [Tim Riley] Make it possible to directly initialize view parts to ease their unit tests (#1357)
21
+ - [Tim Riley] Remove short names for assets helpers (#1356):
22
+ - Keep `javascript_tag` (remove `javascript` and `js` aliases)
23
+ - Keep `stylesheet_tag` (renamed from `stylesheet_link_tag`; remove `stylesheet` and `css` aliases)
24
+ - Keep `favicon_tag` (renamed from `favicon_link_tag`; remove `favicon` alias)
25
+ - Keep `image_tag` (remove `image` alias)
26
+ - Keep `video_tag` (remove `video` alias)
27
+ - Keep `audio_tag` (remove `audio` alias)
28
+
29
+
5
30
  ## v2.1.0.beta2.1 - 2023-10-04
6
31
 
7
32
  ### Added
data/hanami.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  lib = File.expand_path("../lib", __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ $LOAD_PATH.prepend(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "hanami/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
@@ -74,6 +74,20 @@ module Hanami
74
74
  # @since 2.0.0
75
75
  attr_accessor :content_security_policy
76
76
 
77
+ # @!attribute [rw] method_override
78
+ # Sets or returns whether HTTP method override should be enabled for action classes.
79
+ #
80
+ # Defaults to true. You can override this by explicitly setting a
81
+ # true or false value.
82
+ #
83
+ # When true, this will mount `Rack::MethodOverride` in the Rack middleware stack of the App.
84
+ #
85
+ # @return [Boolean]
86
+ #
87
+ # @api public
88
+ # @since 2.1.0
89
+ setting :method_override, default: true
90
+
77
91
  # The following settings are for view and assets integration with actions, and are NOT
78
92
  # publicly released as of 2.0.0. We'll make full documentation available when these become
79
93
  # public in a subsequent release.
@@ -31,14 +31,14 @@ module Hanami
31
31
  # @see Hanami::Extensions::Action::InstanceMethods#initialize
32
32
  def define_new
33
33
  resolve_view = method(:resolve_paired_view)
34
- resolve_view_context = method(:resolve_view_context)
34
+ view_context_class = method(:view_context_class)
35
35
  resolve_routes = method(:resolve_routes)
36
36
  resolve_rack_monitor = method(:resolve_rack_monitor)
37
37
 
38
38
  define_method(:new) do |**kwargs|
39
39
  super(
40
40
  view: kwargs.fetch(:view) { resolve_view.(self) },
41
- view_context: kwargs.fetch(:view_context) { resolve_view_context.() },
41
+ view_context_class: kwargs.fetch(:view_context_class) { view_context_class.() },
42
42
  routes: kwargs.fetch(:routes) { resolve_routes.() },
43
43
  rack_monitor: kwargs.fetch(:rack_monitor) { resolve_rack_monitor.() },
44
44
  **kwargs,
@@ -128,9 +128,9 @@ module Hanami
128
128
  nil
129
129
  end
130
130
 
131
- def resolve_view_context
131
+ def view_context_class
132
132
  if Hanami.bundled?("hanami-view")
133
- return Extensions::View::Context.context_class(slice).new
133
+ return Extensions::View::Context.context_class(slice)
134
134
  end
135
135
 
136
136
  # If hanami-view isn't bundled, try and find a possible third party context class with the
@@ -139,7 +139,7 @@ module Hanami
139
139
  views_namespace = slice.namespace.const_get(:Views)
140
140
 
141
141
  if views_namespace.const_defined?(:Context)
142
- views_namespace.const_get(:Context).new
142
+ views_namespace.const_get(:Context)
143
143
  end
144
144
  end
145
145
  end
@@ -40,7 +40,7 @@ module Hanami
40
40
  attr_reader :view
41
41
 
42
42
  # @api private
43
- attr_reader :view_context
43
+ attr_reader :view_context_class
44
44
 
45
45
  # Returns the app or slice's {Hanami::Slice::RoutesHelper RoutesHelper} for use within
46
46
  # action instance methods.
@@ -70,9 +70,9 @@ module Hanami
70
70
  #
71
71
  # @api public
72
72
  # @since 2.0.0
73
- def initialize(view: nil, view_context: nil, rack_monitor: nil, routes: nil, **kwargs)
73
+ def initialize(view: nil, view_context_class: nil, rack_monitor: nil, routes: nil, **kwargs)
74
74
  @view = view
75
- @view_context = view_context
75
+ @view_context_class = view_context_class
76
76
  @routes = routes
77
77
  @rack_monitor = rack_monitor
78
78
 
@@ -104,7 +104,7 @@ module Hanami
104
104
 
105
105
  # @api private
106
106
  def view_options(request, response)
107
- {context: view_context&.with(**view_context_options(request, response))}.compact
107
+ {context: view_context_class&.new(**view_context_options(request, response))}.compact
108
108
  end
109
109
 
110
110
  # @api private
@@ -104,17 +104,6 @@ module Hanami
104
104
  @content_for = source.instance_variable_get(:@content_for).dup
105
105
  end
106
106
 
107
- def with(**args)
108
- self.class.new(
109
- inflector: @inflector,
110
- settings: @settings,
111
- assets: @assets,
112
- routes: @routes,
113
- request: @request,
114
- **args
115
- )
116
- end
117
-
118
107
  def assets
119
108
  unless @assets
120
109
  raise Hanami::ComponentLoadError, "the hanami-assets gem is required to access assets"
@@ -4,20 +4,69 @@ module Hanami
4
4
  module Extensions
5
5
  module View
6
6
  # @api private
7
+ # @since 2.1.0
7
8
  module Part
8
9
  def self.included(part_class)
9
10
  super
10
11
 
11
12
  part_class.extend(Hanami::SliceConfigurable)
12
- part_class.include(StandardHelpers)
13
13
  part_class.extend(ClassMethods)
14
14
  end
15
15
 
16
16
  module ClassMethods
17
17
  def configure_for_slice(slice)
18
- extend SliceConfiguredHelpers.new(slice)
18
+ extend SliceConfiguredPart.new(slice)
19
+
20
+ const_set :PartHelpers, Class.new(PartHelpers) { |klass|
21
+ klass.configure_for_slice(slice)
22
+ }
19
23
  end
20
24
  end
25
+
26
+ # Returns an object including the default Hanami helpers as well as the user-defined helpers
27
+ # for the part's slice.
28
+ #
29
+ # Use this when you need to access helpers inside your part classes.
30
+ #
31
+ # @return PartHelpers
32
+ #
33
+ # @api public
34
+ # @since 2.1.0
35
+ def helpers
36
+ @helpers ||= self.class.const_get(:PartHelpers).new(context: _context)
37
+ end
38
+ end
39
+
40
+ # Standalone helpers class including both {StandardHelpers} as well as the user-defined
41
+ # helpers for the slice.
42
+ #
43
+ # Used used where helpers should be addressed via an intermediary object (i.e. in parts),
44
+ # rather than mixed into a class directly.
45
+ #
46
+ # @api private
47
+ # @since 2.1.0
48
+ class PartHelpers
49
+ extend Hanami::SliceConfigurable
50
+
51
+ include StandardHelpers
52
+
53
+ def self.configure_for_slice(slice)
54
+ extend SliceConfiguredHelpers.new(slice)
55
+ end
56
+
57
+ # @api public
58
+ # @since 2.1.0
59
+ attr_reader :_context
60
+
61
+ # @api public
62
+ # @since 2.1.0
63
+ alias_method :context, :_context
64
+
65
+ # @api private
66
+ # @since 2.1.0
67
+ def initialize(context:)
68
+ @_context = context
69
+ end
21
70
  end
22
71
  end
23
72
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module Extensions
5
+ module View
6
+ # Provides slice-specific configuration and behavior for any view part class defined within a
7
+ # slice's module namespace.
8
+ #
9
+ # @api private
10
+ # @since 2.1.0
11
+ class SliceConfiguredPart < Module
12
+ attr_reader :slice
13
+
14
+ # @api private
15
+ # @since 2.1.0
16
+ def initialize(slice)
17
+ super()
18
+ @slice = slice
19
+ end
20
+
21
+ # @api private
22
+ # @since 2.1.0
23
+ def extended(klass)
24
+ define_new
25
+ end
26
+
27
+ # @api private
28
+ # @since 2.1.0
29
+ def inspect
30
+ "#<#{self.class.name}[#{slice.name}]>"
31
+ end
32
+
33
+ private
34
+
35
+ # Defines a `.new` method on the part class that provides a default `rendering:` argument of
36
+ # a rendering coming from a view configured for the slice. This means that any part can be
37
+ # initialized standalone (with a `value:` only) and still have access to all the integrated
38
+ # view facilities from the slice, such as helpers. This is helpful when unit testing parts.
39
+ #
40
+ # @example
41
+ # module MyApp::Views::Parts
42
+ # class Post < MyApp::View::Part
43
+ # def title_tag
44
+ # helpers.h1(value.title)
45
+ # end
46
+ # end
47
+ # end
48
+ #
49
+ # # Useful when unit testing parts
50
+ # part = MyApp::Views::Parts::Post.new(value: hello_world_post)
51
+ # part.title_tag # => "<h1>Hello world</h1>"
52
+ #
53
+ # @api private
54
+ # @since 2.1.0
55
+ def define_new
56
+ slice = self.slice
57
+
58
+ define_method(:new) do |**args|
59
+ return super(**args) if args.key?(:rendering)
60
+
61
+ slice_rendering = Class.new(Hanami::View)
62
+ .configure_for_slice(slice)
63
+ .new
64
+ .rendering
65
+
66
+ super(rendering: slice_rendering, **args)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -108,7 +108,7 @@ module Hanami
108
108
  # - We are a slice, and the view's inherited `paths` is identical to the parent's config
109
109
  # (which would result in the view in a slice erroneously trying to find templates in
110
110
  # the app)
111
- if (!slice.parent && view_class.config.paths.empty?) ||
111
+ if view_class.config.paths.empty? ||
112
112
  (slice.parent && view_class.config.paths.map(&:dir) == [templates_path(slice.parent)])
113
113
  view_class.config.paths = templates_path(slice)
114
114
  end
@@ -178,7 +178,7 @@ module Hanami
178
178
  else
179
179
  views_namespace.const_set(:Part, Class.new(part_superclass).tap { |klass|
180
180
  # Give the slice to `configure_for_slice`, since it cannot be inferred when it is
181
- # called via `.inherited`, since the class is anonymous at this point
181
+ # called via `.inherited`, because the class is anonymous at this point
182
182
  klass.configure_for_slice(slice)
183
183
  })
184
184
  end
@@ -154,7 +154,7 @@ module Hanami
154
154
  #
155
155
  # # <script src="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.js"
156
156
  # # type="text/javascript"></script>
157
- def javascript(*source_paths, **options)
157
+ def javascript_tag(*source_paths, **options)
158
158
  options = options.reject { |k, _| k.to_sym == :src }
159
159
 
160
160
  _safe_tags(*source_paths) do |source|
@@ -173,14 +173,6 @@ module Hanami
173
173
  end
174
174
  end
175
175
 
176
- # @api public
177
- # @since 2.1.0
178
- alias_method :js, :javascript
179
-
180
- # @api public
181
- # @since 2.1.0
182
- alias_method :javascript_tag, :javascript
183
-
184
176
  # Generate `link` tag for given source(s)
185
177
  #
186
178
  # It accepts one or more strings representing the name of the asset, if it
@@ -255,7 +247,7 @@ module Hanami
255
247
  #
256
248
  # # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
257
249
  # # type="text/css" rel="stylesheet">
258
- def stylesheet(*source_paths, **options)
250
+ def stylesheet_tag(*source_paths, **options)
259
251
  options = options.reject { |k, _| k.to_sym == :href }
260
252
 
261
253
  _safe_tags(*source_paths) do |source_path|
@@ -275,14 +267,6 @@ module Hanami
275
267
  end
276
268
  end
277
269
 
278
- # @api public
279
- # @since 2.1.0
280
- alias_method :css, :stylesheet
281
-
282
- # @api public
283
- # @since 2.1.0
284
- alias_method :stylesheet_link_tag, :stylesheet
285
-
286
270
  # Generate `img` tag for given source
287
271
  #
288
272
  # It accepts one string representing the name of the asset, if it comes
@@ -346,7 +330,7 @@ module Hanami
346
330
  # <%= image "logo.png" %>
347
331
  #
348
332
  # # <img src="https://assets.bookshelf.org/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
349
- def image(source, options = {})
333
+ def image_tag(source, options = {})
350
334
  options = options.reject { |k, _| k.to_sym == :src }
351
335
  attributes = {
352
336
  src: asset_url(source),
@@ -357,10 +341,6 @@ module Hanami
357
341
  tag.img(**attributes)
358
342
  end
359
343
 
360
- # @api public
361
- # @since 2.1.0
362
- alias_method :image_tag, :image
363
-
364
344
  # Generate `link` tag application favicon.
365
345
  #
366
346
  # If no argument is given, it assumes `favico.ico` from the application.
@@ -416,7 +396,7 @@ module Hanami
416
396
  #
417
397
  # # <link href="https://assets.bookshelf.org/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico"
418
398
  # rel="shortcut icon" type="image/x-icon">
419
- def favicon(source = DEFAULT_FAVICON, options = {})
399
+ def favicon_tag(source = DEFAULT_FAVICON, options = {})
420
400
  options = options.reject { |k, _| k.to_sym == :href }
421
401
 
422
402
  attributes = {
@@ -429,10 +409,6 @@ module Hanami
429
409
  tag.link(**attributes)
430
410
  end
431
411
 
432
- # @api public
433
- # @since 2.1.0
434
- alias_method :favicon_link_tag, :favicon
435
-
436
412
  # Generate `video` tag for given source
437
413
  #
438
414
  # It accepts one string representing the name of the asset, if it comes
@@ -530,15 +506,11 @@ module Hanami
530
506
  # <%= video "movie.mp4" %>
531
507
  #
532
508
  # # <video src="https://assets.bookshelf.org/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
533
- def video(source = nil, options = {}, &blk)
509
+ def video_tag(source = nil, options = {}, &blk)
534
510
  options = _source_options(source, options, &blk)
535
511
  tag.video(**options, &blk)
536
512
  end
537
513
 
538
- # @api public
539
- # @since 2.1.0
540
- alias_method :video_tag, :video
541
-
542
514
  # Generate `audio` tag for given source
543
515
  #
544
516
  # It accepts one string representing the name of the asset, if it comes
@@ -636,15 +608,11 @@ module Hanami
636
608
  # <%= audio "song.ogg" %>
637
609
  #
638
610
  # # <audio src="https://assets.bookshelf.org/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
639
- def audio(source = nil, options = {}, &blk)
611
+ def audio_tag(source = nil, options = {}, &blk)
640
612
  options = _source_options(source, options, &blk)
641
613
  tag.audio(**options, &blk)
642
614
  end
643
615
 
644
- # @api public
645
- # @since 2.1.0
646
- alias_method :audio_tag, :audio
647
-
648
616
  # It generates the relative or absolute URL for the given asset.
649
617
  # It automatically decides if it has to use the relative or absolute
650
618
  # depending on the configuration and current environment.
data/lib/hanami/routes.rb CHANGED
@@ -60,6 +60,35 @@ module Hanami
60
60
  end
61
61
  end
62
62
 
63
+ # Wrapper class for the (otherwise opaque) proc returned from {.routes}, adding an `#empty?`
64
+ # method that returns true if no routes were defined.
65
+ #
66
+ # This is useful when needing to determine behaviour based on the presence of user-defined
67
+ # routes, such as determining whether to show the Hanami welcome page in {Slice#load_router}.
68
+ #
69
+ # @api private
70
+ # @since 2.1.0
71
+ class RoutesProc < DelegateClass(Proc)
72
+ # @api private
73
+ # @since 2.1.0
74
+ def self.empty
75
+ new(proc {}, empty: true)
76
+ end
77
+
78
+ # @api private
79
+ # @since 2.1.0
80
+ def initialize(proc, empty: false)
81
+ @empty = empty
82
+ super(proc)
83
+ end
84
+
85
+ # @api private
86
+ # @since 2.1.0
87
+ def empty?
88
+ !!@empty
89
+ end
90
+ end
91
+
63
92
  # @api private
64
93
  def self.routes
65
94
  @routes ||= build_routes
@@ -68,9 +97,9 @@ module Hanami
68
97
  class << self
69
98
  # @api private
70
99
  def build_routes(definitions = self.definitions)
71
- return if definitions.empty?
100
+ return RoutesProc.empty if definitions.empty?
72
101
 
73
- proc do
102
+ routes_proc = proc do
74
103
  definitions.each do |(name, args, kwargs, block)|
75
104
  if block
76
105
  public_send(name, *args, **kwargs, &block)
@@ -79,6 +108,8 @@ module Hanami
79
108
  end
80
109
  end
81
110
  end
111
+
112
+ RoutesProc.new(routes_proc)
82
113
  end
83
114
 
84
115
  # @api private
data/lib/hanami/slice.rb CHANGED
@@ -952,6 +952,7 @@ module Hanami
952
952
  config = self.config
953
953
  rack_monitor = self["rack.monitor"]
954
954
 
955
+ show_welcome = Hanami.env?(:development) && routes.empty?
955
956
  render_errors = render_errors?
956
957
  render_detailed_errors = render_detailed_errors?
957
958
 
@@ -971,6 +972,8 @@ module Hanami
971
972
  ) do
972
973
  use(rack_monitor)
973
974
 
975
+ use(Hanami::Web::Welcome) if show_welcome
976
+
974
977
  use(
975
978
  Hanami::Middleware::RenderErrors,
976
979
  config,
@@ -982,8 +985,15 @@ module Hanami
982
985
  use(Hanami::Webconsole::Middleware, config)
983
986
  end
984
987
 
985
- if Hanami.bundled?("hanami-controller") && config.actions.sessions.enabled?
986
- use(*config.actions.sessions.middleware)
988
+ if Hanami.bundled?("hanami-controller")
989
+ if config.actions.method_override
990
+ require "rack/method_override"
991
+ use(Rack::MethodOverride)
992
+ end
993
+
994
+ if config.actions.sessions.enabled?
995
+ use(*config.actions.sessions.middleware)
996
+ end
987
997
  end
988
998
 
989
999
  if Hanami.bundled?("hanami-assets") && config.assets.serve
@@ -46,19 +46,16 @@ module Hanami
46
46
  end
47
47
 
48
48
  def load_slices
49
- slice_configs = Dir[root.join(CONFIG_DIR, SLICES_DIR, "*#{RB_EXT}")]
50
- .map { |file| File.basename(file, RB_EXT) }
49
+ slice_configs = root.join(CONFIG_DIR, SLICES_DIR).glob("*#{RB_EXT}")
50
+ .map { _1.basename(RB_EXT) }
51
51
 
52
- slice_dirs = Dir[File.join(root, SLICES_DIR, "*")]
53
- .select { |path| File.directory?(path) }
54
- .map { |path| File.basename(path) }
52
+ slice_dirs = root.join(SLICES_DIR).glob("*")
53
+ .select(&:directory?)
54
+ .map { _1.basename }
55
55
 
56
- slice_names = (slice_dirs + slice_configs).uniq.sort
56
+ (slice_dirs + slice_configs).uniq.sort
57
57
  .then { filter_slice_names(_1) }
58
-
59
- slice_names.each do |slice_name|
60
- load_slice(slice_name)
61
- end
58
+ .each(&method(:load_slice))
62
59
 
63
60
  self
64
61
  end
@@ -97,21 +94,20 @@ module Hanami
97
94
  parent.eql?(parent.app) ? Object : parent.namespace
98
95
  end
99
96
 
100
- # Runs when a slice file has been found at `config/slices/[slice_name].rb`, or a slice directory
101
- # at `slices/[slice_name]`. Attempts to require the slice class, if defined, before registering
102
- # the slice. If a slice class is not found, registering the slice will generate the slice class.
97
+ # Runs when a slice file has been found inside the app at `config/slices/[slice_name].rb`,
98
+ # or when a slice directory exists at `slices/[slice_name]`.
99
+ #
100
+ # If a slice definition file is found by `find_slice_require_path`, then `load_slice` will
101
+ # require the file before registering the slice class.
102
+ #
103
+ # If a slice class is not found, registering the slice will generate the slice class.
103
104
  def load_slice(slice_name)
104
- slice_require_path = root.join(CONFIG_DIR, SLICES_DIR, slice_name).to_s
105
- begin
106
- require(slice_require_path)
107
- rescue LoadError => e
108
- raise e unless e.path == slice_require_path
109
- end
105
+ slice_require_path = find_slice_require_path(slice_name)
106
+ require slice_require_path if slice_require_path
110
107
 
111
- slice_module_name = inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
112
108
  slice_class =
113
109
  begin
114
- inflector.constantize("#{slice_module_name}#{MODULE_DELIMITER}Slice")
110
+ inflector.constantize("#{slice_module_name(slice_name)}#{MODULE_DELIMITER}Slice")
115
111
  rescue NameError => e
116
112
  raise e unless e.name.to_s == inflector.camelize(slice_name) || e.name.to_s == :Slice
117
113
  end
@@ -119,11 +115,36 @@ module Hanami
119
115
  register(slice_name, slice_class)
120
116
  end
121
117
 
118
+ # Finds the path to the slice's definition file, if it exists, in the following order:
119
+ #
120
+ # 1. `config/slices/[slice_name].rb`
121
+ # 2. `slices/[parent_slice_name]/config/[slice_name].rb` (unless parent is the app)
122
+ # 3. `slices/[slice_name]/config/slice.rb`
123
+ #
124
+ # If the slice is nested under another slice then it will look in the following order:
125
+ #
126
+ # 1. `config/slices/[parent_slice_name]/[slice_name].rb`
127
+ # 2. `slices/[parent_slice_name]/config/[slice_name].rb`
128
+ # 3. `slices/[parent_slice_name]/[slice_name]/config/slice.rb`
129
+ def find_slice_require_path(slice_name)
130
+ app_slice_file_path = [slice_name]
131
+ app_slice_file_path.prepend(parent.slice_name) unless parent.eql?(parent.app)
132
+ ancestors = [
133
+ parent.app.root.join(CONFIG_DIR, SLICES_DIR, app_slice_file_path.join(File::SEPARATOR)),
134
+ parent.root.join(CONFIG_DIR, SLICES_DIR, slice_name),
135
+ root.join(SLICES_DIR, slice_name, CONFIG_DIR, "slice")
136
+ ]
137
+
138
+ ancestors
139
+ .uniq
140
+ .find { _1.sub_ext(RB_EXT).file? }
141
+ &.to_s
142
+ end
143
+
122
144
  def build_slice(slice_name, &block)
123
- slice_module_name = inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
124
145
  slice_module =
125
146
  begin
126
- inflector.constantize(slice_module_name)
147
+ inflector.constantize(slice_module_name(slice_name))
127
148
  rescue NameError
128
149
  parent_slice_namespace.const_set(inflector.camelize(slice_name), Module.new)
129
150
  end
@@ -131,6 +152,10 @@ module Hanami
131
152
  slice_module.const_set(:Slice, Class.new(Hanami::Slice, &block))
132
153
  end
133
154
 
155
+ def slice_module_name(slice_name)
156
+ inflector.camelize("#{parent_slice_namespace.name}#{PATH_DELIMITER}#{slice_name}")
157
+ end
158
+
134
159
  def configure_slice(slice_name, slice)
135
160
  slice.instance_variable_set(:@parent, parent)
136
161
 
@@ -7,7 +7,7 @@ module Hanami
7
7
  # @api private
8
8
  module Version
9
9
  # @api public
10
- VERSION = "2.1.0.beta2.1"
10
+ VERSION = "2.1.0.rc1"
11
11
 
12
12
  # @since 0.9.0
13
13
  # @api private