hanami 2.1.0.beta2 → 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 +41 -9
  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 +31 -43
  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_tag_spec.rb +2 -2
  32. data/spec/unit/hanami/helpers/assets_helper/{favicon_link_tag_spec.rb → favicon_tag_spec.rb} +8 -12
  33. data/spec/unit/hanami/helpers/assets_helper/image_tag_spec.rb +1 -1
  34. data/spec/unit/hanami/helpers/assets_helper/javascript_tag_spec.rb +3 -3
  35. data/spec/unit/hanami/helpers/assets_helper/{stylesheet_link_tag_spec.rb → stylesheet_tag_spec.rb} +13 -13
  36. data/spec/unit/hanami/helpers/assets_helper/video_tag_spec.rb +5 -1
  37. data/spec/unit/hanami/version_spec.rb +1 -1
  38. metadata +15 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06d7766d5bdb4f510945a2065cc15457acbf00f43970d691afda74219f0aeb02
4
- data.tar.gz: 58935ca7f23c3d3612a91b277827ed941c30839d04c9bf577e35a095fa667f6f
3
+ metadata.gz: 3ba29263713547809e78df051e0138f202d947ce6e9cf2bc9d06c5bf79438cb7
4
+ data.tar.gz: 0677ae36e85bbb92ed1bdde4ae48e92ac062ccf89f6d77b86b85578fd8975a06
5
5
  SHA512:
