expressr 0.0.2 → 0.0.3

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.
data/README.md CHANGED
@@ -0,0 +1,725 @@
1
+ Expressr
2
+ =======
3
+ Express.js for Ruby
4
+
5
+ Overview
6
+ --------
7
+
8
+ Expressr brings the architecture of Express.js to Ruby. It's a minimal and flexible web application framework that couples the concepts of Express.js and Node.js with the beauty of Ruby.
9
+
10
+ Expressr runs on top of [Noder](https://github.com/tombenner/noder) (Node.js for Ruby).
11
+
12
+ Quick Start
13
+ -----------
14
+
15
+ A web app can be created and started using the following script:
16
+
17
+ ```ruby
18
+ require 'expressr'
19
+
20
+ app = Expressr::App.new
21
+
22
+ app.get('/hello.txt') do |request, response|
23
+ response.out('Hello World')
24
+ end
25
+
26
+ app.listen(3000)
27
+ ```
28
+
29
+ To start the app, put the code into a file named `my_app.rb` and run it:
30
+
31
+ ```bash
32
+ $ ruby my_app.rb
33
+ Running Noder at 0.0.0.0:3000...
34
+ ```
35
+
36
+ Examples
37
+ --------
38
+
39
+ Here are some other examples of common usage:
40
+
41
+ ```ruby
42
+ require 'expressr'
43
+
44
+ app = Expressr::App.new
45
+
46
+ # Log every request to URLs beginning with /admin/
47
+ app.all('/admin/*') do |request, response|
48
+ Noder.logger.info "#{request.locals.user} accessed #{request.url}"
49
+ end
50
+
51
+ # Render JSON
52
+ app.get('/some_json') do |request, response|
53
+ response.out({ some: 'json' })
54
+ end
55
+
56
+ # Render a view
57
+ app.get('/users/:id') do (request, response)
58
+ Noder.with ->{ User.find(request.params.id) } do |user|
59
+ response.render('users/show', user: user.attributes)
60
+ end
61
+ end
62
+
63
+ # Respond to a POST request by creating a record and then redirecting
64
+ app.post('/comment') do |request, response|
65
+ Noder.with ->{ Comment.create(request.params.comment) } do |comment|
66
+ response.redirect("/comment/#{comment.id}")
67
+ endd
68
+ end
69
+
70
+ app.listen(3000)
71
+ ```
72
+
73
+ See the API section below for complete documentation.
74
+
75
+ Please note that the datastore-related lines in the examples above will block the event loop as written. You'll want to use [EM-Synchrony](https://github.com/igrigorik/em-synchrony)'s support for whichever datastore you're using and for other IO operations.
76
+
77
+ API
78
+ ---
79
+
80
+ ### Expressr::App
81
+
82
+ `Expressr::App` lets you create and run web apps.
83
+
84
+ #### Settings
85
+
86
+ An app has settings which configure it:
87
+
88
+ * `'jsonp callback name'` - The param used for determining the JSONP callback's name. The default is `'callback'` (e.g. for `?callback=myFunction`).
89
+ * `'locals'` - A hash of name-value pairs that will be passed to views as local variables.
90
+ * `'root'` - The root directory of the app. This is set automatically.
91
+ * `'view engine'` - The template engine used for views. The default is `'slim'`, and `'haml'` is also supported.
92
+ * `'views'` - The path to the views within the app's root directory. The default value is `'views'`.
93
+
94
+ Settings can be set using `#set` and retrieved using `#get`:
95
+
96
+ ```ruby
97
+ app.set('view engine', 'haml')
98
+ app.get('view engine') # "haml"
99
+ ```
100
+
101
+ A hash of all settings can be accessed by using `app.settings`.
102
+
103
+ #### .new(server_options={})
104
+
105
+ Creates the app.
106
+
107
+ ##### server_options
108
+
109
+ Please see [Noder's docs](https://github.com/tombenner/noder) for the options that can be passed to `Noder::HTTP::Server`. These include options like the server's address, port, whether HTTPS is enabled, etc.
110
+
111
+ #### #set(name, value)
112
+
113
+ Sets the value of a setting.
114
+
115
+ #### #get(name)
116
+
117
+ Gets the value of a setting.
118
+
119
+ #### #enable(name)
120
+
121
+ Sets the value of a setting to `true`.
122
+
123
+ #### #disable(name)
124
+
125
+ Sets the value of a setting to `false`.
126
+
127
+ #### #enabled?(name)
128
+
129
+ Returns a boolean of whether the setting is enabled or not.
130
+
131
+ #### #disabled?(name)
132
+
133
+ Returns a boolean of whether the setting is disabled or not.
134
+
135
+ #### #engine(value)
136
+
137
+ Sets the view engine. This is the equivalent of `set('view engine', value)`. Valid values are `'slim'` and `'haml'`.
138
+
139
+ #### #param(name, &block)
140
+
141
+ Registers a listener for any request that includes the specified param. For example, in a request is made to `/user/5` (with a `/user/:user_id` route) or `/profile?user_id=5`, the following will the log the `user_id` value:
142
+
143
+ ```ruby
144
+ app.param('user_id') do |request, response, continue, user_id|
145
+ user = User.find(user_id)
146
+ if user
147
+ request.locals.user = user
148
+ else
149
+ Noder.logger.info "User not found: #{user_id}"
150
+ end
151
+ end
152
+ ```
153
+
154
+ #### #VERB(path, &block)
155
+
156
+ Registers a listener for any request that matches the VERB (e.g. `get`, `post`, `put`, `delete`) and the path.
157
+
158
+ Respond with `Welcome!` for GET requests to `/welcome`:
159
+
160
+ ```ruby
161
+ app.get('/welcome') do |request, response|
162
+ response.out('Welcome!')
163
+ end
164
+ ```
165
+
166
+ Respond with JSON for POST requests to `/user/5/settings`:
167
+
168
+ ```ruby
169
+ app.post('/user/:id/settings') do |request, response|
170
+ response.out({
171
+ user_id: request.params.id,
172
+ params: request.params
173
+ })
174
+ end
175
+ ```
176
+
177
+ Regular expressions can also be used:
178
+
179
+ ```ruby
180
+ app.get(/^\/commits\/(\w+)\.\.(\w+)/) do |request, response|
181
+ response.out({
182
+ from: request.params[0],
183
+ to: request.params[1]
184
+ })
185
+ end
186
+ ```
187
+
188
+ #### #all(path, &block)
189
+
190
+ This method functions just like the `#VERB(path, &block)` method, but it matches all HTTP verbs.
191
+
192
+ It's very useful for creating global logic for all requests or for requests to specific paths:
193
+
194
+ ```ruby
195
+ app.all('/admin/*') do |request, response|
196
+ if !is_admin?
197
+ response.status = 403
198
+ response.out('Not authorized.')
199
+ end
200
+ end
201
+ ```
202
+
203
+ #### #route(path)
204
+
205
+ Returns an instance of a single route which can then be used to handle HTTP verbs with optional middleware. Using `#route(path)` is a recommended approach to avoiding duplicate route naming and thus typo errors.
206
+
207
+ ```ruby
208
+ app.route('/users').
209
+ all do |request, response|
210
+ Noder.logger.info "Users request performed"
211
+ end.
212
+ get do |request, response|
213
+ response.out(User.find(request.params.user_id))
214
+ end.
215
+ post do |request, response|
216
+ user = User.create(request.params.user)
217
+ response.out(user.attributes)
218
+ end
219
+ ```
220
+
221
+ #### #locals
222
+
223
+ Application local variables are provided to all templates rendered within the application. This is useful for providing helper functions to templates, as well as app-level data.
224
+
225
+ ```ruby
226
+ app.locals.site_name = 'My Site'
227
+ app.locals.contact_email = 'contact@mysite.com'
228
+ ```
229
+
230
+ #### #listen(port=nil, address=nil, &block)
231
+
232
+ Bind and listen for connections on the given host and port. This method is identical to Noder's `Noder::HTTP::Server#listen`.
233
+
234
+ ```ruby
235
+ app = Expressr::App.new
236
+ app.get('/hello.txt') do |request, response|
237
+ response.out('Hello World')
238
+ end
239
+ app.listen(3000)
240
+ ```
241
+
242
+ A block which will be called for all requests can be passed to it, too:
243
+
244
+ ```ruby
245
+ app = Expressr::App.new
246
+ app.listen do |request, response|
247
+ response.out('Hello World')
248
+ end
249
+ ```
250
+
251
+ #### #close
252
+
253
+ Stops the app. This is the same as Noder's `Noder::HTTP::Server#close` and is called when an `INT` or `TERM` signal is sent to a running server's process (e.g. when `Control-C` is pressed).
254
+
255
+ #### #settings
256
+
257
+ A hash of the app's settings:
258
+
259
+ ```ruby
260
+ app.set('my setting', 'My value')
261
+ value = app.settings['my setting']
262
+ ```
263
+
264
+ ### Expressr::Request
265
+
266
+ `Expressr::Request` inherits from (and thus also includes methods from) `Noder::HTTP::Request`.
267
+
268
+ #### #params
269
+
270
+ Similar to Rails' `params`, this includes params from the query string, POST data, and route parameters. Params can be accessed in three ways: `params.user_id`, `params[:user_id]`, or `params['user_id']`.
271
+
272
+ For example, a request to `/user/3?comment_id=4` will include two params:
273
+
274
+ ```ruby
275
+ app.get('/user/:user_id') do |request, response|
276
+ response.out({
277
+ user_id: request.params.user_id,
278
+ comment_id: request.params.comment_id
279
+ })
280
+ end
281
+ ```
282
+
283
+ If a regex route is used, the matches can be accessed at their integer indexes:
284
+
285
+ ```ruby
286
+ app.get(/\/user\/(\d+)\/comment\/(\d+)/) do |request, response|
287
+ response.out({
288
+ user_id: request.params[0],
289
+ comment_id: request.params[1]
290
+ })
291
+ end
292
+ ```
293
+
294
+ #### #query
295
+
296
+ Similar to `#params`, but it only includes params from the query string.
297
+
298
+ For example, a request to `/user/3?comment_id=4` will only include `comment_id`:
299
+
300
+ ```ruby
301
+ app.get('/user/:user_id') do |request, response|
302
+ response.out({
303
+ comment_id: request.query.comment_id
304
+ })
305
+ end
306
+ ```
307
+
308
+ #### #param(name)
309
+
310
+ Returns the value of param `name` when present. This is the equivalent of `params.name` or `params[name]`, which are the preferred forms.
311
+
312
+ ```ruby
313
+ request.param('user_id')
314
+ ```
315
+
316
+ #### #get(name)
317
+
318
+ Returns the value of the `name` header when present.
319
+
320
+ ```ruby
321
+ request.get('Content-Type') # "text/plain"
322
+ request.get('Something') # nil
323
+ ```
324
+
325
+ Aliased as `#header(name)`.
326
+
327
+ #### #accepts(types)
328
+
329
+ Check if the given types are acceptable, returning the best match when true, otherwise `nil`.
330
+
331
+ ```ruby
332
+ # Accept: text/*, application/json
333
+ request.accepts('text/html') # "text/html"
334
+ request.accepts('image/png') # nil
335
+ ```
336
+
337
+ #### #is?(type)
338
+
339
+ Check if the given types are acceptable, returning the best match when true, otherwise `nil`.
340
+
341
+ ```ruby
342
+ # Content-Type: text/html; charset=utf-8
343
+ request.is('text/html') # true
344
+ request.is('image/png') # false
345
+ ```
346
+
347
+ #### #ip
348
+
349
+ Returns the remote IP address.
350
+
351
+ ```ruby
352
+ request.ip # "68.1.8.45"
353
+ ```
354
+
355
+ #### #path
356
+
357
+ Returns the path of the requested URL.
358
+
359
+ ```ruby
360
+ # example.com/users?sort=desc
361
+ request.path # "/users"
362
+ ```
363
+
364
+ #### #host
365
+
366
+ Returns the hostname from the "Host" header field (without the port).
367
+
368
+ ```ruby
369
+ # Host: "example.com:3000"
370
+ request.host # "example.com"
371
+ ```
372
+
373
+ #### #xhr?
374
+
375
+ Check whether the request was issued with the "X-Requested-With" header field set to "XMLHttpRequest" (jQuery etc).
376
+
377
+ ```ruby
378
+ # Host: "example.com:3000"
379
+ request.xhr? # false
380
+ ```
381
+
382
+ #### #protocol
383
+
384
+ Returns the protocol string of the request (e.g. `'http'`, `'https'`).
385
+
386
+ ```ruby
387
+ # "http://example.com/"
388
+ request.protocol # 'http'
389
+ ```
390
+
391
+ #### #secure?
392
+
393
+ Checks whether a TLS connection is established. This is the equivalent of `request.protocol == 'https'`.
394
+
395
+ ```ruby
396
+ # "http://example.com/"
397
+ request.secure? # false
398
+ ```
399
+
400
+ #### #subdomains
401
+
402
+ Returns the subdomains as an array
403
+
404
+ ```ruby
405
+ # Host: "tobi.ferrets.example.com"
406
+ request.subdomains # ["ferrets", "tobi"]
407
+ ```
408
+
409
+ #### #original_url
410
+
411
+ This is similar to `#url`, except that it retains the original URL, allowing you to rewrite `#url` freely.
412
+
413
+ ```ruby
414
+ # /search?q=something
415
+ request.original_url # "/search?q=something"
416
+ ```
417
+
418
+ ### Expressr::Response
419
+
420
+ `Expressr::Response` inherits from (and thus also includes methods from) `Noder::HTTP::Response`.
421
+
422
+ #### #set(name, value=nil)
423
+
424
+ Set a header's value, or pass a hash as a single argument to set multiple headers at once.
425
+
426
+ ```ruby
427
+ response.set('Content-Type', 'text/plain')
428
+ response.set({
429
+ 'Content-Type' => 'text/plain',
430
+ 'Content-Length' => '123',
431
+ 'ETag' => '12345'
432
+ })
433
+ ```
434
+
435
+ #### #get(name)
436
+
437
+ Returns a header's value.
438
+
439
+ ```ruby
440
+ response.get('Content-Type') # "text/plain"
441
+ ```
442
+
443
+ #### #cookie(name, value, options={})
444
+
445
+ Sets a cookie. All of the options supported by [CGI::Cookie](http://ruby-doc.org/stdlib-1.9.3/libdoc/cgi/rdoc/CGI/Cookie.html) are supported.
446
+
447
+ ```ruby
448
+ response.cookie('user_id', '15')
449
+ response.cookie('remember_me', '1', {
450
+ 'expires' => Time.now + 14.days,
451
+ 'domain' => 'example.com'
452
+ })
453
+ ```
454
+
455
+ #### #clear_cookie(name, value, options={})
456
+
457
+ Sets a cookie. All of the options supported by [CGI::Cookie](http://ruby-doc.org/stdlib-1.9.3/libdoc/cgi/rdoc/CGI/Cookie.html) are supported.
458
+
459
+ ```ruby
460
+ response.cookie('user_id', '15')
461
+ response.clear_cookie('user_id')
462
+ ```
463
+
464
+ #### #redirect(status_or_url, url=nil)
465
+
466
+ Redirects to the specified URL with an optional status (default is `302`).
467
+
468
+ ```ruby
469
+ response.redirect('/foo/bar')
470
+ response.redirect(303, '/foo/bar')
471
+ response.redirect('https://www.google.com')
472
+ ```
473
+
474
+ #### #location(url)
475
+
476
+ Sets the `Location` header's value.
477
+
478
+ ```ruby
479
+ response.location('/foo/bar')
480
+ response.location('https://www.google.com')
481
+ ```
482
+
483
+ #### #out(status_or_content=nil, content=nil)
484
+
485
+ Sends the response. (This is the equivalent of Express.js's `send` method, which has another use in Ruby.)
486
+
487
+ ```ruby
488
+ response.out({ some: 'json' })
489
+ response.out('some html')
490
+ response.out(404, 'Sorry, we cannot find that!')
491
+ response.out(500, { error: 'something blew up' })
492
+ response.out(200)
493
+ ```
494
+
495
+ When the content is a string, the Content-Type is set to `text/html`.
496
+
497
+ When the content is a hash, the hash is converted to JSON and the Content-Type is set to `application/json`.
498
+
499
+ #### #json(status_or_content=nil, content=nil)
500
+
501
+ Sends a JSON response. This identical to `#out` when an array or object is passed.
502
+
503
+ ```ruby
504
+ response.json({ some: 'json' })
505
+ response.json(500, { error: 'something blew up' })
506
+ ```
507
+
508
+ #### #jsonp(status_or_content=nil, content=nil)
509
+
510
+ Sends a JSONP response. This identical to `#json`, but it provides JSONP support if the request specifies a JSONP callback.
511
+
512
+ ```ruby
513
+ # /?callback=foo
514
+ response.jsonp({ some: 'json' }) # "foo({"some":"json"});"
515
+ # /
516
+ response.jsonp({ some: 'json' }) # "{"some":"json"}"
517
+ ```
518
+
519
+ The JSONP callback name defaults to `'callback'`, but it can be set using the app's settings:
520
+
521
+ ```ruby
522
+ app.settings.set('jsonp callback name', 'cb')
523
+ # /?cb=foo
524
+ response.jsonp({ some: 'json' }) # "foo({"some":"json"});"
525
+ ```
526
+
527
+ #### #type(value)
528
+
529
+ Sets the `Content-Type` header to the value.
530
+
531
+ ```ruby
532
+ response.type('html')
533
+ response.type('application/json')
534
+ ```
535
+
536
+ #### #format(hash)
537
+
538
+ Performs content-negotiation on the request Accept header field when present.
539
+
540
+ ```ruby
541
+ response.format({
542
+ 'text/html' => proc { |request, response|
543
+ response.out("<h3>Some HTML</h3>")
544
+ },
545
+ 'text/plain' => proc { |request, response|
546
+ response.out("Some text")
547
+ },
548
+ 'application/json' => proc { |request, response|
549
+ response.out({ some: json })
550
+ },
551
+ })
552
+ ```
553
+
554
+ Content type synonyms are supported (e.g. `'json'` and `'application/json'` are equivalent):
555
+
556
+ ```ruby
557
+ response.format({
558
+ 'html' => proc { |request, response|
559
+ response.out("<h3>Some HTML</h3>")
560
+ },
561
+ 'text' => proc { |request, response|
562
+ response.out("Some text")
563
+ },
564
+ 'json' => proc { |request, response|
565
+ response.out({ some: json })
566
+ },
567
+ })
568
+ ```
569
+
570
+ #### #attachment(filename=nil)
571
+
572
+ Sets the Content-Disposition header field to "attachment". If a filename is given then the Content-Type will be automatically set based on the extension via `#type`, and the Content-Disposition's "filename=" parameter will be set.
573
+
574
+ ```ruby
575
+ response.attachment
576
+ # Content-Disposition: attachment
577
+
578
+ response.attachment('path/to/logo.png')
579
+ # Content-Disposition: attachment; filename="logo.png"
580
+ # Content-Type: image/png
581
+ ```
582
+
583
+ #### #send_file(path, options={})
584
+
585
+ Transfers the file at the given path.
586
+
587
+ Automatically defaults the Content-Type response header field based on the filename's extension.
588
+
589
+ ##### options
590
+
591
+ * `root` - Root directory for relative filenames
592
+
593
+ ```ruby
594
+ app.get('/user/:uid/photos/:file') do |request, response|
595
+ uid = request.params.uid
596
+ file = request.params.file
597
+
598
+ if request.locals.user.may_view_files_from(uid)
599
+ response.sendfile("/uploads/#{uid}/#{file}")
600
+ else
601
+ response.out(403, "Sorry! You can't see that.")
602
+ end
603
+ end
604
+ ```
605
+
606
+ #### #download(path, filename=nil)
607
+
608
+ Transfer the file at path as an "attachment". Typically browsers will prompt the user for download. The Content-Disposition "filename=" parameter (the one that will appear in the brower dialog is set to path by default), but you can also provide an override filename.
609
+
610
+ ```ruby
611
+ response.download('/report-12345.pdf')
612
+ response.download('/report-12345.pdf', 'report.pdf')
613
+ ```
614
+
615
+ #### #links(links)
616
+
617
+ Join the given links to populate the "Link" response header field.
618
+
619
+ ```ruby
620
+ response.links({
621
+ next: 'http://api.example.com/users?page=2',
622
+ last: 'http://api.example.com/users?page=5'
623
+ })
624
+
625
+ # Link: <http://api.example.com/users?page=2>; rel="next",
626
+ # <http://api.example.com/users?page=5>; rel="last"
627
+ ```
628
+
629
+ #### #locals
630
+
631
+ Response local variables are scoped to the request, thus only available to the view(s) rendered during that request / response cycle, if any.
632
+
633
+ This object is useful for exposing request-level information such as the request pathname, authenticated user, user settings, etc.
634
+
635
+ ```ruby
636
+ app.use do (request, response)
637
+ response.locals.user = User.find(request.params.user_id)
638
+ response.locals.authenticated = !response.locals.user.is_anonymous?
639
+ end
640
+ ```
641
+
642
+ #### #render(view, locals=nil, &block)
643
+
644
+ Renders a `view`. The view's local variables are supplied by both the `locals` argument and the app's `locals` setting. If the rendering raises an exception, the `&block` is called with the exception as an argument.
645
+
646
+ ```ruby
647
+ app.get('/users/:id') do (request, response)
648
+ user = User.find(request.params.id)
649
+ response.render('profile', user: user.attributes)
650
+ end
651
+
652
+ app.get('/contact') do (request, response)
653
+ response.render('contact') do |exception|
654
+ response.render('error')
655
+ end
656
+ end
657
+ ```
658
+
659
+ To set the locals that will be passed to all views, use:
660
+
661
+ ```ruby
662
+ app.locals.site_name = 'My Site'
663
+ app.locals.contact_email = 'contact@mysite.com'
664
+ ```
665
+
666
+ By default, Expressr looks for views in the `views` directory (e.g. `views/profile.slim`).
667
+
668
+ To set the directory of the views, use:
669
+
670
+ ```ruby
671
+ app.set('views', File.expand_path('../my_views', __FILE__))
672
+ ```
673
+
674
+ Expressr uses the Slim template engine by default, but it also supports Haml:
675
+
676
+ ```ruby
677
+ app.set('view engine', 'haml')
678
+ ```
679
+
680
+ If you'd like to add support for other template engines, doing so is fairly straightforward; just grep for `'slim'` in the codebase, and the steps needed to support a new engine should be clear.
681
+
682
+ ### Expressr::Router
683
+
684
+ `Expressr::App` lets you create routes for your app. An app's router can be accessed at `app.router`.
685
+
686
+ ```ruby
687
+ app = Expressr::App.new
688
+ app.router.get('/hello.txt') do |request, response|
689
+ response.out('Hello World')
690
+ end
691
+ ```
692
+
693
+ #### #use(path=nil, &block)
694
+
695
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
696
+
697
+ #### #param(name, &block)
698
+
699
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
700
+
701
+ #### #use(path=nil, &block)
702
+
703
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
704
+
705
+ #### #VERB(path, &block)
706
+
707
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
708
+
709
+ #### #use(path=nil, &block)
710
+
711
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
712
+
713
+ #### #use(path=nil, &block)
714
+
715
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
716
+
717
+ #### #use(path=nil, &block)
718
+
719
+ Please see the documentation for `Expressr::App#use`, which has the same behavior.
720
+
721
+
722
+ License
723
+ -------
724
+
725
+ Expressr is released under the MIT License. Please see the MIT-LICENSE file for details.