web_pipe 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
```
|