6
- metadata.gz: 50a785250f4cf249c5721dd37a5e5a7a240282e5d3820fe848a53c133b1727ea57e5beb351429e636bb4a7d89812bfc53794b5357865b134598a5d54e60666db
7
- data.tar.gz: 677f40136e0477c57e493e41ed9a25666d9b53d6dd4ae4c23ee6e61d062f9537c39d86eb16d264f194eda4209e5eb0e38fa0554fd3efaf2d39ea23afa1ead27b
6
+ metadata.gz: f24d67421d88e019d3fdf1d282fd55cff3a303e59fa7195fc8b152a6b85d1990aafe1cf3c583eeb2b561cfcae4b76e4120ef9863f12cee424c8372526cfb178a
7
+ data.tar.gz: 8e9ab011e12426e7d2e03ed784858309f7ca33fa8aa84d23ae84cd7d9435563998f38cb9d53322e2631c017334465cec10e0b2f65a58d8dcb0290c360dff5b1c
data/CHANGELOG.md CHANGED
@@ -2,26 +2,58 @@
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
+
30
+ ## v2.1.0.beta2.1 - 2023-10-04
31
+
32
+ ### Added
33
+
34
+ - [Tim Riley, Luca Guidi] Added assets helpers aliases (#1319, #1339):
35
+ - Added `#js` and `#javascript_tag` as alias for `#javascript`
36
+ - Added `#css` and `#stylesheet_link_tag` as alias for `#stylesheet`
37
+ - Added `#image_tag` as alias for `#image`
38
+ - Added `#favicon_link_tag` as alias for `#favicon`
39
+ - Added `#video_tag` as alias for `#video`
40
+ - Added `#audio_tag` as alias for `#audio`
41
+
5
42
  ## v2.1.0.beta2 - 2023-10-04
6
43
 
7
44
  ### Added
8
45
 
9
46
  - [Luca Guidi, Tim Riley] Reimplement assets integration (#1319, #1332, #1333, #1336)
10
47
  - [Tim Riley] Introduce `Hanami::Helpers::AssetsHelper`, automatically included in view templates, scopes and parts when hanami-assets is bundled (#1319)
11
- - [Tim Riley] Renamed assets helpers (#1319):
12
- - Removed `#asset_path` in favour of `#asset_url` only
13
- - Renamed `#javascript` to `#javascript_tag`, retaining `#js` alias
14
- - Renamed `#stylesheet` to `#stylesheet_link_tag`, retaining `#css` alias
15
- - Renamed `#image` to `#image_tag`
16
- - Renamed `#favicon` to `#favicon_link_tag`, with `#favicon` retained as an alias
17
- - Renamed `#video` to `#video_tag`
18
- - Renamed `#audio` to `#audio_tag`
19
-
20
48
 
21
49
  ### Fixed
22
50
 
23
51
  - [Tim Riley] Return appropriate response statuses based on error type (#1330)
24
52
 
53
+ ### Changed
54
+
55
+ - [Tim Riley] Removed `#asset_path` in favour of `#asset_url` only (#1319)
56
+
25
57
  ## v2.1.0.beta1 - 2023-06-29
26
58
 
27
59
  ### 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
@@ -173,10 +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_tag
179
-
180
176
  # Generate `link` tag for given source(s)
181
177
  #
182
178
  # It accepts one or more strings representing the name of the asset, if it
@@ -251,7 +247,7 @@ module Hanami
251
247
  #
252
248
  # # <link href="https://assets.bookshelf.org/assets/application-28a6b886de2372ee3922fcaf3f78f2d8.css"
253
249
  # # type="text/css" rel="stylesheet">
254
- def stylesheet_link_tag(*source_paths, **options)
250
+ def stylesheet_tag(*source_paths, **options)
255
251
  options = options.reject { |k, _| k.to_sym == :href }
256
252
 
257
253
  _safe_tags(*source_paths) do |source_path|
@@ -271,10 +267,6 @@ module Hanami
271
267
  end
272
268
  end
273
269
 
274
- # @api public
275
- # @since 2.1.0
276
- alias_method :css, :stylesheet_link_tag
277
-
278
270
  # Generate `img` tag for given source
279
271
  #
280
272
  # It accepts one string representing the name of the asset, if it comes
@@ -305,37 +297,37 @@ module Hanami
305
297
  #
306
298
  # @example Basic Usage
307
299
  #
308
- # <%= image_tag "logo.png" %>
300
+ # <%= image "logo.png" %>
309
301
  #
310
302
  # # <img src="/assets/logo.png" alt="Logo">
311
303
  #
312
304
  # @example Custom alt Attribute
313
305
  #
314
- # <%= image_tag "logo.png", alt: "Application Logo" %>
306
+ # <%= image "logo.png", alt: "Application Logo" %>
315
307
  #
316
308
  # # <img src="/assets/logo.png" alt="Application Logo">
317
309
  #
318
310
  # @example Custom HTML Attributes
319
311
  #
320
- # <%= image_tag "logo.png", id: "logo", class: "image" %>
312
+ # <%= image "logo.png", id: "logo", class: "image" %>
321
313
  #
322
314
  # # <img src="/assets/logo.png" alt="Logo" id="logo" class="image">
323
315
  #
324
316
  # @example Absolute URL
325
317
  #
326
- # <%= image_tag "https://example-cdn.com/images/logo.png" %>
318
+ # <%= image "https://example-cdn.com/images/logo.png" %>
327
319
  #
328
320
  # # <img src="https://example-cdn.com/images/logo.png" alt="Logo">
329
321
  #
330
322
  # @example Fingerprint Mode
331
323
  #
332
- # <%= image_tag "logo.png" %>
324
+ # <%= image "logo.png" %>
333
325
  #
334
326
  # # <img src="/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
335
327
  #
336
328
  # @example CDN Mode
337
329
  #
338
- # <%= image_tag "logo.png" %>
330
+ # <%= image "logo.png" %>
339
331
  #
340
332
  # # <img src="https://assets.bookshelf.org/assets/logo-28a6b886de2372ee3922fcaf3f78f2d8.png" alt="Logo">
341
333
  def image_tag(source, options = {})
@@ -376,35 +368,35 @@ module Hanami
376
368
  #
377
369
  # @example Basic Usage
378
370
  #
379
- # <%= favicon_link_tag %>
371
+ # <%= favicon %>
380
372
  #
381
373
  # # <link href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
382
374
  #
383
375
  # @example Custom Path
384
376
  #
385
- # <%= favicon_link_tag "fav.ico" %>
377
+ # <%= favicon "fav.ico" %>
386
378
  #
387
379
  # # <link href="/assets/fav.ico" rel="shortcut icon" type="image/x-icon">
388
380
  #
389
381
  # @example Custom HTML Attributes
390
382
  #
391
- # <%= favicon_link_tag "favicon.ico", id: "fav" %>
383
+ # <%= favicon "favicon.ico", id: "fav" %>
392
384
  #
393
385
  # # <link id: "fav" href="/assets/favicon.ico" rel="shortcut icon" type="image/x-icon">
394
386
  #
395
387
  # @example Fingerprint Mode
396
388
  #
397
- # <%= favicon_link_tag %>
389
+ # <%= favicon %>
398
390
  #
399
391
  # # <link href="/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico" rel="shortcut icon" type="image/x-icon">
400
392
  #
401
393
  # @example CDN Mode
402
394
  #
403
- # <%= favicon_link_tag %>
395
+ # <%= favicon %>
404
396
  #
405
397
  # # <link href="https://assets.bookshelf.org/assets/favicon-28a6b886de2372ee3922fcaf3f78f2d8.ico"
406
398
  # rel="shortcut icon" type="image/x-icon">
407
- def favicon_link_tag(source = DEFAULT_FAVICON, options = {})
399
+ def favicon_tag(source = DEFAULT_FAVICON, options = {})
408
400
  options = options.reject { |k, _| k.to_sym == :href }
409
401
 
410
402
  attributes = {
@@ -417,10 +409,6 @@ module Hanami
417
409
  tag.link(**attributes)
418
410
  end
419
411
 
420
- # @api public
421
- # @since 2.1.0
422
- alias_method :favicon, :favicon_link_tag
423
-
424
412
  # Generate `video` tag for given source
425
413
  #
426
414
  # It accepts one string representing the name of the asset, if it comes
@@ -454,26 +442,26 @@ module Hanami
454
442
  #
455
443
  # @example Basic Usage
456
444
  #
457
- # <%= video_tag "movie.mp4" %>
445
+ # <%= video "movie.mp4" %>
458
446
  #
459
447
  # # <video src="/assets/movie.mp4"></video>
460
448
  #
461
449
  # @example Absolute URL
462
450
  #
463
- # <%= video_tag "https://example-cdn.com/assets/movie.mp4" %>
451
+ # <%= video "https://example-cdn.com/assets/movie.mp4" %>
464
452
  #
465
453
  # # <video src="https://example-cdn.com/assets/movie.mp4"></video>
466
454
  #
467
455
  # @example Custom HTML Attributes
468
456
  #
469
- # <%= video_tag("movie.mp4", autoplay: true, controls: true) %>
457
+ # <%= video("movie.mp4", autoplay: true, controls: true) %>
470
458
  #
471
459
  # # <video src="/assets/movie.mp4" autoplay="autoplay" controls="controls"></video>
472
460
  #
473
461
  # @example Fallback Content
474
462
  #
475
463
  # <%=
476
- # video_tag("movie.mp4") do
464
+ # video("movie.mp4") do
477
465
  # "Your browser does not support the video tag"
478
466
  # end
479
467
  # %>
@@ -485,7 +473,7 @@ module Hanami
485
473
  # @example Tracks
486
474
  #
487
475
  # <%=
488
- # video_tag("movie.mp4") do
476
+ # video("movie.mp4") do
489
477
  # tag.track(kind: "captions", src: asset_url("movie.en.vtt"),
490
478
  # srclang: "en", label: "English")
491
479
  # end
@@ -497,25 +485,25 @@ module Hanami
497
485
  #
498
486
  # @example Without Any Argument
499
487
  #
500
- # <%= video_tag %>
488
+ # <%= video %>
501
489
  #
502
490
  # # ArgumentError
503
491
  #
504
492
  # @example Without src And Without Block
505
493
  #
506
- # <%= video_tag(content: true) %>
494
+ # <%= video(content: true) %>
507
495
  #
508
496
  # # ArgumentError
509
497
  #
510
498
  # @example Fingerprint Mode
511
499
  #
512
- # <%= video_tag "movie.mp4" %>
500
+ # <%= video "movie.mp4" %>
513
501
  #
514
502
  # # <video src="/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
515
503
  #
516
504
  # @example CDN Mode
517
505
  #
518
- # <%= video_tag "movie.mp4" %>
506
+ # <%= video "movie.mp4" %>
519
507
  #
520
508
  # # <video src="https://assets.bookshelf.org/assets/movie-28a6b886de2372ee3922fcaf3f78f2d8.mp4"></video>
521
509
  def video_tag(source = nil, options = {}, &blk)
@@ -556,26 +544,26 @@ module Hanami
556
544
  #
557
545
  # @example Basic Usage
558
546
  #
559
- # <%= audio_tag "song.ogg" %>
547
+ # <%= audio "song.ogg" %>
560
548
  #
561
549
  # # <audio src="/assets/song.ogg"></audio>
562
550
  #
563
551
  # @example Absolute URL
564
552
  #
565
- # <%= audio_tag "https://example-cdn.com/assets/song.ogg" %>
553
+ # <%= audio "https://example-cdn.com/assets/song.ogg" %>
566
554
  #
567
555
  # # <audio src="https://example-cdn.com/assets/song.ogg"></audio>
568
556
  #
569
557
  # @example Custom HTML Attributes
570
558
  #
571
- # <%= audio_tag("song.ogg", autoplay: true, controls: true) %>
559
+ # <%= audio("song.ogg", autoplay: true, controls: true) %>
572
560
  #
573
561
  # # <audio src="/assets/song.ogg" autoplay="autoplay" controls="controls"></audio>
574
562
  #
575
563
  # @example Fallback Content
576
564
  #
577
565
  # <%=
578
- # audio_tag("song.ogg") do
566
+ # audio("song.ogg") do
579
567
  # "Your browser does not support the audio tag"
580
568
  # end
581
569
  # %>
@@ -587,7 +575,7 @@ module Hanami
587
575
  # @example Tracks
588
576
  #
589
577
  # <%=
590
- # audio_tag("song.ogg") do
578
+ # audio("song.ogg") do
591
579
  # tag.track(kind: "captions", src: asset_url("song.pt-BR.vtt"),
592
580
  # srclang: "pt-BR", label: "Portuguese")
593
581
  # end
@@ -599,25 +587,25 @@ module Hanami
599
587
  #
600
588
  # @example Without Any Argument
601
589
  #
602
- # <%= audio_tag %>
590
+ # <%= audio %>
603
591
  #
604
592
  # # ArgumentError
605
593
  #
606
594
  # @example Without src And Without Block
607
595
  #
608
- # <%= audio_tag(controls: true) %>
596
+ # <%= audio(controls: true) %>
609
597
  #
610
598
  # # ArgumentError
611
599
  #
612
600
  # @example Fingerprint Mode
613
601
  #
614
- # <%= audio_tag "song.ogg" %>
602
+ # <%= audio "song.ogg" %>
615
603
  #
616
604
  # # <audio src="/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
617
605
  #
618
606
  # @example CDN Mode
619
607
  #
620
- # <%= audio_tag "song.ogg" %>
608
+ # <%= audio "song.ogg" %>
621
609
  #
622
610
  # # <audio src="https://assets.bookshelf.org/assets/song-28a6b886de2372ee3922fcaf3f78f2d8.ogg"></audio>
623
611
  def audio_tag(source = nil, options = {}, &blk)
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