hanami 2.1.0.beta2.1 → 2.1.0.rc1

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 (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