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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98202bd82c61277b38d1300b503c561372f617ef49ff5f61a7ec6582830d1d25
4
- data.tar.gz: ddb88dacbe4bd2c60a3ba337b08ded9d6b9151e01300c2226ef428bd5ab83aa1
3
+ metadata.gz: 7fb150ff5c8b988370259759086c1775d851c5d7b302c223bbd1a1ecf03eaf62
4
+ data.tar.gz: 2d298170408b3ff3893353ba62becbfa88212168cd7f3e212aad4011d2c39e2c
5
5
  SHA512:
6
- metadata.gz: 2185f7fad25bfaed06bcfc199d4986b71739c97ec3ce54d7336299c5eff49b6d5f8b050a6096ca7a761505c4c4d2a25bb5aeb04d9e320cc7df36fc8d974aad32
7
- data.tar.gz: 892055b99db3a063ab79128fe4ef5e89de7d93055d8f006d5ad5197fde8c6aad48fff87db65078b93bc4a113e6aa9357683fda92dcae9a2776607859fc9998ee
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.0)
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
- ```ruby
7
- bundle add 'rack-component'
8
- ```
6
+ ## Install
9
7
 
10
- ## Get Started
8
+ Add `rack-component` to your Gemfile and run `bundle install`:
9
+
10
+ ```
11
+ gem 'rack-component'
12
+ ```
11
13
 
12
- The simplest component is just a function:
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
- Convert your function to a `Rack::Component` when it needs instance methods or state:
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
- Replace `#call` with `#memoized` to make re-renders instant:
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
- You can nest Rack::Components as if they were [React Children][JSX Children] by
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
- PostContent.call(title: post.title, body: post.body)
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
- [JSX Lists][JSX Lists] use JavaScript's `map` function. Rack::Component does
135
- likewise.
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, with JSX, you need to call `join` on your array
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
- ### Mount a Rack::Component tree inside a Rails app
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
- # config/routes.rb
171
- mount MyComponent, at: '/a_path_of_your_choosing'
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
- # config/initializers/my_component.rb
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
- class MyComponent < Rack::Component
282
+
283
+ class Posts < Rack::Component
176
284
  render do |env|
177
- <<~HTML
178
- <h1>Hello from inside a Rails app!</h1>
179
- HTML
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 Rack app out of Rack::Components
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 *slower* than using `Component#call`
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 *outside* a Rack app, because it's not actually dependent on Rack. I
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][Ruby Blocks]
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
- [JSX]: https://reactjs.org/docs/introducing-jsx.html
272
- [JSX Children]: https://reactjs.org/docs/composition-vs-inheritance.html
273
- [JSX Lists]: https://reactjs.org/docs/lists-and-keys.html
274
- [Heredocs]: https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Here+Documents
275
- [String Interpolation]: http://ruby-for-beginners.rubymonstas.org/bonus/string_interpolation.html
276
- [Ruby Blocks]: https://mixandgo.com/learn/mastering-ruby-blocks-in-less-than-5-minutes
277
- [Roda]: http://roda.jeremyevans.net
278
- [Sinatra]: http://sinatrarb.com
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
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class Component
3
- VERSION = '0.4.0'.freeze
3
+ VERSION = '0.4.1'.freeze
4
4
  end
5
5
  end
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.0
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: 2018-12-28 00:00:00.000000000 Z
11
+ date: 2019-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips