rack-component 0.4.0 → 0.4.1
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/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
|