web_pipe 0.13.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +29 -0
  4. data/Gemfile +2 -2
  5. data/README.md +18 -11
  6. data/docs/building_a_rack_application.md +1 -1
  7. data/docs/composing_applications.md +4 -4
  8. data/docs/connection_struct/configuring_the_connection_struct.md +4 -4
  9. data/docs/connection_struct/halting_the_pipe.md +17 -19
  10. data/docs/connection_struct/sharing_data_downstream.md +9 -8
  11. data/docs/connection_struct.md +22 -19
  12. data/docs/design_model.md +10 -9
  13. data/docs/dsl_free_usage.md +85 -14
  14. data/docs/extensions/container.md +9 -10
  15. data/docs/extensions/cookies.md +4 -2
  16. data/docs/extensions/dry_schema.md +5 -4
  17. data/docs/extensions/flash.md +9 -11
  18. data/docs/extensions/{dry_view.md → hanami_view.md} +20 -22
  19. data/docs/extensions/not_found.md +40 -0
  20. data/docs/extensions/params.md +6 -4
  21. data/docs/extensions/rails.md +31 -38
  22. data/docs/extensions/redirect.md +5 -4
  23. data/docs/extensions/router_params.md +5 -5
  24. data/docs/extensions/session.md +4 -4
  25. data/docs/extensions/url.md +6 -6
  26. data/docs/extensions.md +5 -6
  27. data/docs/introduction.md +7 -7
  28. data/docs/plugging_operations/composing_operations.md +3 -3
  29. data/docs/plugging_operations/injecting_operations.md +4 -4
  30. data/docs/plugging_operations/inspecting_operations.md +24 -0
  31. data/docs/plugging_operations/resolving_operations.md +3 -3
  32. data/docs/plugging_operations.md +3 -3
  33. data/docs/plugs/config.md +1 -1
  34. data/docs/plugs/content_type.md +2 -1
  35. data/docs/plugs.md +6 -7
  36. data/docs/recipes/hanami_2_and_dry_rb_integration.md +12 -0
  37. data/docs/recipes/hanami_router_integration.md +3 -1
  38. data/docs/recipes/using_all_restful_methods.md +6 -5
  39. data/docs/testing.md +64 -0
  40. data/docs/using_rack_middlewares/composing_middlewares.md +2 -3
  41. data/docs/using_rack_middlewares/injecting_middlewares.md +6 -6
  42. data/docs/using_rack_middlewares/inspecting_middlewares.md +35 -0
  43. data/docs/using_rack_middlewares.md +6 -6
  44. data/lib/web_pipe/app.rb +22 -25
  45. data/lib/web_pipe/conn.rb +0 -1
  46. data/lib/web_pipe/conn_support/builder.rb +0 -7
  47. data/lib/web_pipe/conn_support/composition.rb +3 -26
  48. data/lib/web_pipe/conn_support/errors.rb +5 -5
  49. data/lib/web_pipe/conn_support/headers.rb +1 -50
  50. data/lib/web_pipe/conn_support/types.rb +3 -3
  51. data/lib/web_pipe/dsl/builder.rb +10 -19
  52. data/lib/web_pipe/dsl/class_context.rb +15 -40
  53. data/lib/web_pipe/dsl/instance_context.rb +53 -0
  54. data/lib/web_pipe/extensions/container/container.rb +2 -15
  55. data/lib/web_pipe/extensions/cookies/cookies.rb +2 -31
  56. data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -56
  57. data/lib/web_pipe/extensions/flash/flash.rb +2 -32
  58. data/lib/web_pipe/extensions/hanami_view/hanami_view.rb +67 -0
  59. data/lib/web_pipe/extensions/not_found/not_found.rb +26 -0
  60. data/lib/web_pipe/extensions/params/params.rb +2 -63
  61. data/lib/web_pipe/extensions/rails/rails.rb +2 -123
  62. data/lib/web_pipe/extensions/redirect/redirect.rb +2 -20
  63. data/lib/web_pipe/extensions/router_params/router_params.rb +1 -39
  64. data/lib/web_pipe/extensions/session/session.rb +2 -25
  65. data/lib/web_pipe/extensions/url/url.rb +2 -5
  66. data/lib/web_pipe/pipe.rb +229 -0
  67. data/lib/web_pipe/plug.rb +31 -65
  68. data/lib/web_pipe/plugs/config.rb +0 -2
  69. data/lib/web_pipe/plugs/content_type.rb +0 -2
  70. data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -26
  71. data/lib/web_pipe/rack_support/middleware.rb +2 -2
  72. data/lib/web_pipe/rack_support/middleware_specification.rb +19 -48
  73. data/lib/web_pipe/test_support.rb +28 -0
  74. data/lib/web_pipe/types.rb +1 -3
  75. data/lib/web_pipe/version.rb +1 -1
  76. data/lib/web_pipe.rb +77 -17
  77. data/web_pipe.gemspec +1 -2
  78. metadata +16 -9
  79. data/docs/recipes/dry_rb_integration.md +0 -18
  80. data/lib/web_pipe/dsl/dsl_context.rb +0 -85
  81. data/lib/web_pipe/dsl/instance_methods.rb +0 -114
  82. data/lib/web_pipe/extensions/dry_view/dry_view.rb +0 -158
