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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc647eb09c8b7eb1d3bcfb910cc55e840285c6b1ab9bd9c69ea9b630f7a116fd
|
4
|
+
data.tar.gz: 6b667b8e71f1fac8d7c46a8160cbff273c70042bf1cc9b06da60c82a83fea5b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f52d9d4c5b381d13de8f27ae03e8fc8bfb2cfe7e020d507f934341c9823f7c2cb2d94ebddc500d9337a59c2437c8ae3899e1b7ef942ed5e1cfc073591f8ac56f
|
7
|
+
data.tar.gz: 0be15319332d738f2b8ff07d7cb9d73583d00bf907e8787a2b8ab0fadc88d235f441b08e546bf162a6fb6fca8953ab68c34077cf300c0d6c1bb26aa37fe8f964
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
5
5
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
6
6
|
|
7
|
+
## [0.9.0] - 2019-08-31
|
8
|
+
### Added
|
9
|
+
- Comprehensive documentation.
|
10
|
+
[[#35]](https://github.com/waiting-for-dev/web_pipe/pull/35)
|
11
|
+
|
7
12
|
## [0.8.0] - 2019-08-30
|
8
13
|
### Added
|
9
14
|
- **BREAKING**. Rename `Rack` module to `RackSupport`.
|
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in web_pipe.gemspec
|
6
8
|
gemspec
|
data/README.md
CHANGED
@@ -3,339 +3,89 @@
|
|
3
3
|
|
4
4
|
# WebPipe
|
5
5
|
|
6
|
-
`web_pipe` is a rack application builder through a pipe of
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
[
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
the propagation of the pipe, leaving any downstream operation
|
48
|
-
unexecuted. This is mainly useful to unauthorize a request while being
|
49
|
-
sure that nothing else will be done to the response.
|
50
|
-
|
51
|
-
As you may know, this is the same model used by Elixir's
|
52
|
-
[`plug`](https://hexdocs.pm/plug/readme.html), from which `web_pipe`
|
53
|
-
takes inspiration.
|
54
|
-
|
55
|
-
This library has been designed to work frictionless along the
|
56
|
-
[`dry-rb`]( https://dry-rb.org/) ruby ecosystem and it uses some of
|
57
|
-
its libraries internally.
|
58
|
-
|
59
|
-
## Usage
|
60
|
-
|
61
|
-
This is a sample `config.ru` for a contrived application built with
|
62
|
-
`web_pipe`. It simply fetches a user from an `id` request
|
63
|
-
parameter. If the user is not found, it returns a not found
|
64
|
-
response. If it is found, it will unauthorize when it is a non `admin`
|
65
|
-
user or greet it otherwise:
|
66
|
-
|
67
|
-
```
|
68
|
-
rackup --port 4000
|
69
|
-
# http://localhost:4000?id=1 => Hello Alice
|
70
|
-
# http://localhost:4000?id=2 => Unauthorized
|
71
|
-
# http://localhost:4000?id=3 => Not found
|
72
|
-
```
|
6
|
+
`web_pipe` is a modular rack application builder through a pipe of
|
7
|
+
operations on an immutable struct.
|
8
|
+
|
9
|
+
In order to use in conjunction with [dry-rb](https://dry-rb.org/)
|
10
|
+
ecosystem, see also
|
11
|
+
[`dry-web-web_pipe`](https://github.com/waiting-for-dev/dry-web-web_pipe).
|
12
|
+
|
13
|
+
1. [Introduction](/docs/introduction.md)
|
14
|
+
1. [Design model](/docs/design_model.md)
|
15
|
+
1. [Building a rack application](/docs/building_a_rack_application.md)
|
16
|
+
1. [Plugging operations](/docs/plugging_operations.md)
|
17
|
+
1. [Resolving operations](/docs/plugging_operations/resolving_operations.md)
|
18
|
+
1. [Injecting operations](/docs/plugging_operations/injecting_operations.md)
|
19
|
+
1. [Composing operations](/docs/plugging_operations/composing_operations.md)
|
20
|
+
1. [Using rack middlewares](/docs/using_rack_middlewares.md)
|
21
|
+
1. [Injecting middlewares](/docs/using_rack_middlewares/injecting_middlewares.md)
|
22
|
+
1. [Composing middlewares](/docs/using_rack_middlewares/composing_middlewares.md)
|
23
|
+
1. [Composing applications](/docs/composing_applications.md)
|
24
|
+
1. [Connection struct](/docs/connection_struct.md)
|
25
|
+
1. [Sharing data downstream](/docs/connection_struct/sharing_data_downstream.md)
|
26
|
+
1. [Halting the pipe](/docs/connection_struct/halting_the_pipe.md)
|
27
|
+
1. [Configuring the connection struct](/docs/connection_struct/configuring_the_connection_struct.md)
|
28
|
+
1. [DSL free usage](/docs/dsl_free_usage.md)
|
29
|
+
1. [Plugs](/docs/plugs.md)
|
30
|
+
1. [Config](/docs/plugs/config.md)
|
31
|
+
1. [ContentType](/docs/plugs/content_type.md)
|
32
|
+
1. [Extensions](/docs/extensions.md)
|
33
|
+
1. [Container](/docs/extensions/container.md)
|
34
|
+
1. [Cookies](/docs/extensions/cookies.md)
|
35
|
+
1. [Flash](/docs/extensions/flash.md)
|
36
|
+
1. [Dry Schema](/docs/extensions/dry_schema.md)
|
37
|
+
1. [Dry View](/docs/extensions/dry_view.md)
|
38
|
+
1. [Params](/docs/extensions/params.md)
|
39
|
+
1. [Redirect](/docs/extensions/redirect.md)
|
40
|
+
1. [Router params](/docs/extensions/router_params.md)
|
41
|
+
1. [Session](/docs/extensions/session.md)
|
42
|
+
1. [URL](/docs/extensions/url.md)
|
43
|
+
1. Recipes
|
44
|
+
1. [dry-rb integration](/docs/recipes/dry_rb_integration.md)
|
45
|
+
1. [hanami-router integration](/docs/recipes/hanami_router_integration.md)
|
46
|
+
1. [Using all RESTful methods](/docs/recipes/using_all_restful_methods.mb)
|
73
47
|
|
74
48
|
```ruby
|
75
49
|
# config.ru
|
76
|
-
require
|
50
|
+
require 'web_pipe'
|
77
51
|
|
78
|
-
|
79
|
-
1 => { name: 'Alice', admin: true },
|
80
|
-
2 => { name: 'Joe', admin: false }
|
81
|
-
}
|
52
|
+
WebPipe.load_extensions(:params)
|
82
53
|
|
83
|
-
class
|
54
|
+
class HelloApp
|
84
55
|
include WebPipe
|
85
|
-
|
86
|
-
|
87
|
-
|
56
|
+
|
57
|
+
AUTHORIZED_USERS = %w[Alice Joe]
|
58
|
+
|
59
|
+
plug :html
|
88
60
|
plug :authorize
|
89
61
|
plug :greet
|
90
|
-
|
62
|
+
|
91
63
|
private
|
92
|
-
|
93
|
-
def
|
94
|
-
conn.add_response_header(
|
95
|
-
'Content-Type', 'text/html'
|
96
|
-
)
|
97
|
-
end
|
98
|
-
|
99
|
-
def fetch_user(conn)
|
100
|
-
user = UsersRepo[conn.params['id'].to_i]
|
101
|
-
if user
|
102
|
-
conn.
|
103
|
-
add(:user, user)
|
104
|
-
else
|
105
|
-
conn.
|
106
|
-
set_status(404).
|
107
|
-
set_response_body('<h1>Not foud</h1>').
|
108
|
-
halt
|
109
|
-
end
|
64
|
+
|
65
|
+
def html(conn)
|
66
|
+
conn.add_response_header('Content-Type', 'text/html')
|
110
67
|
end
|
111
|
-
|
68
|
+
|
112
69
|
def authorize(conn)
|
113
|
-
|
114
|
-
|
70
|
+
user = conn.params['user']
|
71
|
+
if AUTHORIZED_USERS.include?(user)
|
72
|
+
conn.add(:user, user)
|
115
73
|
else
|
116
74
|
conn.
|
117
75
|
set_status(401).
|
118
|
-
set_response_body('<h1>
|
76
|
+
set_response_body('<h1>Not authorized</h1>').
|
119
77
|
halt
|
120
78
|
end
|
121
79
|
end
|
122
80
|
|
123
81
|
def greet(conn)
|
124
|
-
conn.
|
125
|
-
set_response_body("<h1>Hello #{conn.fetch(:user)[:name]}</h1>")
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
run GreetingAdminApp.new
|
130
|
-
```
|
131
|
-
|
132
|
-
As you see, steps required are:
|
133
|
-
|
134
|
-
- Include `WebPipe` in a class.
|
135
|
-
- Specify the stack of operations with `plug`.
|
136
|
-
- Implement those operations.
|
137
|
-
- Initialize the class to obtain resulting rack application.
|
138
|
-
|
139
|
-
`WebPipe::Conn` is a struct of request and response date, seasoned
|
140
|
-
with methods that act on its data. These methods are designed to
|
141
|
-
return a new instance of the struct each time, so they encourage
|
142
|
-
immutability and make method chaining possible.
|
143
|
-
|
144
|
-
Each operation in the pipe must accept a single argument of a
|
145
|
-
`WebPipe::Conn` instance and it must also return an instance of it.
|
146
|
-
In fact, what the first operation in the pipe takes is a
|
147
|
-
`WebPipe::Conn::Ongoing` subclass instance. When one of your operations
|
148
|
-
calls `#halt` on it, a `WebPipe::Conn::Halted` is returned and the pipe
|
149
|
-
is halted. This one or the 'ongoing' instance that reaches the end of
|
150
|
-
the pipe will be in command of the web response.
|
151
|
-
|
152
|
-
Operations have the chance to prepare data to be consumed by
|
153
|
-
downstream operations. Data can be added to the struct through
|
154
|
-
`#add(key, value)`, while it can be consumed with `#fetch(key)`.
|
155
|
-
|
156
|
-
Attributes and methods in `WebPipe::Conn` are [fully
|
157
|
-
documented](https://www.rubydoc.info/github/waiting-for-dev/web_pipe/master/WebPipe/Conn).
|
158
|
-
|
159
|
-
### Specifying operations
|
160
|
-
|
161
|
-
There are several ways you can `plug` operations to the pipe:
|
162
|
-
|
163
|
-
#### Instance methods
|
164
|
-
|
165
|
-
Operations can be just methods defined in the pipe class. This is what
|
166
|
-
you saw in the previous example:
|
167
|
-
|
168
|
-
```ruby
|
169
|
-
class App
|
170
|
-
include WebPipe
|
171
|
-
|
172
|
-
plug :hello
|
173
|
-
|
174
|
-
private
|
175
|
-
|
176
|
-
def hello(conn)
|
177
|
-
# ...
|
178
|
-
end
|
179
|
-
```
|
180
|
-
|
181
|
-
#### Proc (or anything responding to `#call`)
|
182
|
-
|
183
|
-
Operations can also be defined inline as anything that responds to
|
184
|
-
`#call`, like a `Proc`, or also like a block:
|
185
|
-
|
186
|
-
```ruby
|
187
|
-
class App
|
188
|
-
include WebPipe
|
189
|
-
|
190
|
-
plug :hello, ->(conn) { conn }
|
191
|
-
plug(:hello2) { |conn| conn }
|
192
|
-
end
|
193
|
-
```
|
194
|
-
|
195
|
-
The representation of a `WebPipe` as a Proc is itself an operation
|
196
|
-
accepting a `Conn` and returning a `Conn`: the composition of all its
|
197
|
-
plugs. Therefore, it can be plugged to any other `WebPipe`:
|
198
|
-
|
199
|
-
```ruby
|
200
|
-
class HtmlApp
|
201
|
-
include WebPipe
|
202
|
-
|
203
|
-
plug :html
|
204
|
-
|
205
|
-
private
|
206
|
-
|
207
|
-
def html(conn)
|
208
|
-
conn.add_response_header('Content-Type', 'text/html')
|
82
|
+
conn.set_response_body("<h1>Hello #{conn.fetch(:user)}</h1>")
|
209
83
|
end
|
210
84
|
end
|
211
85
|
|
212
|
-
|
213
|
-
include WebPipe
|
214
|
-
|
215
|
-
plug :html, HtmlApp.new
|
216
|
-
plug :body
|
217
|
-
|
218
|
-
private
|
219
|
-
|
220
|
-
def body(conn)
|
221
|
-
conn.set_response_body('Hello, world!')
|
222
|
-
end
|
223
|
-
end
|
224
|
-
```
|
225
|
-
|
226
|
-
#### Container
|
227
|
-
|
228
|
-
When a `String` or a `Symbol` is given, it can be used as the key to
|
229
|
-
resolve an operation from a container. A container is just anything
|
230
|
-
responding to `#[]`.
|
231
|
-
|
232
|
-
The container to be used is configured when you include `WebPipe`:
|
233
|
-
|
234
|
-
```ruby
|
235
|
-
class App
|
236
|
-
Container = Hash[
|
237
|
-
'plugs.hello' => ->(conn) { conn }
|
238
|
-
]
|
239
|
-
|
240
|
-
include WebPipe.(container: Container)
|
241
|
-
|
242
|
-
plug :hello, 'plugs.hello'
|
243
|
-
end
|
86
|
+
run HelloApp.new
|
244
87
|
```
|
245
88
|
|
246
|
-
### Operations injection
|
247
|
-
|
248
|
-
Operations can be injected when the application is initialized,
|
249
|
-
overriding those configured through `plug`:
|
250
|
-
|
251
|
-
```ruby
|
252
|
-
class App
|
253
|
-
include WebPipe
|
254
|
-
|
255
|
-
plug :hello, ->(conn) { conn.set_response_body('Hello') }
|
256
|
-
end
|
257
|
-
|
258
|
-
run App.new(plugs: {
|
259
|
-
hello: ->(conn) { conn.set_response_body('Injected') }
|
260
|
-
}
|
261
|
-
)
|
262
|
-
```
|
263
|
-
|
264
|
-
In the previous example, resulting response body would be `Injected`.
|
265
|
-
|
266
|
-
### Rack middlewares
|
267
|
-
|
268
|
-
Rack middlewares can be added to the generated application through
|
269
|
-
`use`. They will be executed in declaration order before the pipe of
|
270
|
-
plugs:
|
271
|
-
|
272
|
-
```ruby
|
273
|
-
class App
|
274
|
-
include WebPipe
|
275
|
-
|
276
|
-
use :middleware_1, Middleware1
|
277
|
-
use :middleware_1, Middleware2, option_1: value_1
|
278
|
-
|
279
|
-
plug :hello, ->(conn) { conn }
|
280
|
-
end
|
281
|
-
```
|
282
|
-
|
283
|
-
It is also possible to compose all the middlewares from another pipe
|
284
|
-
class. Extending from previous example:
|
285
|
-
|
286
|
-
```ruby
|
287
|
-
class App2
|
288
|
-
include WebPipe
|
289
|
-
|
290
|
-
use :app, App.new # it will also use Middleware1 and Middleware2
|
291
|
-
|
292
|
-
plug :hello, ->(conn) { conn }
|
293
|
-
end
|
294
|
-
```
|
295
|
-
|
296
|
-
Middlewares can also be injected on initialization:
|
297
|
-
|
298
|
-
```ruby
|
299
|
-
App.new(middlewares: {
|
300
|
-
middleware_1: [AnotherMiddleware, options]
|
301
|
-
})
|
302
|
-
```
|
303
|
-
|
304
|
-
### Standalone usage
|
305
|
-
|
306
|
-
If you prefer, you can use the application builder without the
|
307
|
-
DSL. For that, you just have to initialize a `WebPipe::App` with an
|
308
|
-
array of all the operations to be performed:
|
309
|
-
|
310
|
-
```ruby
|
311
|
-
require 'web_pipe/app`
|
312
|
-
|
313
|
-
op_1 = ->(conn) { conn.set_status(200) }
|
314
|
-
op_2 = ->(conn) { conn.set_response_body('Hello') }
|
315
|
-
|
316
|
-
WebPipe::App.new([op_1, op_2])
|
317
|
-
```
|
318
|
-
|
319
|
-
## Plugs
|
320
|
-
|
321
|
-
`web_pipe` ships with a series of common operations you can take
|
322
|
-
advantage in order to build your application:
|
323
|
-
|
324
|
-
- [content_type](lib/web_pipe/plugs/content_type.rb): Sets
|
325
|
-
`Content-Type` response header.
|
326
|
-
|
327
|
-
## Extensions
|
328
|
-
|
329
|
-
By default, `web_pipe` behavior is the very minimal you need to build
|
330
|
-
a web application. However, you can extend it with the following
|
331
|
-
extensions (click on each name for details on the usage):
|
332
|
-
|
333
|
-
- [container](lib/web_pipe/plugs/container.rb): Allows
|
334
|
-
configuring a container to resolve dependencies.
|
335
|
-
- [dry-view](lib/web_pipe/extensions/dry_view/dry_view.rb):
|
336
|
-
Integration with [`dry-view`](https://dry-rb.org/gems/dry-view/)
|
337
|
-
rendering system.
|
338
|
-
|
339
89
|
## Current status
|
340
90
|
|
341
91
|
`web_pipe` is in active development. The very basic features to build
|
@@ -350,4 +100,4 @@ https://github.com/waiting-for-dev/web_pipe.
|
|
350
100
|
|
351
101
|
## Release Policy
|
352
102
|
|
353
|
-
`web_pipe` follows the principles of [semantic versioning](http://semver.org/).
|
103
|
+
`web_pipe` follows the principles of [semantic versioning](http://semver.org/).
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
|
-
require
|
4
|
-
require
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'web_pipe'
|
5
6
|
|
6
7
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
8
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +11,5 @@ require "web_pipe"
|
|
10
11
|
# require "pry"
|
11
12
|
# Pry.start
|
12
13
|
|
13
|
-
require
|
14
|
+
require 'pry'
|
14
15
|
Pry.start
|
data/docs/_config.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
theme: jekyll-theme-slate
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Building a rack application
|
2
|
+
|
3
|
+
In order to build a rack application with `web_pipe` you have to include
|
4
|
+
`WebPipe` module in a class:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
require 'web_pipe'
|
8
|
+
|
9
|
+
class MyApp
|
10
|
+
include WebPipe
|
11
|
+
|
12
|
+
# ...
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
Then, you can plug the operations and add the rack middlewares you need.
|
17
|
+
|
18
|
+
The instance of that class will be the rack application:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
# config.ru
|
22
|
+
require 'my_app'
|
23
|
+
|
24
|
+
run MyApp.new
|
25
|
+
```
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Composing applications
|
2
|
+
|
3
|
+
Previously, we have seen how to [compose plugged
|
4
|
+
operations](/docs/plugging_operations/composing_operations.md) and how to [compose
|
5
|
+
rack middlewares](/docs/using_rack_middlewares/composing_middlewares.md). The logical
|
6
|
+
next step is thinking about composing `web_pipe` applications, which is exactly
|
7
|
+
the same as composing both operations and middlewares at the same time.
|
8
|
+
|
9
|
+
The DSL method `compose` does exactly that:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class HtmlApp
|
13
|
+
include WebPipe
|
14
|
+
|
15
|
+
use :session, Rack::Session::Cookie, key: 'my_app.session', secret: 'long'
|
16
|
+
use :csrf, Rack::Csrf, raise: true
|
17
|
+
|
18
|
+
plug :content_type
|
19
|
+
plug :default_status
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def content_type(conn)
|
24
|
+
conn.add_response_header('Content-Type' => 'text/html')
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_status(conn)
|
28
|
+
conn.set_status(404)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MyApp
|
33
|
+
include WebPipe
|
34
|
+
|
35
|
+
compose :web, HtmlApp.new
|
36
|
+
# It does exactly the same than:
|
37
|
+
# use :web, HtmlApp.new
|
38
|
+
# plug :web, HtmlApp.new
|
39
|
+
|
40
|
+
# use ...
|
41
|
+
# plug ...
|
42
|
+
end
|
43
|
+
```
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Configuring the connection struct
|
2
|
+
|
3
|
+
[Extensions](/docs/extensions.md) add extra behaviour to the connection struct.
|
4
|
+
Sometimes they need some user provided value to work properly or they may allow
|
5
|
+
some tweak depending on user needs.
|
6
|
+
|
7
|
+
For this reason, you can add configuration data to a `WebPipe::Conn` instance
|
8
|
+
so that extensions can fetch it. This shared place where extensions look for
|
9
|
+
what they need is `#config` attribute, which is very similar to `#bag` except
|
10
|
+
for its more private intention.
|
11
|
+
|
12
|
+
In order to interact with `#config`, you can use the method `#add_config(key,
|
13
|
+
value)` or [`Config` plug](/docs/plugs/config.md).
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class MyApp
|
17
|
+
include WebPipe
|
18
|
+
|
19
|
+
plug(:config) do |conn|
|
20
|
+
conn.add_config(
|
21
|
+
foo: :bar
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Halting the pipe
|
2
|
+
|
3
|
+
Each operation in a pipe takes a single `WebPipe::Conn` instance as argument
|
4
|
+
and returns another (or same) instance of it. In this way, a series of
|
5
|
+
operations on the connection struct is propagated until final response is sent
|
6
|
+
to the client.
|
7
|
+
|
8
|
+
More often than not, you may need to conditionally stop the propagation of a
|
9
|
+
pipe at a given operation. A lot of times the requirement to do something like
|
10
|
+
that will be authorization policies. For example, you could fetch the user
|
11
|
+
requesting a resource. In the case she was granted to perform required action
|
12
|
+
you would go on. However, if she wasn't you would like to halt the connection
|
13
|
+
and respond with a 4xx http status code.
|
14
|
+
|
15
|
+
In order to stop the pipe, you simply have to call `#halt` on the connection
|
16
|
+
struct.
|
17
|
+
|
18
|
+
At implementation level, we must admit that we've not been 100% accurate until
|
19
|
+
now. We said that the first operation in the pipe recerived a `WebPipe::Conn`
|
20
|
+
instance. That's true. However, it is more precise saying that it gets a
|
21
|
+
`WebPipe::Conn::Ongoing` instance (`WebPipe::Conn::Ongoing` being a subclass of
|
22
|
+
`WebPipe::Conn`).
|
23
|
+
|
24
|
+
As long as an operation responds with a `WebPipe::Conn::Ongoing`
|
25
|
+
instance, the propagation will go on. However, when an operation
|
26
|
+
returns a `WebPipe::Conn::Halted` instance (another subclass of
|
27
|
+
`WebPipe::Conn`) then any operation downstream will be ignored.
|
28
|
+
Calling `#halt` simply copies all attributes to a `WebPipe::Conn::Halted`
|
29
|
+
instance and returns it.
|
30
|
+
|
31
|
+
This made-up example checks if the user in the request has an admin role. If
|
32
|
+
she has, it returns solicited resource. Otherwise she is unauthorized and never
|
33
|
+
gets the resource.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
WebPipe.load_extensions(:params)
|
37
|
+
|
38
|
+
class ShowTaskApp
|
39
|
+
include WebPipe
|
40
|
+
|
41
|
+
plug :fetch_user
|
42
|
+
plug :authorize
|
43
|
+
plug :render_task
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fetch_user(conn)
|
48
|
+
conn.add(
|
49
|
+
:user, UserRepo.find(conn.params[:user_id])
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def authorize(conn)
|
54
|
+
if conn.fetch(:user).admin?
|
55
|
+
conn
|
56
|
+
else
|
57
|
+
conn.
|
58
|
+
set_status(401).
|
59
|
+
halt
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def render_task(conn)
|
64
|
+
conn.set_response_body(
|
65
|
+
TaskRepo.find(conn.params[:id]).to_json
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
run ShowTaskApp.new
|
71
|
+
```
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Sharing data downstream
|
2
|
+
|
3
|
+
Usually you'll find the need to prepare some data in one operation with the
|
4
|
+
intention for it to be consumed by another downstream operation. The connection
|
5
|
+
struct has a `#bag` attribute which is useful for this purpose.
|
6
|
+
|
7
|
+
`WebPipe::Conn#bag` is a `Hash` with `Symbol` keys where values can
|
8
|
+
be anything you need to share. To help with the process we have following methods:
|
9
|
+
|
10
|
+
- `#add(key, value)`: Assigns a value to a key.
|
11
|
+
- `#fetch(key)`, `#fetch(key, default)`: Retrieves value associated
|
12
|
+
to given key. If it is not found, `default` is returned when
|
13
|
+
provided.
|
14
|
+
|
15
|
+
This is a simple example of a web application which reads a `name`
|
16
|
+
parameter and normalizes it before using in the response body.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
# config.ru
|
20
|
+
require 'web_pipe'
|
21
|
+
|
22
|
+
WebPipe.load_extensions(:params)
|
23
|
+
|
24
|
+
class NormalizeNameApp
|
25
|
+
include WebPipe
|
26
|
+
|
27
|
+
plug :normalize_name
|
28
|
+
plug :respond
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def normalize_name(conn)
|
33
|
+
conn.add(
|
34
|
+
:name, conn.params[:name].downcase.capitalize
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def respond(conn)
|
39
|
+
conn.set_response_body(
|
40
|
+
conn.fetch(:name)
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
run NormalizeNameApp.new
|
46
|
+
```
|