rack-component 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -10
- data/README.md +161 -38
- data/lib/rack/component/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fb150ff5c8b988370259759086c1775d851c5d7b302c223bbd1a1ecf03eaf62
|
4
|
+
data.tar.gz: 2d298170408b3ff3893353ba62becbfa88212168cd7f3e212aad4011d2c39e2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: debdc05c3a309c2ed25334124f3bcfc726c4d92e15fe517d7946f48944ecd78239fb32bb4305f64ee7632bbc7235d6b065f08aefb88bd2b050a01bd9998cc03f
|
7
|
+
data.tar.gz: f365e4454d2189123524f86cf3f845f06f974beb6d0f4993aaf921523464469b2a1041d10d0db6889fcd493c0a3740a1d9755361d996d886eb79c39f9b85e05b
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rack-component (0.4.
|
4
|
+
rack-component (0.4.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -25,7 +25,6 @@ GEM
|
|
25
25
|
jaro_winkler (1.5.1)
|
26
26
|
kwalify (0.7.2)
|
27
27
|
method_source (0.9.0)
|
28
|
-
mustermann (1.0.3)
|
29
28
|
parallel (1.12.1)
|
30
29
|
parser (2.5.1.2)
|
31
30
|
ast (~> 2.4.0)
|
@@ -34,8 +33,6 @@ GEM
|
|
34
33
|
coderay (~> 1.1.0)
|
35
34
|
method_source (~> 0.9.0)
|
36
35
|
rack (2.0.6)
|
37
|
-
rack-protection (2.0.4)
|
38
|
-
rack
|
39
36
|
rack-test (0.8.3)
|
40
37
|
rack (>= 1.0, < 3)
|
41
38
|
rainbow (3.0.0)
|
@@ -67,11 +64,6 @@ GEM
|
|
67
64
|
ruby-progressbar (~> 1.7)
|
68
65
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
69
66
|
ruby-progressbar (1.10.0)
|
70
|
-
sinatra (2.0.4)
|
71
|
-
mustermann (~> 1.0)
|
72
|
-
rack (~> 2.0)
|
73
|
-
rack-protection (= 2.0.4)
|
74
|
-
tilt (~> 2.0)
|
75
67
|
thread_safe (0.3.6)
|
76
68
|
tilt (2.0.8)
|
77
69
|
unicode-display_width (1.4.0)
|
@@ -96,7 +88,6 @@ DEPENDENCIES
|
|
96
88
|
reek (~> 5)
|
97
89
|
rspec (~> 3.0)
|
98
90
|
rubocop (~> 0.59)
|
99
|
-
sinatra (~> 2)
|
100
91
|
tilt (~> 2)
|
101
92
|
yard (~> 0.9)
|
102
93
|
|
data/README.md
CHANGED
@@ -3,13 +3,42 @@
|
|
3
3
|
Like a React.js component, a `Rack::Component` implements a `render` method that
|
4
4
|
takes input data and returns what to display.
|
5
5
|
|
6
|
-
|
7
|
-
bundle add 'rack-component'
|
8
|
-
```
|
6
|
+
## Install
|
9
7
|
|
10
|
-
|
8
|
+
Add `rack-component` to your Gemfile and run `bundle install`:
|
9
|
+
|
10
|
+
```
|
11
|
+
gem 'rack-component'
|
12
|
+
```
|
11
13
|
|
12
|
-
|
14
|
+
## Table of Contents
|
15
|
+
|
16
|
+
* [Getting Started](#getting-started)
|
17
|
+
* [Components as plain functions](#components-as-plain-functions)
|
18
|
+
* [Components as Rack::Components](#components-as-rackcomponents)
|
19
|
+
* [Components that re-render instantly](#components-that-re-render-instantly)
|
20
|
+
* [Recipes](#recipes)
|
21
|
+
* [Render one component inside another](#render-one-component-inside-another)
|
22
|
+
* [Memoize an expensive component for one minute](#memoize-an-expensive-component-for-one-minute)
|
23
|
+
* [Memoize an expensive component until its content changes](#memoize-an-expensive-component-until-its-content-changes)
|
24
|
+
* [Render an HTML list from an array](#render-an-html-list-from-an-array)
|
25
|
+
* [Render a Rack::Component from a Rails controller](#render-a-rackcomponent-from-a-rails-controller)
|
26
|
+
* [Mount a Rack::Component as a Rack app](#mount-a-rackcomponent-as-a-rack-app)
|
27
|
+
* [Build an entire App out of Rack::Components](#build-an-entire-app-out-of-rackcomponents)
|
28
|
+
* [API Reference](#api-reference)
|
29
|
+
* [Performance](#performance)
|
30
|
+
* [Compatibility](#compatibility)
|
31
|
+
* [Anybody using this in production?](#anybody-using-this-in-production)
|
32
|
+
* [Ruby reference](#ruby-reference)
|
33
|
+
* [Development](#development)
|
34
|
+
* [Contributing](#contributing)
|
35
|
+
* [License](#license)
|
36
|
+
|
37
|
+
## Getting Started
|
38
|
+
|
39
|
+
### Components as plain functions
|
40
|
+
|
41
|
+
The simplest component is just a lambda that takes an `env` parameter:
|
13
42
|
|
14
43
|
```ruby
|
15
44
|
Greeter = lambda do |env|
|
@@ -19,11 +48,13 @@ end
|
|
19
48
|
Greeter.call(name: 'Mina') #=> '<h1>Hi, Mina.</h1>'
|
20
49
|
```
|
21
50
|
|
22
|
-
|
51
|
+
### Components as Rack::Components
|
52
|
+
|
53
|
+
Convert your lambda to a `Rack::Component` when it needs instance methods or
|
54
|
+
state:
|
23
55
|
|
24
56
|
```ruby
|
25
57
|
require 'rack/component'
|
26
|
-
|
27
58
|
class FormalGreeter < Rack::Component
|
28
59
|
render do |env|
|
29
60
|
"<h1>Hi, #{title} #{env[:name]}.</h1>"
|
@@ -39,7 +70,9 @@ FormalGreeter.call(name: 'Macron') #=> "<h1>Hi, President Macron.</h1>"
|
|
39
70
|
FormalGreeter.call(name: 'Merkel', title: 'Chancellor') #=> "<h1>Hi, Chancellor Merkel.</h1>"
|
40
71
|
```
|
41
72
|
|
42
|
-
|
73
|
+
### Components that re-render instantly
|
74
|
+
|
75
|
+
Replace `#call` with `#memoized` to make re-renders with the same `env` instant:
|
43
76
|
|
44
77
|
```ruby
|
45
78
|
require 'rack/component'
|
@@ -73,7 +106,8 @@ NetworkGreeter.memoized(name: 'Macron') #=> instant! "Hi, President Macron."
|
|
73
106
|
## Recipes
|
74
107
|
|
75
108
|
### Render one component inside another
|
76
|
-
|
109
|
+
|
110
|
+
You can nest Rack::Components as if they were [React Children][jsx children] by
|
77
111
|
calling them with a block.
|
78
112
|
|
79
113
|
```ruby
|
@@ -94,9 +128,16 @@ end
|
|
94
128
|
class PostPage < Rack::Component
|
95
129
|
render do |env|
|
96
130
|
post = Post.find(id: env[:id])
|
97
|
-
# Nest a PostContent instance inside a Layout instance
|
131
|
+
# Nest a PostContent instance inside a Layout instance, with some arbitrary HTML too
|
98
132
|
Layout.call(title: post.title) do
|
99
|
-
|
133
|
+
<<~HTML
|
134
|
+
<main>
|
135
|
+
#{PostContent.call(title: post.title, body: post.body)}
|
136
|
+
<footer>
|
137
|
+
I am a footer.
|
138
|
+
</footer>
|
139
|
+
</main>
|
140
|
+
HTML
|
100
141
|
end
|
101
142
|
end
|
102
143
|
end
|
@@ -130,9 +171,55 @@ class Layout < Rack::Component
|
|
130
171
|
end
|
131
172
|
```
|
132
173
|
|
174
|
+
### Memoize an expensive component for one minute
|
175
|
+
|
176
|
+
You can use `memoized` as a time-based cache by passing a timestamp to `env`:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
require 'rack/component'
|
180
|
+
|
181
|
+
# Render one million posts as JSON
|
182
|
+
class MillionPosts < Rack::Component
|
183
|
+
render { |env| Post.limit(1_000_000).to_json }
|
184
|
+
end
|
185
|
+
|
186
|
+
MillionPosts.memoized(Time.now.to_i / 60) #=> first call is slow
|
187
|
+
MillionPosts.memoized(Time.now.to_i / 60) #=> next calls in same minute are quick
|
188
|
+
```
|
189
|
+
|
190
|
+
### Memoize an expensive component until its content changes
|
191
|
+
|
192
|
+
This recipe will speed things up when your database calls are fast but your
|
193
|
+
render method is slow:
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
require 'rack/component'
|
197
|
+
class PostAnalysis < Rack::Component
|
198
|
+
render do |env|
|
199
|
+
<<~HTML
|
200
|
+
<h1>#{env[:post].title}</h1>
|
201
|
+
<article>#{env[:post].content}</article>
|
202
|
+
<aside>#{expensive_natural_language_analysis}</aside>
|
203
|
+
HTML
|
204
|
+
end
|
205
|
+
|
206
|
+
def expensive_natural_language_analysis
|
207
|
+
FancyNaturalLanguageLibrary.analyze(env[:post].content)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
PostAnalysis.memoized(post: Post.find(1)) #=> slow, because it runs an expensive natural language analysis
|
212
|
+
PostAnalysis.memoized(post: Post.find(1)) #=> instant, because the content of :post has not changed
|
213
|
+
```
|
214
|
+
|
215
|
+
This recipe works with any Ruby object that implements a `#hash` method based
|
216
|
+
on the object's content, including instances of `ActiveRecord::Base` and
|
217
|
+
`Sequel::Model`.
|
218
|
+
|
133
219
|
### Render an HTML list from an array
|
134
|
-
|
135
|
-
|
220
|
+
|
221
|
+
[JSX Lists][jsx lists] use JavaScript's `map` function. Rack::Component does
|
222
|
+
likewise, only you need to call `join` on the array:
|
136
223
|
|
137
224
|
```ruby
|
138
225
|
require 'rack/component'
|
@@ -150,12 +237,12 @@ class PostsList < Rack::Component
|
|
150
237
|
env[:posts].map { |post|
|
151
238
|
<<~HTML
|
152
239
|
<li class="item">
|
153
|
-
<a href="#{post[:url]}>
|
240
|
+
<a href="#{post[:url]}">
|
154
241
|
#{post[:name]}
|
155
242
|
</a>
|
156
243
|
</li>
|
157
244
|
HTML
|
158
|
-
}.join #unlike
|
245
|
+
}.join #unlike JSX, you need to call `join` on your array
|
159
246
|
end
|
160
247
|
end
|
161
248
|
|
@@ -163,32 +250,66 @@ posts = [{ name: 'First Post', id: 1 }, { name: 'Second', id: 2 }]
|
|
163
250
|
PostsList.call(posts: posts) #=> <h1>This is a list of posts</h1> <ul>...etc
|
164
251
|
```
|
165
252
|
|
166
|
-
###
|
167
|
-
For when just a few parts of your app are built with components:
|
253
|
+
### Render a Rack::Component from a Rails controller
|
168
254
|
|
169
255
|
```ruby
|
170
|
-
#
|
171
|
-
|
256
|
+
# app/controllers/posts_controller.rb
|
257
|
+
class PostsController < ApplicationController
|
258
|
+
def index
|
259
|
+
render json: PostsList.call(params)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# app/components/posts_list.rb
|
264
|
+
class PostsList < Rack::Component
|
265
|
+
render { |env| posts.to_json }
|
266
|
+
|
267
|
+
def posts
|
268
|
+
Post.magically_filter_via_params(env)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
```
|
272
|
+
|
273
|
+
### Mount a Rack::Component as a Rack app
|
172
274
|
|
173
|
-
|
275
|
+
Because Rack::Components follow the same API as a Rack app, you can mount them
|
276
|
+
anywhere you can mount a Rack app. It's up to you to return a valid rack
|
277
|
+
tuple, though.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
# config.ru
|
174
281
|
require 'rack/component'
|
175
|
-
|
282
|
+
|
283
|
+
class Posts < Rack::Component
|
176
284
|
render do |env|
|
177
|
-
|
178
|
-
|
179
|
-
|
285
|
+
[status, headers, [body]]
|
286
|
+
end
|
287
|
+
|
288
|
+
def status
|
289
|
+
200
|
290
|
+
end
|
291
|
+
|
292
|
+
def headers
|
293
|
+
{ 'Content-Type' => 'application/json' }
|
294
|
+
end
|
295
|
+
|
296
|
+
def body
|
297
|
+
Post.all.to_json
|
180
298
|
end
|
181
299
|
end
|
300
|
+
|
301
|
+
run Posts
|
182
302
|
```
|
183
303
|
|
184
|
-
### Build an entire
|
304
|
+
### Build an entire App out of Rack::Components
|
305
|
+
|
185
306
|
In real life, maybe don't do this. Use [Roda] or [Sinatra] for routing, and use
|
186
307
|
Rack::Component instead of Controllers, Views, and templates. But to see an
|
187
308
|
entire app built only out of Rack::Components, see
|
188
309
|
[the example spec](https://github.com/chrisfrank/rack-component/blob/master/spec/raw_rack_example_spec.rb).
|
189
310
|
|
190
|
-
|
191
311
|
## API Reference
|
312
|
+
|
192
313
|
The full API reference is available here:
|
193
314
|
|
194
315
|
https://www.rubydoc.info/gems/rack-component
|
@@ -197,6 +318,7 @@ For info on how to clear or change the size of the memoziation cache, please see
|
|
197
318
|
[the spec][spec].
|
198
319
|
|
199
320
|
## Performance
|
321
|
+
|
200
322
|
On my machine, Rendering a Rack::Component is almost 10x faster than rendering a
|
201
323
|
comparable Tilt template, and almost 100x faster than ERB from the Ruby standard
|
202
324
|
library. Run `ruby spec/benchmarks.rb` to see what to expect in your env.
|
@@ -217,7 +339,7 @@ Calculating -------------------------------------
|
|
217
339
|
Component [memoized] 1.276M (± 0.9%) i/s - 6.432M in 5.041348s
|
218
340
|
```
|
219
341
|
|
220
|
-
Notice that using `Component#memoized` is
|
342
|
+
Notice that using `Component#memoized` is _slower_ than using `Component#call`
|
221
343
|
in this benchmark. Because these components do almost nothing, it's more work to
|
222
344
|
check the memoziation cache than to just render. For components that don't
|
223
345
|
access a database, don't do network I/O, and aren't very CPU-intensive, it's
|
@@ -225,8 +347,9 @@ probably fastest not to memoize. For components that do I/O, using `#memoize`
|
|
225
347
|
can speed things up by several orders of magnitude.
|
226
348
|
|
227
349
|
## Compatibility
|
350
|
+
|
228
351
|
Rack::Component has zero dependencies, and will work in any Rack app. It should
|
229
|
-
even work
|
352
|
+
even work _outside_ a Rack app, because it's not actually dependent on Rack. I
|
230
353
|
packaged it under the Rack namespace because it follows the Rack `call`
|
231
354
|
specification, and because that's where I use and test it.
|
232
355
|
|
@@ -237,14 +360,14 @@ Aye:
|
|
237
360
|
- [future.com](https://www.future.com/)
|
238
361
|
- [Seattle & King County Homelessness Response System](https://hrs.kc.future.com/)
|
239
362
|
|
240
|
-
## Ruby reference
|
363
|
+
## Ruby reference
|
241
364
|
|
242
365
|
Where React uses [JSX] to make components more ergonomic, Rack::Component leans
|
243
366
|
heavily on some features built into the Ruby language, specifically:
|
244
367
|
|
245
368
|
- [Heredocs]
|
246
369
|
- [String Interpolation]
|
247
|
-
- [Calling methods with a block][
|
370
|
+
- [Calling methods with a block][ruby blocks]
|
248
371
|
|
249
372
|
## Development
|
250
373
|
|
@@ -268,11 +391,11 @@ https://github.com/chrisfrank/rack-component.
|
|
268
391
|
MIT
|
269
392
|
|
270
393
|
[spec]: https://github.com/chrisfrank/rack-component/blob/master/spec/rack/component_spec.rb
|
271
|
-
[
|
272
|
-
[
|
273
|
-
[
|
274
|
-
[
|
275
|
-
[
|
276
|
-
[
|
277
|
-
[
|
278
|
-
[
|
394
|
+
[jsx]: https://reactjs.org/docs/introducing-jsx.html
|
395
|
+
[jsx children]: https://reactjs.org/docs/composition-vs-inheritance.html
|
396
|
+
[jsx lists]: https://reactjs.org/docs/lists-and-keys.html
|
397
|
+
[heredocs]: https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Here+Documents
|
398
|
+
[string interpolation]: http://ruby-for-beginners.rubymonstas.org/bonus/string_interpolation.html
|
399
|
+
[ruby blocks]: https://mixandgo.com/learn/mastering-ruby-blocks-in-less-than-5-minutes
|
400
|
+
[roda]: http://roda.jeremyevans.net
|
401
|
+
[sinatra]: http://sinatrarb.com
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Frank
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: benchmark-ips
|