data/lib/web_pipe.rb CHANGED
@@ -1,23 +1,83 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'dry/core/extensions'
3
4
  require 'web_pipe/dsl/builder'
4
5
 
5
- # See [the
6
- # README](https://github.com/waiting-for-dev/web_pipe/blob/master/README.md)
7
- # for a general overview of this library.
6
+ # Entry-point for the DSL layer.
7
+ #
8
+ # Including this module into your class adds to it a DSL layer which makes it
9
+ # convenient to interact with an instance of {WebPipe::Pipe} transparently. It
10
+ # means that the DSL is actually an optional layer, and you can achieve
11
+ # everything by using {WebPipe::Pipe} instances.
12
+ #
13
+ # Your class gets access to {WebPipe::DSL::ClassContext::DSL_METHODS} at the
14
+ # class level, while {WebPipe::DSL::InstanceContext::PIPE_METHODS} are available
15
+ # for every instance of it. Both groups of methods are delegating to
16
+ # {WebPipe::Pipe}, so you can look there for documentation.
17
+ #
18
+ # @example
19
+ # class HelloWorld
20
+ # include WebPipe
21
+ #
22
+ # use :runtime, Rack::Runtime
23
+ #
24
+ # plug :content_type do |conn|
25
+ # conn.add_response_header('Content-Type', 'plain/text')
26
+ # end
27
+ #
28
+ # plug :render do |conn|
29
+ # conn.set_response_body('Hello, World!')
30
+ # end
31
+ # end
32
+ #
33
+ # The instance of your class is itself the final rack application. When you
34
+ # initialize it, you have the chance to inject different plugs or middlewares
35
+ # from those defined at the class level.
36
+ #
37
+ # @example
38
+ # HelloWorld.new(
39
+ # middlewares: {
40
+ # runtime: [Class.new do
41
+ # def initialize(app)
42
+ # @app = app
43
+ # end
44
+ #
45
+ # def call(env)
46
+ # status, headers, body = @app.call(env)
47
+ # [status, headers.merge('Injected' => '1'), body]
48
+ # end
49
+ # end]
50
+ # },
51
+ # plugs: {
52
+ # render: ->(conn) { conn.set_response_body('Injected!') }
53
+ # }
54
+ # )
8
55
  module WebPipe
9
56
  extend Dry::Core::Extensions
10
57
 
11
- # Including just delegates to an instance of `Builder`, so
12
- # `Builder#included` is finally called.
58
+ # Called via {Module#include}, makes available web_pipe's DSL.
59
+ #
60
+ # Includes an instance of `Builder`. That means that `Builder#included` is
61
+ # eventually called.
13
62
  def self.included(klass)
14
63
  klass.include(call)
15
64
  end
16
65
 
66
+ # Chained to {Module#include} to make the DSL available and provide options.
67
+ #
68
+ # @param container [#[]] Container from where resolve operations. See
69
+ # {WebPipe::Plug}.
70
+ #
71
+ # @example
72
+ # include WebPipe.call(container: Container)
17
73
  def self.call(**opts)
