eldr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +28 -0
  4. data/.rubocop_todo.yml +15 -0
  5. data/.travis.yml +3 -0
  6. data/DUCK.md +15 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +89 -0
  9. data/LICENSE +20 -0
  10. data/README.md +876 -0
  11. data/Rakefile +9 -0
  12. data/TODOS +3 -0
  13. data/eldr.gemspec +36 -0
  14. data/examples/README.md +7 -0
  15. data/examples/action_objects.ru +28 -0
  16. data/examples/app.ru +75 -0
  17. data/examples/builder.ru +24 -0
  18. data/examples/custom_response.ru +23 -0
  19. data/examples/errors.ru +16 -0
  20. data/examples/hello_world.ru +8 -0
  21. data/examples/inheritance.ru +22 -0
  22. data/examples/multiple_apps.ru +30 -0
  23. data/examples/rails_style_routing.ru +14 -0
  24. data/examples/rendering_templates.ru +38 -0
  25. data/examples/views/cats.slim +1 -0
  26. data/lib/eldr/app.rb +146 -0
  27. data/lib/eldr/builder.rb +60 -0
  28. data/lib/eldr/configuration.rb +37 -0
  29. data/lib/eldr/matcher.rb +36 -0
  30. data/lib/eldr/recognizer.rb +35 -0
  31. data/lib/eldr/route.rb +92 -0
  32. data/lib/eldr/version.rb +3 -0
  33. data/lib/eldr.rb +1 -0
  34. data/spec/app_spec.rb +194 -0
  35. data/spec/builder_spec.rb +11 -0
  36. data/spec/configuration_spec.rb +29 -0
  37. data/spec/examples/action_objects_spec.rb +18 -0
  38. data/spec/examples/builder_spec.rb +16 -0
  39. data/spec/examples/custom_response_spec.rb +17 -0
  40. data/spec/examples/errors_spec.rb +18 -0
  41. data/spec/examples/example_app_spec.rb +98 -0
  42. data/spec/examples/hello_world_spec.rb +17 -0
  43. data/spec/examples/inheritance_spec.rb +23 -0
  44. data/spec/examples/multiple_apps_spec.rb +31 -0
  45. data/spec/examples/rails_style_routing_spec.rb +17 -0
  46. data/spec/examples/rendering_templates_spec.rb +26 -0
  47. data/spec/matcher_spec.rb +21 -0
  48. data/spec/readme_definitions.yml +28 -0
  49. data/spec/readme_spec.rb +117 -0
  50. data/spec/recognizer_spec.rb +32 -0
  51. data/spec/route_spec.rb +131 -0
  52. data/spec/spec_helper.rb +14 -0
  53. metadata +252 -0
