web_pipe 0.8.0 → 0.9.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +4 -2
- data/README.md +60 -310
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/docs/_config.yml +1 -0
- data/docs/building_a_rack_application.md +25 -0
- data/docs/composing_applications.md +43 -0
- data/docs/connection_struct/configuring_the_connection_struct.md +25 -0
- data/docs/connection_struct/halting_the_pipe.md +71 -0
- data/docs/connection_struct/sharing_data_downstream.md +46 -0
- data/docs/connection_struct.md +77 -0
- data/docs/design_model.md +44 -0
- data/docs/dsl_free_usage.md +26 -0
- data/docs/extensions/container.md +41 -0
- data/docs/extensions/cookies.md +47 -0
- data/docs/extensions/dry_schema.md +51 -0
- data/docs/extensions/dry_view.md +113 -0
- data/docs/extensions/flash.md +41 -0
- data/docs/extensions/params.md +117 -0
- data/docs/extensions/redirect.md +25 -0
- data/docs/extensions/router_params.md +40 -0
- data/docs/extensions/session.md +39 -0
- data/docs/extensions/url.md +11 -0
- data/docs/extensions.md +13 -0
- data/docs/introduction.md +73 -0
- data/docs/plugging_operations/composing_operations.md +36 -0
- data/docs/plugging_operations/injecting_operations.md +32 -0
- data/docs/plugging_operations/resolving_operations.md +71 -0
- data/docs/plugging_operations.md +21 -0
- data/docs/plugs/config.md +18 -0
- data/docs/plugs/content_type.md +16 -0
- data/docs/plugs.md +13 -0
- data/docs/recipes/dry_rb_integration.md +18 -0
- data/docs/recipes/hanami_router_integration.md +25 -0
- data/docs/recipes/using_all_restful_methods.md +25 -0
- data/docs/using_rack_middlewares/composing_middlewares.md +26 -0
- data/docs/using_rack_middlewares/injecting_middlewares.md +47 -0
- data/docs/using_rack_middlewares.md +22 -0
- data/lib/web_pipe/app.rb +4 -2
- data/lib/web_pipe/conn.rb +5 -3
- data/lib/web_pipe/conn_support/builder.rb +2 -0
- data/lib/web_pipe/conn_support/composition.rb +9 -7
- data/lib/web_pipe/conn_support/errors.rb +3 -1
- data/lib/web_pipe/conn_support/headers.rb +12 -10
- data/lib/web_pipe/conn_support/types.rb +11 -9
- data/lib/web_pipe/dsl/builder.rb +5 -3
- data/lib/web_pipe/dsl/class_context.rb +5 -3
- data/lib/web_pipe/dsl/dsl_context.rb +7 -5
- data/lib/web_pipe/dsl/instance_methods.rb +7 -5
- data/lib/web_pipe/extensions/container/container.rb +2 -0
- data/lib/web_pipe/extensions/cookies/cookies.rb +4 -3
- data/lib/web_pipe/extensions/dry_schema/dry_schema.rb +2 -0
- data/lib/web_pipe/extensions/dry_schema/plugs/sanitize_params.rb +4 -2
- data/lib/web_pipe/extensions/dry_view/dry_view.rb +13 -9
- data/lib/web_pipe/extensions/flash/flash.rb +7 -7
- data/lib/web_pipe/extensions/params/params/transf.rb +3 -1
- data/lib/web_pipe/extensions/params/params.rb +7 -5
- data/lib/web_pipe/extensions/redirect/redirect.rb +8 -6
- data/lib/web_pipe/extensions/router_params/router_params.rb +4 -2
- data/lib/web_pipe/extensions/session/session.rb +6 -4
- data/lib/web_pipe/extensions/url/url.rb +3 -1
- data/lib/web_pipe/plug.rb +7 -5
- data/lib/web_pipe/plugs.rb +7 -1
- data/lib/web_pipe/rack_support/app_with_middlewares.rb +3 -1
- data/lib/web_pipe/rack_support/middleware.rb +2 -0
- data/lib/web_pipe/rack_support/middleware_specification.rb +5 -3
- data/lib/web_pipe/types.rb +3 -1
- data/lib/web_pipe/version.rb +3 -1
- data/lib/web_pipe.rb +5 -3
- data/web_pipe.gemspec +34 -34
- metadata +82 -48
@@ -0,0 +1,77 @@
|
|
1
|
+
# Connection struct
|
2
|
+
|
3
|
+
First operation you plug in a `web_pipe` application receives an instance of
|
4
|
+
`WebPipe::Conn` which has been automatically created.
|
5
|
+
|
6
|
+
This is just a struct data type which contains all the information from current
|
7
|
+
web request. In this regard, you can think of it as a structured rack's env
|
8
|
+
hash.
|
9
|
+
|
10
|
+
Request related attributes of this struct are:
|
11
|
+
|
12
|
+
- `#scheme`: `:http` or `:https`.
|
13
|
+
- `#request_method`: `:get`, `:post`...
|
14
|
+
- `#host`: e.g. `'www.example.org'`.
|
15
|
+
- `#ip`: e.g. `'192.168.1.1'`.
|
16
|
+
- `#port`: e.g. `80` or `443`.
|
17
|
+
- `#script_name`: e.g. `'index.rb'`.
|
18
|
+
- `#path_info`: e.g. `'/foor/bar'`.
|
19
|
+
- `#query_string`: e.g. `'foo=bar&bar=foo'`
|
20
|
+
- `#request_body`: e.g. `'{ id: 1 }'`
|
21
|
+
- `#request_headers`: e.g. `{ 'Accept-Charset' => 'utf8' }`
|
22
|
+
- `#env`: Rack's env hash.
|
23
|
+
- `#request`: Rack::Request instance.
|
24
|
+
|
25
|
+
Your operations must return another (or same) instance of the struct, which
|
26
|
+
will be consumed by next operation downstream. The struct contains methods to
|
27
|
+
add response data to it:
|
28
|
+
|
29
|
+
- `#set_status(code)`: makes it accessible in `#status` attribute.
|
30
|
+
- `#set_response_body(body)`: makes it accessible in `#response_body`
|
31
|
+
attribute.
|
32
|
+
- `#set_response_headers(headers)`: makes them accessible in
|
33
|
+
`#response_headers` attribute. Besides, there are also
|
34
|
+
`#add_response_header(key, value)` and `#delete_response_header(key)`
|
35
|
+
methods.
|
36
|
+
|
37
|
+
Response in last struct returned in the pipe will be what is sent to client.
|
38
|
+
|
39
|
+
Every attribute and method is [fully
|
40
|
+
documented](https://www.rubydoc.info/github/waiting-for-dev/web_pipe/master/WebPipe/Conn)
|
41
|
+
in code documentation.
|
42
|
+
|
43
|
+
Here we have a contrived web application which just returns as response body
|
44
|
+
the request body it has received:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
# config.ru
|
48
|
+
require 'web_pipe'
|
49
|
+
|
50
|
+
class DummyApp
|
51
|
+
include WebPipe
|
52
|
+
|
53
|
+
plug :build_response
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_response(conn)
|
58
|
+
conn.
|
59
|
+
set_status(200).
|
60
|
+
add_response_header('Content-Type', 'text/html').
|
61
|
+
set_response_body(
|
62
|
+
"<p>#{conn.request_body}</p>"
|
63
|
+
)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
run DummyApp.new
|
68
|
+
```
|
69
|
+
|
70
|
+
As you can see, default available features are the very minimal to read from a
|
71
|
+
request and to write a response. However, you can pick from several
|
72
|
+
(extensions)[/docs/extensions.md] which will make your life much easier.
|
73
|
+
|
74
|
+
Immutability is a core design principle in `web_pipe`. All methods in
|
75
|
+
`WebPipe::Conn` which are used to add data to it (both in core behaviour and
|
76
|
+
extensions) return a fresh new instance. It also makes possible chaining
|
77
|
+
methods in a very readable way.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Design model
|
2
|
+
|
3
|
+
If you are familiar with rack you know that it models a two-way pipe. In it,
|
4
|
+
each middleware has the ability to:
|
5
|
+
|
6
|
+
- During the outbound trip modifying the request as it heads to the actual
|
7
|
+
application.
|
8
|
+
|
9
|
+
- During the return trip modifying the response as it gets back from the
|
10
|
+
application.
|
11
|
+
|
12
|
+
```
|
13
|
+
|
14
|
+
---------------------> request ----------------------->
|
15
|
+
|
16
|
+
Middleware 1 Middleware 2 Application
|
17
|
+
|
18
|
+
<--------------------- response <-----------------------
|
19
|
+
|
20
|
+
|
21
|
+
```
|
22
|
+
|
23
|
+
`web_pipe` follows a simpler but equally powerful model: a one-way pipe which
|
24
|
+
is abstracted on top of rack. A struct that contains data from a web request is
|
25
|
+
piped trough a stack of operations (functions). Each operation takes as
|
26
|
+
argument an instance of the struct and returns also an instance of it.
|
27
|
+
Response data can be added to the struct at any moment in the pipe.
|
28
|
+
|
29
|
+
```
|
30
|
+
|
31
|
+
Operation 1 Operation 2 Operation 3
|
32
|
+
|
33
|
+
--------------------- request/response ---------------->
|
34
|
+
|
35
|
+
```
|
36
|
+
|
37
|
+
Additionally, any operation in the stack can halt the propagation of the pipe,
|
38
|
+
leaving downstream operations unexecuted. In this way, final response is the
|
39
|
+
one contained in the struct at the moment the pipe was halted, or last one if
|
40
|
+
the pipe wasn't halted.
|
41
|
+
|
42
|
+
As you may know, this is the same model used by Elixir's
|
43
|
+
[`plug`](https://hexdocs.pm/plug/readme.html), from which `web_pipe` takes
|
44
|
+
inspiration.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# DSL free usage
|
2
|
+
|
3
|
+
DSL's (like the one in `web_pipe` with methods like `plug` or
|
4
|
+
`use`) provide developers with a user friendly and ergonomic way to
|
5
|
+
use a library. However, they usually come at expenses of increasing complexity
|
6
|
+
in internal code (which sooner than later translates into some kind of issue).
|
7
|
+
|
8
|
+
`web_pipe` has tried to do an extra effort to minimize these problems. For this
|
9
|
+
reason, DSL in this library is just a layer on top of an independent core
|
10
|
+
functionality.
|
11
|
+
|
12
|
+
To use `web_pipe` without its DSL layer, you just need to initialize a
|
13
|
+
`WebPipe::App` instance with an array of all the operations that otherwise
|
14
|
+
you'd `plug`. That instance will be a rack application ready to be used.
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
# config.ru
|
18
|
+
require 'web_pipe/app'
|
19
|
+
|
20
|
+
op_1 = ->(conn) { conn.set_status(200) }
|
21
|
+
op_2 = ->(conn) { conn.set_response_body('Hello, World!') }
|
22
|
+
|
23
|
+
app = WebPipe::App.new([op_1, op_2])
|
24
|
+
|
25
|
+
run app
|
26
|
+
```
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Container
|
2
|
+
|
3
|
+
`:container` is a very simple extension which allows you to configure a
|
4
|
+
dependency injection container to be accessible from a `WebPipe::Conn`
|
5
|
+
instance.
|
6
|
+
|
7
|
+
The container to use must be configured under `:configuration` key. It will be
|
8
|
+
accessible through the `#container` method.
|
9
|
+
|
10
|
+
You may be thinking why you should worry about configuring a container for a
|
11
|
+
connection instance when you already have access to the container configured
|
12
|
+
for an application (from where you can resolve plugged operations). The idea
|
13
|
+
here is decoupling operations from application DSL. If at anytime in the future
|
14
|
+
you decide to get rid off the DSL, the process will be straightforward if
|
15
|
+
operations are using the container configured in a connection instance.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'web_pipe'
|
19
|
+
require 'web_pipe/plugs/config'
|
20
|
+
require 'my_container'
|
21
|
+
|
22
|
+
WebPipe.load_extensions(:container)
|
23
|
+
|
24
|
+
class MyApp
|
25
|
+
include WebPipe.(container: MyContainer)
|
26
|
+
|
27
|
+
plug :config, WebPipe::Plugs::Config.(
|
28
|
+
container: MyContainer
|
29
|
+
)
|
30
|
+
plug :this, :this # Resolved thanks to the container in `include`
|
31
|
+
plug :that
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def that(conn)
|
36
|
+
conn.set_response_body(
|
37
|
+
conn.container['do'].() # Resolved thanks to the container in `:config`
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Cookies
|
2
|
+
|
3
|
+
Extension helping to deal with request and response cookies.
|
4
|
+
|
5
|
+
Remember, cookies are just the value of `Set-Cookie` header.
|
6
|
+
|
7
|
+
This extension adds following methods:
|
8
|
+
|
9
|
+
- `#request_cookies`: Returns request cookies
|
10
|
+
|
11
|
+
- `#set_cookie(key, value)` or `#set_cookie(key, value, options)`: Instructs
|
12
|
+
browser to add a new cookie with given key and value.
|
13
|
+
|
14
|
+
Some options can be given as keyword arguments (see [MDN reference on
|
15
|
+
cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) for an
|
16
|
+
explanation):
|
17
|
+
|
18
|
+
- `domain:` must be a string.
|
19
|
+
- `path:` must be a string.
|
20
|
+
- `max_age:` must be an integer with the number of seconds.
|
21
|
+
- `expires:` must be a `Time`.
|
22
|
+
- `secure:` must be `true` or `false`.
|
23
|
+
- `http_only:` must be `true` or `false`.
|
24
|
+
- `same_site:` must be one of the symbols `:none`, `:lax` or `:strict`.
|
25
|
+
|
26
|
+
- `#delete_cookie(key)` or `#delete_cookie(key, options)`: Instructs browser to
|
27
|
+
delete a previously sent cookie. Deleting a cookie just means setting again
|
28
|
+
the same key with an expiration time in the past.
|
29
|
+
|
30
|
+
It accepts `domain:` and `path:` options (see above for a description of
|
31
|
+
them).
|
32
|
+
|
33
|
+
Example:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'web_pipe'
|
37
|
+
|
38
|
+
WebPipe.load_extensions(:cookies)
|
39
|
+
|
40
|
+
class MyApp
|
41
|
+
include WebPipe
|
42
|
+
|
43
|
+
plug(:set_cookie) do |conn|
|
44
|
+
conn.set_cookie('foo', 'bar', secure: true, http_only: true)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
```
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Dry Schema
|
2
|
+
|
3
|
+
Extension providing integration for a common
|
4
|
+
[`dry-schema`](https://dry-rb.org/gems/dry-schema/) workflow to validate
|
5
|
+
parameters.
|
6
|
+
|
7
|
+
A plug `WebPipe::Plugs::SanitizeParams` is added so that you can use it in your
|
8
|
+
pipe of operations. It takes as arguments a `dry-schema` schema and a handler.
|
9
|
+
On success, it makes output available at `WebPipe::Conn#sanitized_params`. On
|
10
|
+
error, it calls given handler with the connection struct and validation result.
|
11
|
+
|
12
|
+
This extension automatically loads [`:params` extension](/docs/extensions/params.md),
|
13
|
+
as it takes `WebPipe::Conn#params` as input for the validation schema.
|
14
|
+
|
15
|
+
Instead of providing an error handler as the second argument for the plug, you
|
16
|
+
can configure it under `:param_sanitization_handler` key. In this way, it can
|
17
|
+
be reused through composition by others applications.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'db'
|
21
|
+
require 'dry/schema'
|
22
|
+
require 'web_pipe'
|
23
|
+
require 'web_pipe/plugs/config'
|
24
|
+
|
25
|
+
WebPipe.load_extensions(:dry_schema)
|
26
|
+
|
27
|
+
class MyApp
|
28
|
+
include WebPipe
|
29
|
+
|
30
|
+
Schema = Dry::Schema.Params do
|
31
|
+
required(:name).filled(:string)
|
32
|
+
end
|
33
|
+
|
34
|
+
plug :config, WebPipe::Plugs::Config.(
|
35
|
+
param_sanitization_handler: lambda do |conn, result|
|
36
|
+
conn.
|
37
|
+
set_status(500).
|
38
|
+
set_response_body('Error with request parameters').
|
39
|
+
halt
|
40
|
+
end
|
41
|
+
)
|
42
|
+
|
43
|
+
plug :sanitize_params, WebPipe::Plugs::SanitizeParams.(
|
44
|
+
Schema
|
45
|
+
)
|
46
|
+
|
47
|
+
plug(:this) do |conn|
|
48
|
+
DB.persist(:entity, conn.sanitized_params)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# Dry View
|
2
|
+
|
3
|
+
This extensions integrates with
|
4
|
+
[dry-view](https://dry-rb.org/gems/dry-view/) rendering system to
|
5
|
+
set a dry-view output as response body.
|
6
|
+
|
7
|
+
`WebPipe::Conn#view` method is at the core of this extension. In its basic
|
8
|
+
behaviour, you provide to it a view instance you want to render and any
|
9
|
+
exposures or options it may need:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'web_pipe'
|
13
|
+
require 'dry/view'
|
14
|
+
require 'my_context'
|
15
|
+
|
16
|
+
WebPipe.load_extensions(:dry_view)
|
17
|
+
|
18
|
+
class SayHelloView < Dry::View
|
19
|
+
config.paths = [File.join(__dir__, '..', 'templates')]
|
20
|
+
config.template = 'say_hello'
|
21
|
+
config.default_context = MyContext
|
22
|
+
|
23
|
+
expose :name
|
24
|
+
end
|
25
|
+
|
26
|
+
class MyApp
|
27
|
+
include WebPipe
|
28
|
+
|
29
|
+
plug :render
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def render(conn)
|
34
|
+
conn.view(SayHelloView.new, name: 'Joe')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
However, you can resolve a view from a container if you also use (`:container`
|
40
|
+
extension)[/docs/extensions/container.md]:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
require 'dry_view'
|
44
|
+
require 'my_container'
|
45
|
+
require 'web_pipe'
|
46
|
+
require 'web_pipe/plugs/config'
|
47
|
+
|
48
|
+
WebPipe.load_extensions(:dry_view, :container)
|
49
|
+
|
50
|
+
class MyApp
|
51
|
+
include WebPipe
|
52
|
+
|
53
|
+
plug :config, WebPipe::Plugs::Config.(
|
54
|
+
container: MyContainer
|
55
|
+
)
|
56
|
+
plug :render
|
57
|
+
|
58
|
+
def render(conn)
|
59
|
+
conn.view('views.say_hello', name: 'Joe')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
As in a standard call to `Dry::View#call`, you can override the context
|
65
|
+
(`Dry::View::Context`) to use through `context:` option. However, it is still
|
66
|
+
possible to leverage configured default context while being able to inject
|
67
|
+
request specific data to it.
|
68
|
+
|
69
|
+
For that to work, you have to specify required dependencies (in this case,
|
70
|
+
request specific data) to your dry-view's context. A very convenient way to do
|
71
|
+
that is with [`dry-auto_inject`](https://dry-rb.org/gems/dry-auto_inject):
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
require 'dry/view/context'
|
75
|
+
require 'my_import'
|
76
|
+
|
77
|
+
class MyContext < Dry::View::Context
|
78
|
+
include MyImport::Import[:current_path]
|
79
|
+
|
80
|
+
# Without `dry-auto_inject` you have to manually specify dependencies and
|
81
|
+
# override the initializer:
|
82
|
+
#
|
83
|
+
# attr_reader :current_path
|
84
|
+
#
|
85
|
+
# def initialize(current_path:, **options)
|
86
|
+
# @current_path = current_path
|
87
|
+
# super
|
88
|
+
# end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
Then, you have to configure a `:view_context` setting, which must be a lambda
|
93
|
+
accepting a `WebPipe::Conn` instance and returning a hash matching required
|
94
|
+
dependencies:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
require 'web_pipe'
|
98
|
+
require 'web_pipe/plugs/config'
|
99
|
+
|
100
|
+
WebPipe.load_extensions(:url)
|
101
|
+
|
102
|
+
class MyApp
|
103
|
+
include WebPipe
|
104
|
+
|
105
|
+
plug :config, WebPipe::Plugs::Config.(
|
106
|
+
view_context: ->(conn) { { current_path: conn.full_path} }
|
107
|
+
)
|
108
|
+
plug(:render) do |conn|
|
109
|
+
conn.view(SayHelloView.new, name: 'Joe')
|
110
|
+
# `:current_path` will be provided to the context
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Flash
|
2
|
+
|
3
|
+
This extension provides with typical flash messages functionality. Messages for
|
4
|
+
users are stored in session in order to be consumed by another request after a
|
5
|
+
redirect.
|
6
|
+
|
7
|
+
This extension depends on
|
8
|
+
[`Rack::Flash`](https://rubygems.org/gems/rack-flash3) (gem name is
|
9
|
+
`rack-flash3`) and `Rack::Session` (shipped with rack) middlewares.
|
10
|
+
|
11
|
+
`WebPipe::Conn#flash` contains the flash bag. In order to add a message to it,
|
12
|
+
you can use `#add_flash(key, value)` method.
|
13
|
+
|
14
|
+
There is also an `#add_flash_now(key, value)` method, which is used to add a
|
15
|
+
message to the bag with the intention for it to be consumed in the current
|
16
|
+
request. Be aware that it is in fact a coupling with the view layer.
|
17
|
+
Something that has to be consumed in the current request should be just data
|
18
|
+
given to the view layer, but it helps when it can treat both scenarios as flash
|
19
|
+
messages.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'web_pipe'
|
23
|
+
require 'rack/session/cookie'
|
24
|
+
require 'rack-flash'
|
25
|
+
|
26
|
+
WebPipe.load_extensions(:flash)
|
27
|
+
|
28
|
+
class MyApp
|
29
|
+
include WebPipe
|
30
|
+
|
31
|
+
use :session, Rack::Session::Cookie, secret: 'secret'
|
32
|
+
use :flash, Rack::Flash
|
33
|
+
|
34
|
+
plug :add_to_flash, ->(conn) { conn.add_flash(:notice, 'Hello world') }
|
35
|
+
|
36
|
+
# Usually you will end up making `conn.flash` available to your view
|
37
|
+
# system:
|
38
|
+
#
|
39
|
+
# <div class="notice"><%= flash[:notice] %></div>
|
40
|
+
end
|
41
|
+
```
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# Params
|
2
|
+
|
3
|
+
This extension adds a `WebPipe::Conn#params` method which returns
|
4
|
+
request parameters as a Hash where any number of transformations
|
5
|
+
can be configured.
|
6
|
+
|
7
|
+
When no transformations are configured, `#params` just returns GET and POST parameters as a hash:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# http://www.example.com?foo=bar
|
11
|
+
conn.params # => { 'foo' => 'bar' }
|
12
|
+
```
|
13
|
+
|
14
|
+
You can configure a stack of transformations to be applied to the
|
15
|
+
parameter hash. For that, we lean on [`transproc`
|
16
|
+
gem](https://github.com/solnic/transproc) (you have to add it yourself to your
|
17
|
+
Gemfile). All hash transformations in `transproc` are available by default.
|
18
|
+
|
19
|
+
Transformations must be configured under `:param_transformations`
|
20
|
+
key:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'web_pipe'
|
24
|
+
require 'web_pipe/plugs/config'
|
25
|
+
|
26
|
+
WebPipe.load_extensions(:params)
|
27
|
+
|
28
|
+
class MyApp
|
29
|
+
incude WebPipe
|
30
|
+
|
31
|
+
plug :config, WebPipe::Plugs::Config.(
|
32
|
+
param_transformations: [:deep_symbolize_keys]
|
33
|
+
)
|
34
|
+
|
35
|
+
plug(:this) do |conn|
|
36
|
+
# http://www.example.com?foo=bar
|
37
|
+
conn.params => # => { foo: 'bar' }
|
38
|
+
# ...
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Extra needed arguments can be provided as an array:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# ...
|
47
|
+
plug :config, WebPipe::Plugs::Config.(
|
48
|
+
param_transformations: [
|
49
|
+
:deep_symbolize_keys, [:reject_keys, [:zoo]]
|
50
|
+
]
|
51
|
+
)
|
52
|
+
|
53
|
+
plug(:this) do |conn|
|
54
|
+
# http://www.example.com?foo=bar&zoo=zoo
|
55
|
+
conn.params => # => { foo: 'bar' }
|
56
|
+
# ...
|
57
|
+
end
|
58
|
+
# ...
|
59
|
+
```
|
60
|
+
|
61
|
+
Custom transformations can be registered in `WebPipe::Params::Transf` `transproc` register:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
fake = ->(_params) { { fake: :params } }
|
65
|
+
WebPipe::Params::Transf.register(:fake, fake)
|
66
|
+
|
67
|
+
# ...
|
68
|
+
plug :config, WebPipe::Plugs::Config.(
|
69
|
+
param_transformations: [:fake]
|
70
|
+
)
|
71
|
+
|
72
|
+
plug(:this) do |conn|
|
73
|
+
# http://www.example.com?foo=bar
|
74
|
+
conn.params => # => { fake: :params }
|
75
|
+
# ...
|
76
|
+
end
|
77
|
+
# ...
|
78
|
+
```
|
79
|
+
|
80
|
+
Your own transformation functions can depend on the `WebPipe::Conn`
|
81
|
+
instance at the moment of calling `#params`. Those functions must accept
|
82
|
+
the connection struct as its last argument:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
add_name = ->(params, conn) { params.merge(name: conn.fetch(:name)) }
|
86
|
+
WebPipe::Params::Transf.register(:add_name, add_name)
|
87
|
+
|
88
|
+
# ...
|
89
|
+
plug :config, WebPipe::Plugs::Config.(
|
90
|
+
param_transformations: [:deep_symbolize_keys, :add_name]
|
91
|
+
)
|
92
|
+
|
93
|
+
plug(:add_name) do |conn|
|
94
|
+
conn.add(:name, 'Alice')
|
95
|
+
end
|
96
|
+
|
97
|
+
plug(:this) do |conn|
|
98
|
+
# http://www.example.com?foo=bar
|
99
|
+
conn.params => # => { foo: :bar, name: 'Alice' }
|
100
|
+
# ...
|
101
|
+
end
|
102
|
+
# ...
|
103
|
+
```
|
104
|
+
Finally, you can override configured transformations injecting another set at the moment of calling `#params`:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
# ...
|
108
|
+
plug :config, WebPipe::Plugs::Config.(
|
109
|
+
param_transformations: [:deep_symbolize_keys]
|
110
|
+
)
|
111
|
+
|
112
|
+
plug(:this) do |conn|
|
113
|
+
# http://www.example.com?foo=bar&zoo=zoo
|
114
|
+
conn.params([:reject_keys, ['zoo']]) => # => { 'foo' => 'zoo' }
|
115
|
+
# ...
|
116
|
+
end
|
117
|
+
# ...
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Redirect
|
2
|
+
|
3
|
+
This extension helps with creating a redirect response.
|
4
|
+
|
5
|
+
Redirect responses consist of two pieces:
|
6
|
+
|
7
|
+
- `Location` response header with the URL to which browsers should redirect.
|
8
|
+
- A 3xx status code.
|
9
|
+
|
10
|
+
A `#redirect(location, code)` method is added to `WebPipe::Conn` which takes
|
11
|
+
care of both steps. `code` argument is optional, defaulting to `302`.
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require 'web_pipe'
|
15
|
+
|
16
|
+
WebPipe.load_extensions(:redirect)
|
17
|
+
|
18
|
+
class MyApp
|
19
|
+
include WebPipe
|
20
|
+
|
21
|
+
plug(:redirect) do |conn|
|
22
|
+
conn.redirect('/')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# Router params
|
2
|
+
|
3
|
+
This extension can be used in order to merge placeholder parameters
|
4
|
+
that usually routers support (like `get /users/:id`) to the parameters hash
|
5
|
+
added through [`:params` extension](/docs/extensions/params.md) (which is
|
6
|
+
automatically loaded).
|
7
|
+
|
8
|
+
What this extension does is adding a transformation function to the registry
|
9
|
+
with name `:router_params`. Internally, it merges what is present in rack env's
|
10
|
+
`router.params` key.
|
11
|
+
|
12
|
+
It automatically integrates with
|
13
|
+
[`hanami-router`](https://github.com/hanami/router).
|
14
|
+
|
15
|
+
Don't forget that you have to add yourself the `:router_params`
|
16
|
+
transformation to the stack.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
require 'web_pipe'
|
20
|
+
require 'web_pipe/plugs/config'
|
21
|
+
|
22
|
+
WebPipe.load_extensions(:router_params)
|
23
|
+
|
24
|
+
class MyApp
|
25
|
+
include WebPipe
|
26
|
+
|
27
|
+
plug :config, WebPipe::Plugs::Config.(
|
28
|
+
param_transformations: [:router_params, :deep_symbolize_keys]
|
29
|
+
)
|
30
|
+
plug :this
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def this(conn)
|
35
|
+
# http://example.com/users/1/edit
|
36
|
+
conn.params # => { id: 1 }
|
37
|
+
# ...
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Session
|
2
|
+
|
3
|
+
Wrapper around `Rack::Session` middleware to help working with
|
4
|
+
sessions in your plugged operations.
|
5
|
+
|
6
|
+
It depends on `Rack::Session` middleware, which is shipped by rack.
|
7
|
+
|
8
|
+
It adds following methods to `WebPipe::Conn`:
|
9
|
+
|
10
|
+
- `#fetch_session(key)`, `#fetch_session(key, default)` or
|
11
|
+
`#fetch_session(key) { default }`. Returns what is stored under
|
12
|
+
given session key. A default value can be given as a second
|
13
|
+
argument or a block.
|
14
|
+
- `#add_session(key, value)`. Adds given key/value pair to the
|
15
|
+
session.
|
16
|
+
- `#delete_session(key)`. Deletes given key from the session.
|
17
|
+
- `#clear_session`. Deletes everything from the session.
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'web_pipe'
|
21
|
+
require 'rack/session'
|
22
|
+
|
23
|
+
WebPipe.load_extensions(:session)
|
24
|
+
|
25
|
+
class MyApp
|
26
|
+
include WebPipe
|
27
|
+
|
28
|
+
use Rack::Session::Cookie, secret: 'top_secret'
|
29
|
+
|
30
|
+
plug(:add_to_session) do |conn|
|
31
|
+
conn.add_session('foo', 'bar')
|
32
|
+
end
|
33
|
+
plug(:fetch_from_session) do |conn|
|
34
|
+
conn.add(
|
35
|
+
:foo, conn.fetch_session('foo')
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
```
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# URL
|
2
|
+
|
3
|
+
`:url` extension just adds a few methods which cook raw request information
|
4
|
+
about the URL into something more digestible.
|
5
|
+
|
6
|
+
Specifically, it adds:
|
7
|
+
|
8
|
+
- `#base_url`: Which is schema + host + port (unless it is the default for the scheme). I.e. `'https://example.org'` or `'http://example.org:8000'`.
|
9
|
+
- `#path`: Which is script name (if any) + path information. I.e. `'index.rb/users/1'` or `'users/1'`.
|
10
|
+
- `#full_path`: Which is path + query string (if any). I.e. `'users/1?view=table'`.
|
11
|
+
- `#url`: Which is base url + full path. I.e. `'http://example.org:8000/users/1?view=table'`.
|
data/docs/extensions.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Extensions
|
2
|
+
|
3
|
+
`WebPipe::Conn` features are by default raw: the very minimal you
|
4
|
+
need to be able to build a web application. However, there are
|
5
|
+
several extensions to progressively add just the ingredients you
|
6
|
+
want to use.
|
7
|
+
|
8
|
+
In order to load the extensions, you have to call
|
9
|
+
`#load_extensions` method in `WebPipe`:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
WebPipe.load_extensions(:params, :cookies)
|
13
|
+
```
|