18
74
  DSL::Builder.new(**opts)
19
75
  end
20
76
 
77
+ register_extension :container do
78
+ require 'web_pipe/extensions/container/container'
79
+ end
80
+
21
81
  register_extension :cookies do
22
82
  require 'web_pipe/extensions/cookies/cookies'
23
83
  end
@@ -27,24 +87,16 @@ module WebPipe
27
87
  require 'web_pipe/extensions/dry_schema/plugs/sanitize_params'
28
88
  end
29
89
 
30
- register_extension :dry_view do
31
- require 'web_pipe/extensions/dry_view/dry_view'
32
- end
33
-
34
- register_extension :container do
35
- require 'web_pipe/extensions/container/container'
36
- end
37
-
38
90
  register_extension :flash do
39
91
  require 'web_pipe/extensions/flash/flash'
40
92
  end
41
93
 
42
- register_extension :router_params do
43
- require 'web_pipe/extensions/router_params/router_params'
94
+ register_extension :hanami_view do
95
+ require 'web_pipe/extensions/hanami_view/hanami_view'
44
96
  end
45
97
 
46
- register_extension :redirect do
47
- require 'web_pipe/extensions/redirect/redirect'
98
+ register_extension :not_found do
99
+ require 'web_pipe/extensions/not_found/not_found'
48
100
  end
49
101
 
50
102
  register_extension :params do
@@ -55,6 +107,14 @@ module WebPipe
55
107
  require 'web_pipe/extensions/rails/rails'
56
108
  end
57
109
 
110
+ register_extension :redirect do
111
+ require 'web_pipe/extensions/redirect/redirect'
112
+ end
113
+
114
+ register_extension :router_params do
115
+ require 'web_pipe/extensions/router_params/router_params'
116
+ end
117
+
58
118
  register_extension :session do
59
119
  require 'web_pipe/extensions/session/session'
60
120
  end
data/web_pipe.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
 
13
13
  spec.summary = 'Rack application builder through a pipe of operations on an immutable struct.'
14
14
  spec.homepage = 'https://github.com/waiting-for-dev/web_pipe'
15
+ spec.licenses = ['MIT']
15
16
 
16
17
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
18
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -43,8 +44,6 @@ Gem::Specification.new do |spec|
43
44
  spec.add_development_dependency 'bundler'
44
45
  spec.add_development_dependency 'dry-schema', '~> 1.0'
45
46
  spec.add_development_dependency 'dry-transformer', '~> 0.1'
46
- # TODO: Readd when dry-rb 0.8 is released (ruby 3.0 support)
47
- # spec.add_development_dependency 'dry-view', '~> 0.8'
48
47
  spec.add_development_dependency 'pry-byebug'
49
48
  spec.add_development_dependency 'rack-flash3', '~> 1.0'
50
49
  spec.add_development_dependency 'rack-test', '~> 1.1'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web_pipe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Busqué
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-15 00:00:00.000000000 Z
11
+ date: 2021-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-monads
@@ -277,8 +277,9 @@ files:
277
277
  - docs/extensions/container.md
278
278
  - docs/extensions/cookies.md
279
279
  - docs/extensions/dry_schema.md
280
- - docs/extensions/dry_view.md
281
280
  - docs/extensions/flash.md
281
+ - docs/extensions/hanami_view.md
282
+ - docs/extensions/not_found.md
282
283
  - docs/extensions/params.md
283
284
  - docs/extensions/rails.md
284
285
  - docs/extensions/redirect.md
@@ -289,16 +290,19 @@ files:
289
290
  - docs/plugging_operations.md
290
291
  - docs/plugging_operations/composing_operations.md
291
292
  - docs/plugging_operations/injecting_operations.md
293
+ - docs/plugging_operations/inspecting_operations.md
292
294
  - docs/plugging_operations/resolving_operations.md
293
295
  - docs/plugs.md
294
296
  - docs/plugs/config.md
295
297
  - docs/plugs/content_type.md
296
- - docs/recipes/dry_rb_integration.md
298
+ - docs/recipes/hanami_2_and_dry_rb_integration.md
297
299
  - docs/recipes/hanami_router_integration.md
298
300
  - docs/recipes/using_all_restful_methods.md
301
+ - docs/testing.md
299
302
  - docs/using_rack_middlewares.md
300
303
  - docs/using_rack_middlewares/composing_middlewares.md
301
304
  - docs/using_rack_middlewares/injecting_middlewares.md
305
+ - docs/using_rack_middlewares/inspecting_middlewares.md
302
306
  - lib/web_pipe.rb
303
307
  - lib/web_pipe/app.rb
304
308
  - lib/web_pipe/conn.rb
@@ -309,14 +313,14 @@ files:
309
313
  - lib/web_pipe/conn_support/types.rb
310
314
  - lib/web_pipe/dsl/builder.rb
311
315
  - lib/web_pipe/dsl/class_context.rb
312
- - lib/web_pipe/dsl/dsl_context.rb
313
- - lib/web_pipe/dsl/instance_methods.rb
316
+ - lib/web_pipe/dsl/instance_context.rb
314
317
  - lib/web_pipe/extensions/container/container.rb
315
318
  - lib/web_pipe/extensions/cookies/cookies.rb
316
319
  - lib/web_pipe/extensions/dry_schema/dry_schema.rb
317
320
  - lib/web_pipe/extensions/dry_schema/plugs/sanitize_params.rb
318
- - lib/web_pipe/extensions/dry_view/dry_view.rb
319
321
  - lib/web_pipe/extensions/flash/flash.rb
322
+ - lib/web_pipe/extensions/hanami_view/hanami_view.rb
323
+ - lib/web_pipe/extensions/not_found/not_found.rb
320
324
  - lib/web_pipe/extensions/params/params.rb
321
325
  - lib/web_pipe/extensions/params/params/transf.rb
322
326
  - lib/web_pipe/extensions/rails/rails.rb
@@ -324,6 +328,7 @@ files:
324
328
  - lib/web_pipe/extensions/router_params/router_params.rb
325
329
  - lib/web_pipe/extensions/session/session.rb
326
330
  - lib/web_pipe/extensions/url/url.rb
331
+ - lib/web_pipe/pipe.rb
327
332
  - lib/web_pipe/plug.rb
328
333
  - lib/web_pipe/plugs.rb
329
334
  - lib/web_pipe/plugs/config.rb
@@ -331,11 +336,13 @@ files:
331
336
  - lib/web_pipe/rack_support/app_with_middlewares.rb
332
337
  - lib/web_pipe/rack_support/middleware.rb
333
338
  - lib/web_pipe/rack_support/middleware_specification.rb
339
+ - lib/web_pipe/test_support.rb
334
340
  - lib/web_pipe/types.rb
335
341
  - lib/web_pipe/version.rb
336
342
  - web_pipe.gemspec
337
343
  homepage: https://github.com/waiting-for-dev/web_pipe
338
- licenses: []
344
+ licenses:
345
+ - MIT
339
346
  metadata:
340
347
  allowed_push_host: https://rubygems.org
341
348
  homepage_uri: https://github.com/waiting-for-dev/web_pipe
@@ -356,7 +363,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
356
363
  - !ruby/object:Gem::Version
357
364
  version: '0'
358
365
  requirements: []
359
- rubygems_version: 3.2.3
366
+ rubygems_version: 3.1.2
360
367
  signing_key:
361
368
  specification_version: 4
362
369
  summary: Rack application builder through a pipe of operations on an immutable struct.