data/README.md ADDED
@@ -0,0 +1,876 @@
1
+ # Eldr [![Build Status](https://travis-ci.org/eldr-rb/eldr.svg)](https://travis-ci.org/eldr-rb/eldr) [![Code Climate](https://codeclimate.com/github/eldr-rb/eldr/badges/gpa.svg)](https://codeclimate.com/github/eldr-rb/eldr) [![Coverage Status](https://coveralls.io/repos/eldr-rb/eldr/badge.svg?branch=master)](https://coveralls.io/r/eldr-rb/eldr?branch=master) [![Dependency Status](https://gemnasium.com/eldr-rb/eldr.svg)](https://gemnasium.com/eldr-rb/eldr) [![Inline docs](https://inch-ci.org/github/eldr-rb/eldr.svg?branch=master)](http://inch-ci.org/github/eldr-rb/eldr) [![Gratipay](https://img.shields.io/gratipay/k2052.svg)](https://www.gratipay.com/k2052/)
2
+
3
+ Eldr is a minimal ruby framework that doesn't hide the rack. It aims to be lightweight, simple, modular and above all, clear. Eldr is a ruby framework without all the magic.
4
+
5
+ Eldr apps are rack apps like this:
6
+
7
+ ```ruby
8
+ class App < Eldr::App
9
+ get '/posts' do |params|
10
+ Rack::Response.new "posts", 200
11
+ end
12
+ end
13
+ ```
14
+
15
+ Since they are rack apps you can boot them like this:
16
+
17
+ ```ruby
18
+ run App
19
+ ```
20
+
21
+ And when you want to combine them you can do this:
22
+
23
+ ```ruby
24
+ class Posts < Eldr::App
25
+ get '/' do |params|
26
+ Rack::Response.new "posts", 200
27
+ end
28
+ end
29
+
30
+ class Tasks < Eldr::App
31
+ get '/' do |params|
32
+ Rack::Response.new "tasks", 200
33
+ end
34
+ end
35
+
36
+ map '/posts' do
37
+ Posts.new
38
+ end
39
+
40
+ map '/tasks' do
41
+ run Tasks.new
42
+ end
43
+ ```
44
+
45
+ <!-- START doctoc generated TOC please keep comment here to allow auto update -->
46
+ <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
47
+ ## Table of Contents
48
+
49
+ - [Eldr](#eldr)
50
+ - [Features](#features)
51
+ - [Installation and Usage](#installation-and-usage)
52
+ - [Quickstart Guides](#quickstart-guides)
53
+ - [Hello World](#hello-world)
54
+ - [Rendering a Template](#rendering-a-template)
55
+ - [Before/After Filters](#beforeafter-filters)
56
+ - [Helpers](#helpers)
57
+ - [Rails Style Routing](#rails-style-routing)
58
+ - [Rails Style Responses](#rails-style-responses)
59
+ - [Errors](#errors)
60
+ - [Error Catching](#error-catching)
61
+ - [Rails Style Requests](#rails-style-requests)
62
+ - [Conditions](#conditions)
63
+ - [Access Control](#access-control)
64
+ - [Inheritance](#inheritance)
65
+ - [Extensions](#extensions)
66
+ - [Extending using Ruby patterns](#extending-using-ruby-patterns)
67
+ - [Extending using Rack](#extending-using-rack)
68
+ - [Using Rack inside Eldr](#using-rack-inside-eldr)
69
+ - [Redirecting Things](#redirecting-things)
70
+ - [Route Handlers: The Power of Action Objects](#route-handlers-the-power-of-action-objects)
71
+ - [Testing Eldr Apps](#testing-eldr-apps)
72
+ - [Performance](#performance)
73
+ - [Help/Resources](#helpresources)
74
+ - [Contributing](#contributing)
75
+ - [License](#license)
76
+
77
+ <!-- END doctoc generated TOC please keep comment here to allow auto update -->
78
+
79
+ ## Features
80
+
81
+ - **Small** Eldr's core is under 500 lines; you can learn it in an afternoon.
82
+ - **Flexible** Build your apps how you want to and with what you want.
83
+ - **Modular** Eldr's views, response helpers, sessions etc; are all independent gems.
84
+ - **No Magic** Eldr encourages you to build apps that you actually understand.
85
+ - **Fast** Eldr is among the fastest ruby microframeworks and it is getting faster.
86
+ - **Powerful Router** Powered by [Mustermann](https://github.com/rkh/mustermann) -- you can use everything from Sinatra to Rails style routing.
87
+ - **Flexible** That router? Is also just a Rack app. You can swap it out just like everything else in Eldr.
88
+ - **Extensible** with standard ruby classes, modules, and patterns. In Eldr there are no mystical helper blocks.
89
+
90
+ ## Installation and Usage
91
+
92
+ To install Eldr add the following to your gemfile:
93
+
94
+ ```ruby
95
+ gem 'eldr'
96
+ # or if you want to use the master branch:
97
+ # gem 'eldr', github: 'eldr-rb/eldr'
98
+ ```
99
+
100
+ Then run bundler:
101
+
102
+ ```sh
103
+ $ bundle
104
+ ```
105
+
106
+ To use it you need to create a rackup file. Add the following to a config.ru file:
107
+
108
+ ```ruby
109
+ class HelloWorld < Edlr::App
110
+ get '/', proc { [200, {'Content-Type' => 'txt'}, ['Hello World!']]}
111
+ end
112
+
113
+ run HelloWorld
114
+ ```
115
+
116
+ Route handlers are anything that respond to call and return a valid Rack response. All the http verbs you expect are available:
117
+
118
+ ```ruby
119
+ get '/', proc { [200, {'Content-Type' => 'txt'}, ['Hello World!']]}
120
+ post '/', proc { [201, {'Content-Type' => 'txt'}, ['Hello World!']]}
121
+ ```
122
+
123
+ etc...
124
+
125
+ For further usage examples checkout the [https://github.com/eldr-rb/eldr/tree/master/examples](examples folder)
126
+
127
+ I have already built and released extensions for many common tasks:
128
+
129
+ - [eldr-sessions](https://github.com/eldr-rb/eldr-sessions): session helpers like `signed_in?` and `current_user?`
130
+ - [eldr-rendering](https://github.com/eldr-rb/eldr-rendering): a `render` helper for templating.
131
+ - [eldr-assets](https://github.com/eldr-rb/eldr-assets): asset tag helpers like `js('jquery', 'app')`, `css('app')` etc
132
+ - [eldr-responders](https://github.com/eldr-rb/eldr-responders): rails-responder like helpers
133
+ - [eldr-action](https://github.com/eldr-rb/eldr-action): Action Objects
134
+ - [eldr-service](https://github.com/eldr-rb/eldr-service): Action Objects for external services.
135
+ - [eldr-cascade](https://github.com/eldr-rb/eldr-cascade): A fork of Rack::Cascade that plays well with Rack::Response and eldr-action.
136
+
137
+ ## Quickstart Guides
138
+
139
+ ### Hello World
140
+
141
+ Start by adding the following to your gemfile:
142
+
143
+ ```ruby
144
+ gem 'edlr'
145
+ ```
146
+
147
+ Then run bundler:
148
+
149
+ ```
150
+ $ bundle install
151
+ ```
152
+
153
+ Now create a new file called app.rb with the following contents:
154
+
155
+ ```ruby
156
+ class App < Eldr::App
157
+ get '/' do |params|
158
+ Rack::Response.new "Hello World!", 200
159
+ end
160
+ end
161
+ ```
162
+
163
+ This defines a new App with a root route that returns "Hello World!". To make use of this app we need to add a rackup file. In the root of your project create a new file called config.ru:
164
+
165
+ ```ruby
166
+ require_relative 'app'
167
+ run App
168
+ ```
169
+
170
+ Now boot it using rackup:
171
+
172
+ ```sh
173
+ $ rackup
174
+ ```
175
+
176
+ When you visit http:///localhost:9292 in your browser you should see "Hello World!"
177
+
178
+ ### Rendering a Template
179
+
180
+ Eldr provides no render helper in it's core but it is easy to define your own.
181
+ One can use Tilt as the templating library (with your engine of choice) and then create some helper methods to handle finding the template and rendering it.
182
+
183
+ Create a module with the following:
184
+
185
+ ```ruby
186
+ module RenderingHelpers
187
+ def render(path, resp_code=200)
188
+ Rack::Response.new Tilt.new(find_template(path)).render(self), resp_code
189
+ end
190
+
191
+ def find_template(path)
192
+ template = File.join('views', path)
193
+ raise NotFoundError unless File.exists? template
194
+ template
195
+ end
196
+ end
197
+ ```
198
+
199
+ This takes a template, finds the template and then passes it off to tilt for handling.
200
+ If the template does not exist it raises a NotFoundError.
201
+
202
+ We can use these helpers by including them in our App:
203
+
204
+ ```ruby
205
+ class App
206
+ include RenderingHelpers
207
+
208
+ get '/cats' do
209
+ render 'cats.slim'
210
+ end
211
+ end
212
+ ```
213
+
214
+ Using it, is as simple as including it!
215
+
216
+ *Checkout*: [eldr-rendering](https://github.com/eldr-rb/eldr-rendering) for some pre-made rendering helpers.
217
+
218
+ ### Before/After Filters
219
+
220
+ Eldr comes with support for before/after filters. You can add a filter like this:
221
+
222
+ ```ruby
223
+ class App < Eldr::App
224
+ before do
225
+ @message = 'Hello World!'
226
+ end
227
+
228
+ after do
229
+ puts 'after'
230
+ end
231
+
232
+ get '/', proc { [200, {}, [@message]] }
233
+ end
234
+ ```
235
+
236
+ Filters can be limited to specific routes by giving your route a name:
237
+
238
+ ```ruby
239
+ class App < Eldr::App
240
+ before(:bob) do
241
+ @message = 'Hello World!'
242
+ end
243
+
244
+ after(:bob) do
245
+ puts 'after'
246
+ end
247
+
248
+ get '/', name: :bob, proc { [200, {}, [@message]] }
249
+ end
250
+ ```
251
+
252
+ ### Helpers
253
+
254
+ In Eldr, helpers are not mystical elements, they are plain ruby modules:
255
+
256
+ ```ruby
257
+ module Helpers
258
+ end
259
+ ```
260
+
261
+ If you need registration callbacks then you can make use of ruby's own included callback:
262
+
263
+ ```ruby
264
+ module Helpers
265
+ def included(klass)
266
+ end
267
+ end
268
+ ```
269
+
270
+ If you need to define these methods directly on the app class, something like _helpers do_, you can do the following:
271
+
272
+ ```ruby
273
+ class App
274
+ def helper_method
275
+ end
276
+ end
277
+ ```
278
+
279
+ This will put your helper method on every instance of your app's class and make it available to any templates.
280
+
281
+ ### Rails Style Routing
282
+
283
+ In the rails world routes are methods on controller instances:
284
+
285
+ ```ruby
286
+ class Cats
287
+ def index
288
+ # stuff here
289
+ end
290
+ end
291
+ ```
292
+
293
+ Then in a separate router object we map our routes to these action methods.
294
+
295
+ ```ruby
296
+ get '/cats', to: 'cats#index'
297
+ ```
298
+
299
+ This is easy to do with Eldr; because Eldr apps are Rack apps, we can use one as a dispatcher/router and then another as our controller.
300
+
301
+ ```ruby
302
+ class Cats
303
+ def index
304
+ Rack::Response.new "Hello From all the Cats!"
305
+ end
306
+ end
307
+ ```
308
+
309
+ Then run it:
310
+
311
+ ```ruby
312
+ run Router.new do
313
+ get '/cats', to: 'cats#index'
314
+ end
315
+ ```
316
+
317
+ We could add a lot more abstraction but this is simple enough and accomplishes our goals. Eldr encourages you to _build up_ abstractions as you go.
318
+
319
+ Pre-building flexibility and accounting for things you don't need to do yet is unnecessary work. You should build apps that you can _see into_ and actually understand, no magic.
320
+
321
+ ### Rails Style Responses
322
+
323
+ The Rails world has a tremendous amount of conventions and abstraction for dealing with responses. They range from complex to simple, from format helpers to full fledged REST abstractions. At their core, they all take information from Rack::Request and turn it into a valid Rack response -- and a valid rack response is just an array.
324
+
325
+ Once we realize this, replicating rails becomes just a matter of patterns.
326
+
327
+ Eldr routes must return a valid rack response, which is an array -- actually its just something that can act like an array. We can return an object that responds to :to_a and Rack will be just as happy.
328
+
329
+ Let's create a object that holds a response string, response headers, and a status.
330
+
331
+ ```ruby
332
+ class Response
333
+ attr_accessor :status, :headers, :body
334
+
335
+ def initialize(body, status=200, headers={})
336
+ @body, @status, @headers = body, status, headers
337
+ end
338
+ end
339
+ ```
340
+
341
+ Now add a to_a method:
342
+
343
+ ```ruby
344
+ def to_a
345
+ [@status, @headers, [@body]]
346
+ end
347
+ alias_method :to_ary, :to_a # this makes ruby aware the object can act like an array
348
+ ```
349
+
350
+ To use this we just return a new instance of it as our response:
351
+
352
+ ```ruby
353
+ Eldr::App.new do
354
+ get '/' do
355
+ Response.new("Hello World!")
356
+ end
357
+ end
358
+ ```
359
+
360
+ This simple pattern is enough to build all sorts of powerful abstractions.
361
+
362
+ ### Errors
363
+
364
+ You can `raise` an error with any class that inherits from StandardError and responds to call. This is useful in before filters, when you want to halt a route from executing.
365
+
366
+ An error class looks like this:
367
+
368
+ ```ruby
369
+ class ErrorResponse < StandardErrro
370
+ def call(env)
371
+ Rack::Response.new message, 500
372
+ end
373
+ end
374
+ ```
375
+
376
+ And you can use it like a standard ruby error class:
377
+
378
+ ```ruby
379
+ app = Edlr::App.new do
380
+ get '/' do
381
+ raise ErrorResponse, "Bad Data"
382
+ end
383
+ end
384
+
385
+ run app
386
+ ```
387
+
388
+ ### Error Catching
389
+
390
+ Eldr will NOT catch all errors. In a production setting you will need to use middleware to make certain nothing ever explodes at your user. Something like [rack-robustness](https://github.com/blambeau/rack-robustness) will work fine:
391
+
392
+ ```ruby
393
+ class App < Edlr::App
394
+ use Rack::Robustness do |g|
395
+ g.on(ArgumentError){|ex| 400 }
396
+ g.on(SecurityError){|ex| 403 }
397
+
398
+ g.content_type 'text/plain'
399
+
400
+ g.body{|ex|
401
+ ex.message
402
+ }
403
+
404
+ g.ensure(true){|ex|
405
+ env['rack.errors'].write(ex.message)
406
+ }
407
+ end
408
+ end
409
+
410
+ run App
411
+ ```
412
+
413
+ ### Rails Style Requests
414
+
415
+ Rails provides all sorts of ways to digest the data passed to it by a client. At their core they all operate on Rack's env object. They take Rack's env object and pass it off to a wrapper.
416
+
417
+ The wrapper modifies the request _only_ on the env object, this means at any point we can duplicate the state of the wrapper by passing it the env; and any objects that need parsed request data (e.g params) will know to find it in the env object.
418
+
419
+ The most typical way rails helps us with requests is through parameter parsing and validation. If we of params a just a hash, then validating, error handling on them etc is simple, we can use all the tools we are already used to.
420
+
421
+ Lets start by getting our parameters into a hash. To do this we pass env into Rack::Request; a wrapper object that parses the query string and injects the results into @env["rack.request.query_hash"]:
422
+
423
+ ```ruby
424
+ class App < Eldr::App
425
+ post '/cats' do |env|
426
+ request = Rack::Request.new(env)
427
+ params = request.GET # we could also access it from env["rack.request.query_hash"]
428
+ end
429
+ end
430
+ ```
431
+
432
+ Now we will need an object to _validate_ the parameters. We can use Virtus and ActiveModel::Validations for this:
433
+
434
+ ```ruby
435
+ class CatParams
436
+ include Virtus.model
437
+ include ActiveModel::Validations
438
+
439
+ attribute :name, String
440
+ attribute :age, Integer
441
+ attribute :human_kils, Integer
442
+
443
+ validates :name, :age, :human_kills, presence: true
444
+ end
445
+ ```
446
+
447
+ To _validate_ a request we need to instantiate CatParams:
448
+
449
+ ```ruby
450
+ class InvalidParams < Edlr::ResponseError
451
+ def status
452
+ 400
453
+ end
454
+ end
455
+
456
+ class App < Eldr::App
457
+ post '/cats' do |env|
458
+ request = Rack::Request.new(env)
459
+ params = CatParams.new(request.GET)
460
+
461
+ raise InvalidParams.new(errors) unless params.valid?
462
+
463
+ cat = Cat.create(params.attributes)
464
+ Rack::Response.new(cat.to_json)
465
+ end
466
+ end
467
+ ```
468
+
469
+ If our parameters are invalid we raise an InvalidParams response (Eldr has error handling). If they are valid, then we create our Cat and return it as JSON.
470
+
471
+ ### Conditions
472
+
473
+ Most microframeworks provide a DSL for route conditions. This needlesly complicates a framework with multiple ways to do things. Do we check for _x_ in our conditions or do we check for it in the route's handler?
474
+
475
+ Eldr's strives for clarity. There is only one place to place logic for a route, in the route's handler.
476
+
477
+ If you want a route to only be executed under certain conditions you check those conditions in the handler and then `throw :pass` if conditions match/do not match:
478
+
479
+ ```ruby
480
+ get '/' do
481
+ throw :pass if params['agent'] == 'secret'
482
+ # respond here
483
+ end
484
+
485
+ # Executed only if we pass from the first route
486
+ get '/' do
487
+ end
488
+ ```
489
+
490
+ ### Access Control
491
+
492
+ Originally Eldr had an access control DSL that looked like this:
493
+
494
+ ```ruby
495
+ access_control do
496
+ allow :create, :registered, :admin
497
+ end
498
+ ```
499
+
500
+ The DSL pulled a route's name and it's roles into a before filter, then checked them against the current_user's roles. The filter itself was five lines.
501
+
502
+ I soon realized that this was redundant abstraction. The DSL didn't save me any coding, it merely gave the code pretty words. I was sacrificing clarity for poetry.
503
+
504
+ To protect a route you need a before filter that checks the user for roles. It can look like this:
505
+
506
+ ```ruby
507
+ before :create, :edit do
508
+ unless current_user.has_role? :admin
509
+ raise NotAuthorized, "You are not authorized to do this!"
510
+ end
511
+ end
512
+ ```
513
+
514
+ It is just as short as any DSL and ambiguity free. Any ruby developer can guess what
515
+ it means without having to know your framework's secret language.
516
+
517
+ ### Inheritance
518
+
519
+ In many frameworks you are encouraged to wrap your controllers in a central app.
520
+ You build a main app that handles routing, scoping, sharing configuration and middleware etc; often controllers are merely blocks executed in the context of this app. This allows you to build DRY controllers but at the cost of a large central app.
521
+
522
+ In Eldr we reverse this model and encourage you to build up your controllers through inheritance. Eldr apps can inherit middleware and configuration from parent apps.
523
+ This allows you to define a base app and allow all your controllers to share the same configuration.
524
+
525
+ For example:
526
+
527
+ ```ruby
528
+ class SimpleMiddleware
529
+ def initialize(app)
530
+ @app = app
531
+ end
532
+
533
+ def call(env)
534
+ env['eldr.simple_counter'] ||= 0
535
+ env['eldr.simple_counter'] += 1
536
+ @app.call(env)
537
+ end
538
+ end
539
+
540
+ class BaseApp < Eldr::App
541
+ use SimpleCounterMiddleware
542
+ set :bob, 'what about him?'
543
+ end
544
+
545
+ class InheritedApp < BaseApp
546
+ get '/', proc { [200, {}, [env['eldr.simple_counter']]]}
547
+ end
548
+
549
+ run InheritedApp
550
+ ```
551
+
552
+ ### Extensions
553
+
554
+ Extending a framework is a mystical process full of esoteric patterns and confusing APIs. Engines, helper blocks, plugin apis, and if you are lucky yaml configuration.
555
+
556
+ You can extend Eldr in two ways;
557
+
558
+ 1. Through standard Ruby patterns
559
+ 2. Through standard Rack patterns
560
+
561
+ #### Extending using Ruby patterns
562
+
563
+ Ruby patterns are something you are already intimately familiar with. Include, extend, ducks, blocks, procs etc are something you know like the front of your MacBook (btw you should _really_ clean that smudge). Eldr is a DSL you already know how to speak.
564
+
565
+ I have designed Edlr to work well with common ruby patterns; you can include it, extend it, pass it blocks etc, and it wont blow up. If it does blow up its a bug.
566
+
567
+ You can add methods to your app (i.e helpers) by including them:
568
+
569
+ ```ruby
570
+ class App < Eldr::App
571
+ include Helpers
572
+ end
573
+ ```
574
+
575
+ If you want to use an app as a block you can:
576
+
577
+ ```ruby
578
+ run App.new do
579
+ get '/' { Rack::Response.new "Hello World" }
580
+ end
581
+ ```
582
+
583
+ Flexibility in Eldr is not accomplished through abstraction but through clarity. You can make Eldr do what you want it to because you will understand it not because it provides every feature.
584
+
585
+ #### Extending using Rack
586
+
587
+ Rails has a powerful code reuse model. You can generalize routes, controllers, even assets and views, then share them. Authentication/Authorization, shopping carts, social engines, admin panels, all abound in the rails world. This reuse of code allows one to build complex large apps that once took months, in a matter of minutes.
588
+
589
+ In Eldr you re-use applications the Rack way. You will architect your app so it can _be mounted_ as a Rack app, then an end-developer can mount it or extend it using Rack tools.
590
+
591
+ For example, imagine a user registration/authentication app, something like Devise.
592
+
593
+ First we create our app class:
594
+
595
+ ```ruby
596
+ class EldrWise
597
+ ###
598
+ # Registrations
599
+ ###
600
+
601
+ # Register a new user
602
+ post '/user' do
603
+ # ... user creation stuff
604
+ redirect_to "/users/#{user.id}"
605
+ end
606
+
607
+ # ... more code
608
+
609
+ ###
610
+ # Users
611
+ ###
612
+ get '/users/:id' do
613
+ render 'users/show'
614
+ end
615
+ end
616
+ ```
617
+
618
+ We would probably want to break this up into separate controllers:
619
+
620
+ ```ruby
621
+ class EldrWise::Registrations
622
+ # ... registration routes
623
+ end
624
+
625
+ class EldrWise::Users
626
+ # ... user routes
627
+ end
628
+
629
+ module EldrWise
630
+ map '/registration' do
631
+ Registrations
632
+ end
633
+
634
+ map '/users' do
635
+ Users
636
+ end
637
+ end
638
+ ```
639
+
640
+ Then an end user can either mount the entire thing:
641
+
642
+ ```ruby
643
+ App.extend EldrWise
644
+ run App
645
+ ```
646
+
647
+ Or just a part:
648
+
649
+ ```ruby
650
+ map '/users' do
651
+ EldrWise::Users
652
+ end
653
+ run App
654
+ ```
655
+
656
+ To customize it they can either extend it:
657
+
658
+ ```ruby
659
+ class UsersController < EldrWise::Users
660
+ end
661
+
662
+ map '/users' do
663
+ UsersController
664
+ end
665
+ ```
666
+
667
+ If we wan to define all our controllers on the root we can use [eldr-cascade](https://github.com/eldr-rb/eldr-cascade). We define the routes we want to override, Cascade will get a 404 on the ones we didn't, then call the next app until it gets a response:
668
+
669
+ ```ruby
670
+ class App
671
+ # override the users post route but nothing else
672
+ post '/users' do
673
+ end
674
+ end
675
+ run Eldr::Cascade.new[App, EldrWise]
676
+ ```
677
+
678
+ Thinking about Rack when you construct your Eldr apps will make them flexible and easy to reuse.
679
+
680
+ ### Using Rack inside Eldr
681
+
682
+ Eldr apps are an instance of Rack::Builder; this means we can use any middleware or other Rack apps (including other Eldr apps) inside an app. To use Rack::Builder features you call the methods on the class.
683
+
684
+ For example, to use Rack session cookies we do:
685
+
686
+ ```ruby
687
+ class App < Eldr::App
688
+ use Rack::Session::Cookie
689
+ end
690
+ ```
691
+
692
+ If we wanted to mount other Eldr apps/controllers in our app we would do the following:
693
+
694
+ ```ruby
695
+ class App < Eldr::App
696
+ map '/users' do
697
+ UsersController
698
+ end
699
+ end
700
+ ```
701
+
702
+ Because rack builder itself can take rack builder instances we can run our App in config.ru just like we expect:
703
+
704
+ ```ruby
705
+ class App < Eldr::App
706
+ use Rack::Session::Cookie
707
+ end
708
+
709
+ run App
710
+ ```
711
+
712
+ ### Redirecting Things
713
+
714
+ Redirects are an _enormously_ complex subject involving thousands of lines of helper code. Just kidding, they are just a status code and a new path. We can create a redirection helper in 5 lines:
715
+
716
+ ```ruby
717
+ module Helpers
718
+ def redirect(path, message)
719
+ [302, {'location' => path}, [message]]
720
+ end
721
+ end
722
+ ```
723
+
724
+ ### Route Handlers: The Power of Action Objects
725
+
726
+ In Edlr (just like in Rack) route handlers are things that respond to call. This means we can use everthing from procs, to blocks, to instances of a class.
727
+
728
+ If we wanted to we could do the following:
729
+
730
+ ```ruby
731
+ class Handler
732
+ def call(env)
733
+ Rack::Response.new "Hi there Dave!"
734
+ end
735
+ end
736
+
737
+ class App
738
+ get '/', Handler.new
739
+ end
740
+ ```
741
+
742
+ This isn't as silly as it might seem. Its a common and powerful pattern that can clean up
743
+ complex controllers.
744
+
745
+ Imagine we have a complex show action that takes up hundreds of lines. Our first inclination will be to split this up into different methods on our controller. This would make our show action a lot shorter; but we would have logic for our show action spread across our controller. Our other actions don't need to be able to access the show action's logic. It would be better to have all the logic for the show action in one object.
746
+
747
+ We can do this by writing our show action as an Action Object. Our action's logic will be contained entirely in this object.
748
+
749
+ Action objects give us the freedom to instantiatie them, inject things, pass them around, test and generally just engage in whatever sociopathic whimsys we might want to do to them.
750
+
751
+ Let's take a look at that show action:
752
+
753
+ ```ruby
754
+ class Show
755
+ attr_accessor :env
756
+
757
+ def helper_logic
758
+ # do things here
759
+ end
760
+
761
+ def params
762
+ env['eldr.params']
763
+ end
764
+
765
+ def call(env)
766
+ @env = env
767
+
768
+ helper_logic
769
+ # @cat = Cat.find params[:id]
770
+ Rack::Response.new "Found cat named #{params['name'].capitalize}!"
771
+ end
772
+ end
773
+
774
+ class CatsController
775
+ get '/cats/:name', Show.new
776
+ end
777
+
778
+ run CatsController
779
+ ```
780
+
781
+ Once you start using this pattern you cant stop. Like nutella, you start putting it on everything, trying it on absurd things you know are wrong -- just in case it might work well.
782
+
783
+ Don't overuse action objects, but remember they are there when you want deliciousness.
784
+
785
+ ### Testing Eldr Apps
786
+
787
+ You are ready to set out on your own but you are afraid of breaking all the things. You need some safety checks to keep you from hurting yourself or your apps. Testing Eldr apps is the same as testing rack apps.
788
+
789
+ In your spec helper include rack-test. With rspec that would look like this:
790
+
791
+ ```ruby
792
+ RSpec.configure do |config|
793
+ config.include Rack::Test::Methods
794
+ end
795
+ ```
796
+
797
+ Now you need to define your app method so rack-test can mount your app. Eldr apps are rack apps so we can _simply_ return a new instance of it:
798
+
799
+ ```ruby
800
+ def app
801
+ YourEldrApp.new
802
+ end
803
+ ```
804
+
805
+ If you want to test each of your apps individually you can use `let` to create a new Rack::Test session thingy:
806
+
807
+ ```ruby
808
+ let(:rt) do
809
+ Rack::Test::Session.new YourEldrApp.new
810
+ end
811
+ ```
812
+
813
+ Then you'll be able to access the rack-test methods from `rt`. For example:
814
+
815
+ ```ruby
816
+ response = rt.get '/'
817
+ response.status.should == 200
818
+ ```
819
+
820
+ Rack apps sure are easy to work with!
821
+
822
+ See the spec/ in this repo for some specific rspec examples.
823
+
824
+ ## Performance
825
+
826
+ Eldr has been built with perfomance in mind. Right now it performs in the middle of the pack and more performance improvements are forthcoming:
827
+
828
+ | Framework | Requests/sec | % from best |
829
+ |---------------|:-------------:|------------:|
830
+ | rack | 10282.20 | 100.0% |
831
+ | hobbit | 8853.81 | 86.11% |
832
+ | roda | 8830.00 | 85.88% |
833
+ | cuba | 8517.00 | 82.83% |
834
+ | lotus-router | 8464.81 | 82.32% |
835
+ | rack-response | 7939.67 | 77.22% |
836
+ | brooklyn | 7639.88 | 74.3% |
837
+ | *eldr* | 7170.15 | 69.73% |
838
+ | rambutan | 6954.75 | 67.64% |
839
+ | nancy | 6516.84 | 63.38% |
840
+ | gin | 3850.00 | 37.44% |
841
+ | nyny | 3745.96 | 36.43% |
842
+ | sinatra | 2740.50 | 26.65% |
843
+ | rails | 2475.50 | 24.08% |
844
+ | scorched | 1692.18 | 16.46% |
845
+ | ramaze | 1490.42 | 14.5% |
846
+
847
+ See the [https://github.com/eldr-rb/bench-micro](bench-micro) repo to run your own perfomance benchmarks
848
+
849
+ ## Help/Resources
850
+
851
+ You can get help from the following places:
852
+
853
+ - [@k_2052](http://twitter.com/k_2052) Feel free to mention me on twitter for quick questions
854
+ - [The Mailing List](https://groups.google.com/forum/#!forum/eldr-ruby) For questions that aren't a bug or feature request
855
+ - [The examples folder](https://github.com/eldr/eldr/tree/master/examples) in this repo has full runnable versions of the quickstart's examples
856
+ - [Issues](https://github.com/eldr/eldr/issues) on this repo for bug reports and feature requests
857
+ - [The Wiki](https://github.com/eldr/eldr/wiki) has a FAQ and links to projects using Eldrg
858
+ - And of course checkout the GitHub org [eldr/](https://github.com/eldr) for all the Eldr gems.
859
+
860
+ ## Contributing
861
+
862
+ 1. Fork. it
863
+ 2. Create. your feature branch (git checkout -b cat-evolver)
864
+ 3. Commit. your changes (git commit -am 'Add Cat Evolution')
865
+ 4. Test. your changes (always be testing)
866
+ 5. Push. to the branch (git push origin cat-evolver)
867
+ 6. Pull. Request. (for extra points include funny gif and or pun in comments)
868
+
869
+ To remember this you can use the easy to remember and totally not tongue-in-check initialism:
870
+ FCCTPP.
871
+
872
+ I don't want any of these steps to scare you off. If you don't know how to do something or are struggle getting it to work feel free to create a pull request or issue anyway. I'll be happy to help you get your contributions up to code and into the repo!
873
+
874
+ ## License
875
+
876
+ Licensed under MIT by K-2052.