@@ -1,18 +0,0 @@
1
- # dry-rb integration
2
-
3
- `web_pipe` has been designed to integrate smoothly with
4
- [dry-rb](https://dry-rb.org/) ecosystem. It shares same design
5
- principles and it ships with some extensions which even make this
6
- integration tighter (like
7
- [`:dry-view`](../extensions/dry_view.md) or
8
- [`:dry-schema`](../extensions/dry_schema.md) extensions).
9
-
10
- If you want to use `web_pipe` with the rest of dry-rb libraries,
11
- your best bet is to use
12
- [`dry-web-web_pipe`](https://github.com/waiting-for-dev/dry-web-web_pipe)
13
- skeleton generator. It is a fork of
14
- [`dry-web-roda`](https://github.com/dry-rb/dry-web-roda) with
15
- `roda` dependency switched to a combination of `web_pipe` and
16
- [`hanami-router`](https://github.com/hanami/router).
17
-
18
- Look at `dry-web-web_pipe` README for more details.
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'web_pipe'
4
- require 'web_pipe/types'
5
- require 'web_pipe/plug'
6
- require 'web_pipe/rack_support/middleware_specification'
7
-
8
- module WebPipe
9
- module DSL
10
- # Defines the DSL for the pipe class and keeps it state.
11
- #
12
- # This allows adding rack middlewares and plugs at the class
13
- # definition level.
14
- #
15
- # @api private
16
- class DSLContext
17
- # @!attribute middleware_specifications
18
- # @return [Array<RackSupport::MiddlewareSpecifications>]
19
- attr_reader :middleware_specifications
20
-
21
- # @!attribute plugs
22
- # @return [Array<Plug>]
23
- attr_reader :plugs
24
-
25
- def initialize(middleware_specifications, plugs)
26
- @middleware_specifications = Types.Array(
27
- RackSupport::MiddlewareSpecification
28
- )[middleware_specifications]
29
- @plugs = Types.Array(Plug::Instance)[plugs]
30
- end
31
-
32
- # Creates and add rack middleware specifications to the stack.
33
- #
34
- # The spec can be given in two forms:
35
- #
36
- # - As one or two arguments, first one being a
37
- # rack middleware class and second one optionally its
38
- # initialization options.
39
- # - As a {WebPipe} class instance, in which case all its rack
40
- # middlewares will be considered.
41
- #
42
- # @param name [RackSupport::MiddlewareSpecification::Name[]]
43
- # @param spec [RackSupport::MiddlewareSpecification::Spec[]]
44
- #
45
- # @return [Array<RackSupport::Middleware>]
46
- def use(name, *spec)
47
- middleware_specifications << RackSupport::MiddlewareSpecification.new(name: name, spec: spec)
48
- end
49
-
50
- # Creates and adds a plug to the stack.
51
- #
52
- # The spec can be given as a {Plug::Spec}, as a block (which
53
- # is captured into a {Proc}, one of the options for a
54
- # {Plug::Spec} or as a {WebPipe} (in which case all its plugs
55
- # will be composed).
56
- #
57
- # @param name [Plug::Name[]]
58
- # @param spec [Plug::Spec[], WebPipe]
59
- # @param block_spec [Proc]
60
- #
61
- # @return [Array<Plug>]
62
- def plug(name, spec = nil, &block_spec)
63
- plug_spec = if spec.is_a?(WebPipe)
64
- spec.to_proc
65
- elsif spec
66
- spec
67
- else
68
- block_spec
69
- end
70
-
71
- plugs << Plug.new(name: name, spec: plug_spec)
72
- end
73
-
74
- # Adds middlewares and plugs from a WebPipe to respective
75
- # stacks.
76
- #
77
- # @param name [Plug::Name[], Middleware::Name[]]
78
- # @param spec [WebPipe]
79
- def compose(name, spec)
80
- use(name, spec)
81
- plug(name, spec)
82
- end
83
- end
84
- end
85
- end
@@ -1,114 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'web_pipe/types'
4
- require 'web_pipe/conn'
5
- require 'web_pipe/app'
6
- require 'web_pipe/plug'
7
- require 'web_pipe/rack_support/app_with_middlewares'
8
- require 'web_pipe/rack_support/middleware_specification'
9
- require 'web_pipe/conn_support/composition'
10
-
11
- module WebPipe
12
- module DSL
13
- # Instance methods for the pipe.
14
- #
15
- # It is from here that you get the rack application you can route
16
- # to. The initialization phase gives you the chance to inject any
17
- # of the plugs or middlewares, while the instance you get has the
18
- # `#call` method expected by rack.
19
- #
20
- # The pipe state can be accessed through the pipe class, which
21
- # has been configured through {ClassContext}.
22
- #
23
- # @api private
24
- module InstanceMethods
25
- # No injections at all.
26
- EMPTY_INJECTIONS = {
27
- plugs: Types::EMPTY_HASH,
28
- middlewares: Types::EMPTY_HASH
29
- }.freeze
30
-
31
- # Type for how plugs and middlewares should be injected.
32
- Injections = Types::Strict::Hash.schema(
33
- plugs: Plug::Injections,
34
- middlewares: RackSupport::MiddlewareSpecification::Injections
35
- )
36
-
37
- # @!attribute [r] injections [Injections[]]
38
- # Injected plugs and middlewares that allow overriding what
39
- # has been configured.
40
- attr_reader :injections
41
-
42
- # @return [RackSupport::AppWithMiddlewares[]]
43
- attr_reader :rack_app
44
-
45
- # @return [ConnSupport::Composition::Operation[]]
46
- attr_reader :operations
47
-
48
- # @return [Array<RackSupport::Middlewares>]
49
- attr_reader :middlewares
50
-
51
- # rubocop:disable Metrics/AbcSize
52
- def initialize(injects = EMPTY_INJECTIONS)
53
- @injections = Injections[injects]
54
- container = self.class.container
55
- @middlewares = RackSupport::MiddlewareSpecification.inject_and_resolve(
56
- self.class.middleware_specifications, injections[:middlewares]
57
- )
58
- @operations = Plug.inject_and_resolve(
59
- self.class.plugs, injections[:plugs], container, self
60
- )
61
- app = App.new(operations)
62
- @rack_app = RackSupport::AppWithMiddlewares.new(middlewares, app)
63
- end
64
- # rubocop:enable Metrics/AbcSize
65
-
66
- # Expected interface for rack.
67
- #
68
- # @param env [Hash] Rack env
69
- #
70
- # @return [Array] Rack response
71
- def call(env)
72
- rack_app.call(env)
73
- end
74
-
75
- # Proc for the composition of all operations.
76
- #
77
- # This can be used to plug a {WebPipe} itself as an operation.
78
- #
79
- # @example
80
- # class HtmlApp
81
- # include WebPipe
82
- #
83
- # plug :html
84
- #
85
- # private
86
- #
87
- # def html(conn)
88
- # conn.add_response_header('Content-Type', 'text/html')
89
- # end
90
- # end
91
- #
92
- # class App
93
- # include WebPipe
94
- #
95
- # plug :html, &HtmlApp.new
96
- # plug :body
97
- #
98
- # private
99
- #
100
- # def body(conn)
101
- # conn.set_response_body('Hello, world!')
102
- # end
103
- # end
104
- #
105
- # @see ConnSupport::Composition
106
- def to_proc
107
- ConnSupport::Composition
108
- .new(operations)
109
- .method(:call)
110
- .to_proc
111
- end
112
- end
113
- end
114
- end
@@ -1,158 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'web_pipe/types'
4
- require 'web_pipe/conn'
5
- require 'dry/view'
6
-
7
- #:nodoc:
8
- module WebPipe
9
- # Integration with `dry-view` rendering system.
10
- #
11
- # This extensions adds a {#view} method to {WebPipe::Conn} which
12
- # sets the string output of a `dry-view` view as response body.
13
- #
14
- # @example
15
- # WebPipe.load_extensions(:dry_view)
16
- #
17
- # class SayHelloView < Dry::View
18
- # config.paths = [File.join(__dir__, '..', 'templates')]
19
- # config.template = 'say_hello'
20
- #
21
- # expose :name
22
- # end
23
- #
24
- # class App
25
- # include WebPipe
26
- #
27
- # plug :render
28
- #
29
- # def render(conn)
30
- # conn.view(SayHello.new, name: 'Joe')
31
- # end
32
- # end
33
- #
34
- # If there is a `:container` configured (in {Conn#config}), the view
35
- # instance can be resolved from it.
36
- #
37
- # @example
38
- # WebPipe.load_extensions(:dry_view, :container)
39
- #
40
- # class App
41
- # include WebPipe
42
- #
43
- # Container = { 'views.say_hello' => SayHelloView.new }.freeze
44
- #
45
- # plug :config_container, ->(conn) { conn.add_config(:container, Container) }
46
- # plug :render
47
- #
48
- # def render(conn)
49
- # conn.view('views.say_hello', name: 'Joe')
50
- # end
51
- # end
52
- #
53
- # Context ({Dry::View::Context}) for the view can be set explicetly
54
- # through the `context:` argument, as in a standard call to
55
- # {Dry::View#call}. However, it is possible to leverage configured
56
- # default context while still being able to inject request specific
57
- # context. For that to work, `:view_context` should be present in
58
- # {WebPipe::Conn#config}. Its value must be a lambda accepting the
59
- # {Conn} instance and returning a hash, which will be passed to
60
- # {Dry::View::Context#with} to create the final context at the
61
- # moment {#view} is called.
62
- #
63
- # @example
64
- # class MyContext < Dry::View::Context
65
- # attr_reader :current_path
66
- #
67
- # def initialize(current_path: nil, **options)
68
- # @current_path = current_path
69
- # super
70
- # end
71
- # end
72
- #
73
- # class SayHelloView < Dry::View
74
- # config.paths = [File.join(__dir__, '..', 'templates')]
75
- # config.template = 'say_hello'
76
- # config.default_context = MyContext.new
77
- #
78
- # expose :name
79
- # end
80
- #
81
- # WebPipe.load_extensions(:url)
82
- #
83
- # class App
84
- # include WebPipe
85
- #
86
- # plug :config_view_context
87
- # plug :render
88
- #
89
- # def config_view_context(conn)
90
- # conn.add_config(:view_context, ->(conn) { { current_path: conn.full_path } })
91
- # end
92
- #
93
- # def render(conn)
94
- # conn.view(SayHelloView.new, name: 'Joe') # `current_path`
95
- # # will be available in the view scope
96
- # end
97
- # end
98
- #
99
- # @see https://dry-rb.org/gems/dry-view/
100
- # @see WebPipe::Container
101
- module DryView
102
- # Where to find in {#config} request's view context generator.
103
- VIEW_CONTEXT_KEY = :view_context
104
-
105
- # Default request's view context
106
- DEFAULT_VIEW_CONTEXT = ->(_conn) { Types::EMPTY_HASH }
107
-
108
- # Sets string output of a view as response body.
109
- #
110
- # If the view is not a {Dry::View} instance, it is resolved from
111
- # the configured container.
112
- #
113
- # `kwargs` is used as the input for the view (the arguments that
114
- # {Dry::View#call} receives). If they doesn't contain an explicit
115
- # `context:` key, it can be added through the injection of the
116
- # result of a lambda present in context's `:view_context`.(see
117
- # {Dry::View::Context#with}).
118
- #
119
- # @param view_spec [Dry::View, Any]
120
- # @param kwargs [Hash] Arguments to pass along to `Dry::View#call`
121
- #
122
- # @return WebPipe::Conn
123
- def view(view_spec, **kwargs)
124
- view_instance = view_instance(view_spec)
125
- view_input = view_input(kwargs, view_instance)
126
-
127
- set_response_body(
128
- view_instance.call(
129
- **view_input
130
- ).to_str
131
- )
132
- end
133
-
134
- private
135
-
136
- def view_instance(view_spec)
137
- return view_spec if view_spec.is_a?(Dry::View)
138
-
139
- fetch_config(:container)[view_spec]
140
- end
141
-
142
- def view_input(kwargs, view_instance)
143
- return kwargs if kwargs.key?(:context)
144
-
145
- context = view_instance
146
- .config
147
- .default_context
148
- .with(
149
- **fetch_config(
150
- VIEW_CONTEXT_KEY, DEFAULT_VIEW_CONTEXT
151
- ).call(self)
152
- )
153
- kwargs.merge(context: context)
154
- end
155
- end
156
-
157
- Conn.include(DryView)
158
- end