lotus-controller 0.2.0 → 0.3.0

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
  SHA1:
3
- metadata.gz: 596855476b8c72b53e86d1101e54edb8137e4206
4
- data.tar.gz: 40df1097012f2d091aad3eff3734cdd71077ecde
3
+ metadata.gz: 4f0f02211060856c6f853b05907e1993a23684a2
4
+ data.tar.gz: 98db51b586aa274b812def351f2960219d26a254
5
5
  SHA512:
6
- metadata.gz: ebeec9005fb1c28cc195af167872a9d6aa206c65c951488b9671c11a472ff36c017c3c647c9e964db92a903cfa16b7a0f6b3b4dab777d1c18fa7de7438c590e5
7
- data.tar.gz: 2626b3767d3a68ef4a2b7418c3d0711e68e27ea70f32fed7623391419ef2eacc447b65476eaabac224121ff68f1e51b9cf2bf07961a3280637d3231b39a0c159
6
+ metadata.gz: 8a9988d2c322dba57028f60615652aa4307ef2808a1a742e7a12c15c70609defb83abec4cd8d0c31eddaf5de49e618f74a4010c97c2c03d95a4544fe459b388a
7
+ data.tar.gz: 688a5dba74eecf6c8d6a14891c9b4d0d2c8b853864e66fa0beb6f09c0abd3918b18ab2daf957e64ece73a1e72ea86f803b67ffbf6bd3a8bc1e0e433a66767f05
data/CHANGELOG.md CHANGED
@@ -1,127 +1,65 @@
1
- ## v0.2.0
2
- ### Jun 23, 2014
3
-
4
- 48a5715 2014-06-22 **Luca Guidi** Made Lotus::Action#content_type public
5
-
6
- 2d7b0cc 2014-06-22 **Luca Guidi** Let to specify a default format for all the requests that aren't strict about the requested Mime type (eg. `*/*`).
7
-
8
- 401c1af 2014-06-19 **Luca Guidi** [breaking] Raise error if Lotus::Controller::Configuration#format doesn't receive the proper argument. Let Lotus::Action::Mime#accept to work with registered mime types
9
-
10
- 028ec2e 2014-06-19 **Luca Guidi** Make Lotus::Action::Mime#format a public method
11
-
12
- ce42cd4 2014-06-19 **Luca Guidi** Detect the asked mime type and return the corresponding format
13
-
14
- 87323ad 2014-06-19 **Luca Guidi** Let actions to detect accepted mime type and to return the correct format symbol. Configuration can now register mime types
15
-
16
- adf0357 2014-06-18 **Krzysztof Zalewski** [breaking] Implement Action#format
17
-
18
- 328153b 2014-06-17 **Luca Guidi** Bump version to v0.2.0
19
-
20
- b2e5c85 2014-06-17 **Luca Guidi** Depend on lotus-utils ~> 0.2
21
-
22
- 166a3c8 2014-06-17 **Luca Guidi** Controller.duplicate can accept nil controllers namespace
23
-
24
- c11bc96 2014-06-17 **Luca Guidi** Lotus::Controller: .duplicate => .dup, .generate => .duplicate
25
-
26
- 9488c66 2014-06-16 **Luca Guidi** Gem: build only with lib/ and essential files
27
-
28
- cd7038c 2014-06-16 **Luca Guidi** [breaking] Removed Lotus::Action::Throwable#throw in favor of #halt
29
-
30
- c200e68 2014-06-16 **Luca Guidi** Pretty print exceptions in rack.errors
31
-
32
- 3c122b0 2014-06-13 **Krzysztof Zalewski** Reference exception in rack.errors
33
-
34
- 9e64c3f 2014-06-11 **Luca Guidi** Let Lotus::Controller.generate to set action_module
35
-
36
- f28d7e3 2014-06-11 **Luca Guidi** Introducing Lotus::Controller.generate as shortcut for .duplicate and .configure
37
-
38
- af217ea 2014-06-08 **Luca Guidi** [breaking] Use composition over inheritance for Lotus::Action::Params
39
-
40
- 47e86db 2014-06-08 **Luca Guidi** [breaking] Use composition over inheritance for Lotus::Action::CookieJar
41
-
42
- 5e2a4a7 2014-05-28 **Luca Guidi** Allow standalone actions to inherit configuration from the right framework. Added Configuration#modules in order to configure the additional modules to include by default
43
-
44
- ceb7214 2014-05-28 **Luca Guidi** Better Ruby idioms
45
-
46
- 075f9c3 2014-05-28 **Luca Guidi** Use the proper level of encapsulation for Configuration
47
-
48
- 431970c 2014-05-27 **Luca Guidi** Ensure the right level of duplication for Lotus::Controller
49
-
50
- ae49c57 2014-05-27 **Luca Guidi** [breaking] Keep independent copies of framework, controller and action configurations. Introduced in action_module for configuration.
51
-
52
- c56683a 2014-05-27 **Luca Guidi** [breaking] Introduced configuration for Controller and Action
53
-
54
- 8d39557 2014-05-10 **Luca Guidi** Added support for Ruby 2.1.2
55
-
56
- c2854b6 2014-04-27 **Luca Guidi** Make HTTP status messages compliant with IANA and Rack
57
-
58
- c137c3f 2014-04-27 **Luca Guidi** Implemented Action.use, that let to use a Rack middleware as a before callback
59
-
60
- eb86286 2014-04-20 **Damir Zekic** Replace `#throw` override with `#halt` method
61
-
62
- 6da21f9 2014-03-17 **Damir Zekic** Allow exception handling to be disabled
63
-
64
- c0ccb72 2014-02-24 **Luca Guidi Added support for Ruby 2.1.1
65
-
66
- ## v0.1.0
67
- ### Feb 23, 2014
68
-
69
- f750e4c 2014-02-22 **Luca Guidi** Moved .handled_exceptions from Action to Controller, so that the place where to configure the framework will be easier to find for developers.
70
-
71
- 1de2a27 2014-02-17 **Luca Guidi** Added Lotus::Action.handle_exception
72
-
73
- fd9e080 2014-02-13 **Luca Guidi** Implemented Lotus::Action.accept, in order to restrict the access when a non supported mime type is requested
74
-
75
- 516983f 2014-02-13 **Luca Guidi** Implemented Lotus::Action#accept?
76
-
77
- 634719a 2014-02-13 **Luca Guidi** Changed auto content type policy
78
-
79
- 7cce354 2014-02-12 **Luca Guidi** Use @_env instead of the argument in Lotus::Action::Callable#call
80
-
81
- a41b43d 2014-02-12 **Luca Guidi** Extracted constants for Lotus::Action::Params
82
-
83
- 6bdf55b 2014-01-31 **Luca Guidi** Make session support optional
84
-
85
- 29c5f8c 2014-01-31 **Luca Guidi** Make cookies support optional
86
-
87
- 0b36afb 2014-01-31 **Luca Guidi** Removed Lotus::HTTP::Request and Response
88
-
89
- 25b2bcf 2014-01-31 **Luca Guidi** Return serialized Rack response (Array) instead of the object. Lotus::Action::Redirect no longer depends on Response. Lotus::Action::CookieJar no longer depends on Response, but on headers.
90
-
91
- b306f4f 2014-01-31 **Luca Guidi** Lotus::Action::Rack#session no longer depends on Request
92
-
93
- 9a44477 2014-01-31 **Luca Guidi** Lotus::Action::Mime no longer depends on Request
94
-
95
- 9b7f524 2014-01-31 **Luca Guidi** Lotus::Action::CookieJar logic for cookies extraction/set/get is no longer dependant on Request and Response.
96
-
97
- fc6de40 2013-09-24 **Luca Guidi** Don't wrap body if respond to #each
98
-
99
- ce46399 2013-09-24 **Luca Guidi** Introducing factory for Response
100
-
101
- 83adc9c 2013-08-07 **Luca Guidi** Ensure to wrap body for HTTP::Response
102
-
103
- 0a41bf8 2013-08-07 **Luca Guidi** Introducing Lotus::HTTP::Request/Response
104
-
105
- c44d440 2013-07-12 **Luca Guidi** Integration tests for sessions
106
-
107
- e62b355 2013-07-11 **Luca Guidi** Added automatic Mime Type capabilities
108
-
109
- 351eb2f 2013-07-11 **Luca Guidi** Split features in proper `Lotus::Action` submodules.
110
-
111
- 9d268df 2013-07-10 **Luca Guidi** Introducing throw facility, to stop the request flow immediately.
112
-
113
- 73af6d1 2013-07-09 **Luca Guidi** Integration tests with Lotus::Router
114
-
115
- 6938fae 2013-07-05 **Luca Guidi** Implemented cookies facilities.
116
-
117
- 69f4c62 2013-07-02 **Luca Guidi** Rack compatibility
118
-
119
- 13bd2d2 2013-06-28 **Luca Guidi** Implemented #redirect_to
120
-
121
- 5adbee1 2013-06-28 **Luca Guidi** Implemented sessions support
122
-
123
- 4bb794d 2013-06-28 **Luca Guidi** Implemented action callbacks
124
-
125
- 3b792dc 2013-06-25 **Luca Guidi** Introducing Lotus::Controller
126
-
127
- d279c48 2013-06-25 **Luca Guidi** Initial mess
1
+ # Lotus::Controller
2
+ Complete, fast and testable actions for Rack
3
+
4
+ ## v0.3.0 - 2014-12-23
5
+ ### Added
6
+ - [Luca Guidi] Introduced `Action#request_id` as unique identifier for an incoming HTTP request
7
+ - [Luca Guidi] Introduced `Lotus::Controller.load!` as loading framework entry point
8
+ - [Kir Shatrov] Allow to define a default charset (`default_charset` configuration)
9
+ - [Kir Shatrov] Automatic content type with charset (eg `Content-Type: text/html; charset=utf-8`)
10
+ - [Michał Krzyżanowski] Allow to specify custom exception handlers: procs or methods (`exception_handler` configuration)
11
+ - [Karl Freeman & Lucas Souza] Introduced HTTP caching (`Cache-Control`, `Last-Modified`, ETAG, Conditional GET, expires)
12
+ - [Satoshi Amemiya] Introduced `Action::Params#to_h` and `#to_hash`
13
+ - [Luca Guidi] Added `#params` and `#errors` as default exposures
14
+ - [Luca Guidi] Introduced complete params validations
15
+ - [Luca Guidi & Matthew Bellantoni] Allow to whitelist params
16
+ - [Luca Guidi & Matthew Bellantoni] Allow to define custom classes for params via `Action.params`
17
+ - [Krzysztof Zalewski] Introduced `Action#format` as query method to introspect the requested mime type
18
+ - [Luca Guidi] Official support for Ruby 2.2
19
+
20
+ ### Changed
21
+ - [Trung Lê] Renamed `Configuration#modules` to `#prepare`
22
+ - [Luca Guidi] Update HTTP status codes to IETF RFC 7231
23
+ - [Luca Guidi] When `Lotus::Controller` is included, don't inject code
24
+ - [Luca Guidi] Removed `Controller.action` as a DSL to define actions
25
+ - [Krzysztof Zalewski] Removed `Action#content_type` in favor of `#format=` which accepts a symbol (eg. `:json`)
26
+ - [Fuad Saud] Reduce method visibility where possible (Ruby `private` and `protected`)
27
+
28
+ ### Fixed
29
+ - [Luca Guidi] Don't let exposures definition to override existing methods
30
+
31
+ ## v0.2.0 - 2014-06-23
32
+ ### Added
33
+ - [Luca Guidi] Introduced `Controller.configure` and `Controller.duplicate`
34
+ - [Luca Guidi] Introduced `Action.use`, that let to use a Rack middleware as a before callback
35
+ – [Luca Guidi] Allow to define a default mime type when the request is `Accept: */*` (`default_format` configuration)
36
+ [Luca Guidi] Allow to register custom mime types and associate them to a symbol (`format` configuration)
37
+ - [Luca Guidi] Introduced `Configuration#handle_exceptions` to associate exceptions to HTTP statuses
38
+ - [Damir Zekic] Allow developers to toggle exception handling (`handle_exceptions` configuration)
39
+ - [Luca Guidi] Introduced `Controller::Configuration`
40
+ - [Luca Guidi] Official support for Ruby 2.1
41
+
42
+ ### Changed
43
+ - [Luca Guidi] `Lotus::Action::Params` doesn't inherit from `Lotus::Utils::Hash` anymore
44
+ - [Luca Guidi] `Lotus::Action::CookieJar` doesn't inherit from `Lotus::Utils::Hash` anymore
45
+ - [Luca Guidi] Make HTTP status messages compliant with IANA and Rack
46
+ - [Damir Zekic] Moved `#throw` override logic into `#halt`, which keeps the same semantic
47
+
48
+ ### Fixed
49
+ - [Krzysztof Zalewski] Reference exception in `rack.errors`
50
+
51
+ ## v0.1.0 - 2014-02-23
52
+ ### Added
53
+ - [Luca Guidi] Introduced `Action.accept` to whitelist accepted mime types
54
+ - [Luca Guidi] Introduced `Action#accept?` as a query method for the current request
55
+ - [Luca Guidi] Allow to whitelist handled exceptions and associate them to an HTTP status
56
+ - [Luca Guidi] Automatic `Content-Type`
57
+ - [Luca Guidi] Use `throw` as a control flow which understands HTTP status
58
+ - [Luca Guidi] Introduced opt-in support for HTTP/Rack cookies
59
+ - [Luca Guidi] Introduced opt-in support for HTTP/Rack sessions
60
+ - [Luca Guidi] Introduced HTTP redirect API
61
+ - [Luca Guidi] Introduced callbacks for actions: before and after
62
+ - [Luca Guidi] Introduced exceptions handling with HTTP statuses
63
+ - [Luca Guidi] Introduced exposures
64
+ - [Luca Guidi] Introduced basic actions compatible with Rack
65
+ - [Luca Guidi] Official support for Ruby 2.0
data/README.md CHANGED
@@ -52,8 +52,8 @@ It's designed to be fast and testable.
52
52
 
53
53
  ### Actions
54
54
 
55
- The core of this frameworks are the actions.
56
- They are the endpoint that responds to incoming HTTP requests.
55
+ The core of this framework are the actions.
56
+ They are the endpoints that respond to incoming HTTP requests.
57
57
 
58
58
  ```ruby
59
59
  class Show
@@ -66,17 +66,17 @@ end
66
66
  ```
67
67
 
68
68
  The usage of `Lotus::Action` follows the Lotus philosophy: include a module and implement a minimal interface.
69
- In this case, it's only one method: `#call(params)`.
69
+ In this case, the interface is one method: `#call(params)`.
70
70
 
71
71
  Lotus is designed to not interfere with inheritance.
72
72
  This is important, because you can implement your own initialization strategy.
73
73
 
74
- __An action is an object__ after all, it's important that __you have the full control on it__.
75
- In other words, you have the freedom of instantiate, inject dependencies and test it, both with unit and integration.
74
+ __An action is an object__. That's important because __you have the full control on it__.
75
+ In other words, you have the freedom to instantiate, inject dependencies and test it, both at the unit and integration level.
76
76
 
77
- In the example below, we're stating that the default repository is `Article`, but during an unit test we can inject a stubbed version, and invoke `#call` with the params that we want to simulate.
77
+ In the example below, the default repository is `Article`. During a unit test we can inject a stubbed version, and invoke `#call` with the params.
78
78
  __We're avoiding HTTP calls__, we're eventually avoiding to hit the database (it depends on the stubbed repository), __we're just dealing with message passing__.
79
- Imagine how **fast** can be a unit test like this.
79
+ Imagine how **fast** the unit test could be.
80
80
 
81
81
  ```ruby
82
82
  class Show
@@ -99,7 +99,7 @@ action.call({ id: 23 })
99
99
 
100
100
  The request params are passed as an argument to the `#call` method.
101
101
  If routed with *Lotus::Router*, it extracts the relevant bits from the Rack `env` (eg the requested `:id`).
102
- Otherwise everything it's passed as it is: the full Rack `env` in production, and the given `Hash` for unit tests.
102
+ Otherwise everything passed as is: the full Rack `env` in production, and the given `Hash` for unit tests.
103
103
 
104
104
  With Lotus::Router:
105
105
 
@@ -143,9 +143,81 @@ action = Show.new
143
143
  response = action.call({ id: 23, key: 'value' })
144
144
  ```
145
145
 
146
+ #### Whitelisting
147
+
148
+ Params represent an untrusted input.
149
+ For security reasons it's recommended to whitelist them.
150
+
151
+ ```ruby
152
+ require 'lotus/controller'
153
+
154
+ class Signup
155
+ include Lotus::Action
156
+
157
+ params do
158
+ param :first_name
159
+ param :last_name
160
+ param :email
161
+ end
162
+
163
+ def call(params)
164
+ # Describe inheritance hierarchy
165
+ puts params.class # => Signup::Params
166
+ puts params.class.superclass # => Lotus::Action::Params
167
+
168
+ # Whitelist :first_name, but not :admin
169
+ puts params[:first_name] # => "Luca"
170
+ puts params[:admin] # => nil
171
+ end
172
+ end
173
+ ```
174
+
175
+ #### Validations & Coercions
176
+
177
+ Because params are a well defined set of data required to fulfill a feature
178
+ in your application, you can validate them. So you can avoid hitting lower MVC layers
179
+ when params are invalid.
180
+
181
+ If you specify the `:type` option, the param will be coerced.
182
+
183
+ ```ruby
184
+ require 'lotus/controller'
185
+
186
+ class Signup
187
+ MEGABYTE = 1024 ** 2
188
+ include Lotus::Action
189
+
190
+ params do
191
+ param :first_name, presence: true
192
+ param :last_name, presence: true
193
+ param :email, presence: true, format: /@/, confirmation: true
194
+ param :password, presence: true, confirmation: true
195
+ param :terms_of_service, acceptance: true
196
+ param :avatar, size: 0..(MEGABYTE * 3)
197
+ param :age, type: Integer, size: 18..99
198
+ end
199
+
200
+ def call(params)
201
+ halt 400 unless params.valid?
202
+ # ...
203
+ end
204
+ end
205
+
206
+ action = Signup.new
207
+
208
+ action.call(valid_params) # => [200, {}, ...]
209
+ action.errors.empty? # => true
210
+
211
+ action.call(invalid_params) # => [400, {}, ...]
212
+ action.errors # => #<Lotus::Validations::Errors:0x007fabe4b433d0 @errors={...}>
213
+
214
+ action.errors.for(:email)
215
+ # => [#<Lotus::Validations::Error:0x007fabe4b432e0 @attribute=:email, @validation=:presence, @expected=true, @actual=nil>]
216
+ ```
217
+
146
218
  ### Response
147
219
 
148
- The output of `#call` is a serialized Rack::Response (see [#finish](http://rack.rubyforge.org/doc/classes/Rack/Response.html#M000182)):
220
+ The output of `#call` is a serialized Rack::Response (see [#finish](http://rubydoc.info/github/rack/rack/master/Rack/Response#finish-instance_method)):
149
221
 
150
222
  ```ruby
151
223
  class Show
@@ -176,13 +248,20 @@ end
176
248
  action = Show.new
177
249
  action.call({}) # => [201, { "X-Custom" => "OK" }, ["Hi!"]]
178
250
  ```
251
+
179
252
  ### Exposures
180
253
 
181
254
  We know that actions are objects and Lotus::Action respects one of the pillars of OOP: __encapsulation__.
182
255
  Other frameworks extract instance variables (`@ivar`) and make them available to the view context.
183
- The solution of Lotus::Action is a simple and powerful DSL: `expose`.
184
- It's a thin layer on top of `attr_reader`. When used, it creates a getter for the given attribute, and adds it to the _exposures_.
185
- Exposures (`#exposures`) is set of exposed attributes, so that the view context can have the information needed to render a page.
256
+
257
+ Lotus::Action's solution is the simple and powerful DSL: `expose`.
258
+ It's a thin layer on top of `attr_reader`.
259
+
260
+ Using `expose` creates a getter for the given attribute, and adds it to the _exposures_.
261
+ Exposures (`#exposures`) are a set of attributes exposed to the view.
262
+ That is to say the variables necessary for rendering a view.
263
+
264
+ By default, all Lotus::Actions expose `#params` and `#errors`.
186
265
 
187
266
  ```ruby
188
267
  class Show
@@ -205,7 +284,7 @@ puts action.exposures # => { article: <Article:0x007f965c1d0318 @id=23> }
205
284
 
206
285
  ### Callbacks
207
286
 
208
- It offers powerful, inheritable callbacks chain which is executed before and/or after your `#call` method invocation:
287
+ It offers a powerful, inheritable callback chain which is executed before and/or after your `#call` method invocation:
209
288
 
210
289
  ```ruby
211
290
  class Show
@@ -235,7 +314,7 @@ class Show
235
314
  include Lotus::Action
236
315
 
237
316
  before { ... } # do some authentication stuff
238
- before {|params| @article = Article.find params[:id] }
317
+ before { |params| @article = Article.find params[:id] }
239
318
 
240
319
  def call(params)
241
320
  end
@@ -259,7 +338,7 @@ action = Show.new
259
338
  action.call({}) # => [500, {}, ["Internal Server Error"]]
260
339
  ```
261
340
 
262
- You can define how a specific raised exception should be transformed in an HTTP status.
341
+ You can map a specific raised exception to a different HTTP status.
263
342
 
264
343
  ```ruby
265
344
  class Show
@@ -275,6 +354,27 @@ action = Show.new
275
354
  action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
276
355
  ```
277
356
 
357
+ You can also define custom handlers for exceptions.
358
+
359
+ ```ruby
360
+ class Create
361
+ include Lotus::Action
362
+ handle_exception ArgumentError => :my_custom_handler
363
+
364
+ def call(params)
365
+ raise ArgumentError.new("Invalid arguments")
366
+ end
367
+
368
+ private
369
+ def my_custom_handler(exception)
370
+ status 400, exception.message
371
+ end
372
+ end
373
+
374
+ action = Create.new
375
+ action.call({}) # => [400, {}, ["Invalid arguments"]]
376
+ ```
377
+
278
378
  Exception policies can be defined globally, **before** the controllers/actions
279
379
  are loaded.
280
380
 
@@ -304,22 +404,22 @@ end
304
404
 
305
405
  # or
306
406
 
307
- class ArticlesController
308
- include Lotus::Controller
407
+ module Articles
408
+ class Show
409
+ include Lotus::Action
309
410
 
310
- configure do
311
- handle_exceptions false
312
- end
411
+ configure do
412
+ handle_exceptions false
413
+ end
313
414
 
314
- action 'Show' do
315
415
  def call(params)
316
416
  @article = Article.find params[:id]
317
417
  end
318
418
  end
319
419
  end
320
420
 
321
- action = ArticlesController::Show.new
322
- action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
421
+ action = Articles::Show.new
422
+ action.call({id: 'unknown'}) # => raises RecordNotFound
323
423
  ```
324
424
 
325
425
  ### Throwable HTTP statuses
@@ -348,9 +448,9 @@ action.call({}) # => [401, {}, ["Unauthorized"]]
348
448
 
349
449
  ### Cookies
350
450
 
351
- It offers convenient access to cookies.
451
+ Lotus::Controller offers convenient access to cookies.
352
452
 
353
- They are read as an Hash from Rack env:
453
+ They are read as a Hash from Rack env:
354
454
 
355
455
  ```ruby
356
456
  require 'lotus/controller'
@@ -370,7 +470,7 @@ action = ReadCookiesFromRackEnv.new
370
470
  action.call({'HTTP_COOKIE' => 'foo=bar'})
371
471
  ```
372
472
 
373
- They are set like an Hash:
473
+ They are set like a Hash:
374
474
 
375
475
  ```ruby
376
476
  require 'lotus/controller'
@@ -432,7 +532,7 @@ action = ReadSessionFromRackEnv.new
432
532
  action.call({ 'rack.session' => { 'age' => '31' }})
433
533
  ```
434
534
 
435
- Values can be set like an Hash:
535
+ Values can be set like a Hash:
436
536
 
437
537
  ```ruby
438
538
  require 'lotus/controller'
@@ -452,7 +552,7 @@ action = SetSession.new
452
552
  action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."]
453
553
  ```
454
554
 
455
- Values can be removed like an Hash:
555
+ Values can be removed like a Hash:
456
556
 
457
557
  ```ruby
458
558
  require 'lotus/controller'
@@ -480,6 +580,92 @@ use Rack::Session::Cookie, secret: SecureRandom.hex(64)
480
580
  run Show.new
481
581
  ```
482
582
 
583
+ ### Http Cache
584
+
585
+ Lotus::Controller sets your headers correctly according to RFC 2616 / 14.9 for more on standard cache control directives: http://tools.ietf.org/html/rfc2616#section-14.9.1
586
+
587
+ You can easily set the Cache-Control header for your actions:
588
+
589
+ ```ruby
590
+ require 'lotus/controller'
591
+ require 'lotus/action/cache'
592
+
593
+ class HttpCacheController
594
+ include Lotus::Action
595
+ include Lotus::Action::Cache
596
+
597
+ cache_control :public, max_age: 600 # => Cache-Control: public, max-age=600
598
+
599
+ def call(params)
600
+ # ...
601
+ end
602
+ end
603
+ ```
604
+
605
+ Expires header can be specified using `expires` method:
606
+
607
+ ```ruby
608
+ require 'lotus/controller'
609
+ require 'lotus/action/cache'
610
+
611
+ class HttpCacheController
612
+ include Lotus::Action
613
+ include Lotus::Action::Cache
614
+
615
+ expires 60, :public, max_age: 600 # => Expires: Sun, 03 Aug 2014 17:47:02 GMT, Cache-Control: public, max-age=600
616
+
617
+ def call(params)
618
+ # ...
619
+ end
620
+ end
621
+ ```
622
+
623
+ ### Conditional Get
624
+
625
+ According to HTTP specification, conditional GETs provide a way for web servers to inform clients that the response to a GET request hasn't change since the last request returning a Not Modified header (304).
626
+
627
+ Passing the HTTP_IF_NONE_MATCH (content identifier) or HTTP_IF_MODIFIED_SINCE (timestamp) headers allows the web server define if the client has a fresh version of a given resource.
628
+
629
+ You can easily take advantage of Conditional Get using `#fresh` method:
630
+
631
+ ```ruby
632
+ require 'lotus/controller'
633
+ require 'lotus/action/cache'
634
+
635
+ class ConditionalGetController
636
+ include Lotus::Action
637
+ include Lotus::Action::Cache
638
+
639
+ def call(params)
640
+ # ...
641
+ fresh etag: @resource.cache_key
642
+ # => halt 304 with header IfNoneMatch = @resource.cache_key
643
+ end
644
+ end
645
+ ```
646
+
647
+ If `@resource.cache_key` is equal to `IfNoneMatch` header, then lotus will `halt 304`.
648
+
649
+ The same behavior is accomplished using `last_modified`:
650
+
651
+ ```ruby
652
+ require 'lotus/controller'
653
+ require 'lotus/action/cache'
654
+
655
+ class ConditionalGetController
656
+ include Lotus::Action
657
+ include Lotus::Action::Cache
658
+
659
+ def call(params)
660
+ # ...
661
+ fresh last_modified: @resource.update_at
662
+ # => halt 304 with header IfModifiedSince = @resource.update_at.httpdate
663
+ end
664
+ end
665
+ ```
666
+
667
+ If `@resource.update_at` is equal to `IfModifiedSince` header, then lotus will `halt 304`.
668
+
483
669
  ### Redirect
484
670
 
485
671
  If you need to redirect the client to another resource, use `#redirect_to`:
@@ -498,7 +684,23 @@ action = Create.new
498
684
  action.call({ article: { title: 'Hello' }}) # => [302, {'Location' => '/articles/23'}, '']
499
685
  ```
500
686
 
501
- ### Mime types
687
+ You can also redirect with a custom status code:
688
+
689
+ ```ruby
690
+ class Create
691
+ include Lotus::Action
692
+
693
+ def call(params)
694
+ # ...
695
+ redirect_to 'http://example.com/articles/23', status: 301
696
+ end
697
+ end
698
+
699
+ action = Create.new
700
+ action.call({ article: { title: 'Hello' }}) # => [301, {'Location' => '/articles/23'}, '']
701
+ ```
702
+
703
+ ### Mime Types
502
704
 
503
705
  Lotus::Action automatically sets the `Content-Type` header, according to the request.
504
706
 
@@ -627,10 +829,10 @@ You can set the body directly (see [response](#response)), or use [Lotus::View](
627
829
 
628
830
  ### Controllers
629
831
 
630
- A Controller is nothing more than a logical group for actions.
832
+ A Controller is nothing more than a logical group of actions: just a Ruby module.
631
833
 
632
834
  ```ruby
633
- class ArticlesController
835
+ module Articles
634
836
  class Index
635
837
  include Lotus::Action
636
838
 
@@ -643,29 +845,13 @@ class ArticlesController
643
845
  # ...
644
846
  end
645
847
  end
646
- ```
647
-
648
- Which is a bit verbose. Instead, just do:
649
-
650
- ```ruby
651
- class ArticlesController
652
- include Lotus::Controller
653
848
 
654
- action 'Index' do
655
- # ...
656
- end
657
-
658
- action 'Show' do
659
- # ...
660
- end
661
- end
662
-
663
- ArticlesController::Index.new.call({})
849
+ Articles::Index.new.call({})
664
850
  ```
665
851
 
666
852
  ### Lotus::Router integration
667
853
 
668
- While Lotus::Router works great with this framework, Lotus::Controller doesn't depend from it.
854
+ While Lotus::Router works great with this framework, Lotus::Controller doesn't depend on it.
669
855
  You, as developer, are free to choose your own routing system.
670
856
 
671
857
  But, if you use them together, the **only constraint is that an action must support _arity 0_ in its constructor**.
@@ -703,20 +889,19 @@ While a Lotus application's architecture is more web oriented, this framework is
703
889
  ### Rack middleware
704
890
 
705
891
  Rack middleware can be configured globally in `config.ru`, but often they add an
706
- unnecessary overhead for all those endpoints who aren't direct users of a
707
- certain middleware. Think about a middleware to create sessions, where only
708
- `SessionsController::Create` may be involved and the rest of the application
709
- shouldn't pay the performance ticket of calling that middleware.
892
+ unnecessary overhead for all those endpoints that aren't direct users of a
893
+ certain middleware.
894
+
895
+ Think about a middleware to create sessions, where only `SessionsController::Create` needs that middleware, but every other action pays the performance price for that middleware.
710
896
 
711
- An action can employ one or more Rack middleware, with `.use`.
897
+ The solution is that an action can employ one or more Rack middleware, with `.use`.
712
898
 
713
899
  ```ruby
714
900
  require 'lotus/controller'
715
901
 
716
- class SessionsController
717
- include Lotus::Controller
718
-
719
- action 'Create' do
902
+ module Sessions
903
+ class Create
904
+ include Lotus::Action
720
905
  use OmniAuth
721
906
 
722
907
  def call(params)
@@ -729,10 +914,10 @@ end
729
914
  ```ruby
730
915
  require 'lotus/controller'
731
916
 
732
- class SessionsController
733
- include Lotus::Controller
917
+ module Sessions
918
+ class Create
919
+ include Lotus::Controller
734
920
 
735
- action 'Create' do
736
921
  use XMiddleware.new('x', 123)
737
922
  use YMiddleware.new
738
923
  use ZMiddleware
@@ -746,7 +931,7 @@ end
746
931
 
747
932
  ### Configuration
748
933
 
749
- Lotus::Controller can be configured with a DSL that determines its behavior.
934
+ Lotus::Controller can be configured with a DSL.
750
935
  It supports a few options:
751
936
 
752
937
  ```ruby
@@ -754,7 +939,7 @@ require 'lotus/controller'
754
939
 
755
940
  Lotus::Controller.configure do
756
941
  # Handle exceptions with HTTP statuses (true) or don't catch them (false)
757
- # Argument: boolean, defaults to true
942
+ # Argument: boolean, defaults to `true`
758
943
  #
759
944
  handle_exceptions true
760
945
 
@@ -764,65 +949,77 @@ Lotus::Controller.configure do
764
949
  #
765
950
  handle_exception ArgumentError => 404
766
951
 
767
- # Configure which module to include when Lotus::Controller.action is used
768
- # Argument: module, defaults to Lotus::Action
769
- #
770
- action_module MyApp::Action # module, defaults to Lotus::Action
771
-
772
952
  # Register a format to mime type mapping
773
953
  # Argument: hash, key: format symbol, value: mime type string, empty by default
774
954
  #
775
955
  format custom: 'application/custom'
776
956
 
777
- # Configure the modules to be included/extended/prepended by default.
778
- # Argument: proc, empty by default
957
+ # Define a default format to return in case of HTTP request with `Accept: */*`
958
+ # If not defined here, it will return Rack's default: `application/octet-stream`
959
+ # Argument: symbol, it should be already known. defaults to `nil`
779
960
  #
780
- modules do
961
+ default_format :html
962
+
963
+ # Define a default charset to return in the `Content-Type` response header
964
+ # If not defined here, it returns `utf-8`
965
+ # Argument: string, defaults to `nil`
966
+ #
967
+ default_charset 'koi8-r'
968
+
969
+ # Configure the logic to be executed when Lotus::Action is included
970
+ # This is useful to DRY code by having a single place where to configure
971
+ # shared behaviors like authentication, sessions, cookies etc.
972
+ # Argument: proc
973
+ #
974
+ prepare do
781
975
  include Lotus::Action::Sessions
782
- prepend MyLibrary::Session::Store
976
+ include MyAuthentication
977
+ use SomeMiddleWare
978
+
979
+ before { authenticate! }
783
980
  end
784
981
  end
785
982
  ```
786
983
 
787
- All those global configurations can be overwritten at a finer grained level:
788
- controllers. Each controller and action has its own copy of the global
789
- configuration, so that changes are inherited from the top to the bottom, but
790
- not bubbled up in the opposite direction.
984
+ All of the global configurations can be overwritten at the controller level.
985
+ Each controller and action has its own copy of the global configuration.
986
+
987
+ This means changes are inherited from the top to the bottom, but do not bubble back up.
791
988
 
792
989
  ```ruby
793
990
  require 'lotus/controller'
794
991
 
795
992
  Lotus::Controller.configure do
796
- handle_exception ArgumentError => 404
993
+ handle_exception ArgumentError => 400
797
994
  end
798
995
 
799
- class ArticlesController
800
- include Lotus::Controller
996
+ module Articles
997
+ class Create
998
+ include Lotus::Action
801
999
 
802
- configure do
803
- handle_exceptions false
804
- end
1000
+ configure do
1001
+ handle_exceptions false
1002
+ end
805
1003
 
806
- action 'Create' do
807
1004
  def call(params)
808
1005
  raise ArgumentError
809
1006
  end
810
1007
  end
811
1008
  end
812
1009
 
813
- class UsersController
814
- include Lotus::Controller
1010
+ module Users
1011
+ class Create
1012
+ include Lotus::Action
815
1013
 
816
- action 'Create' do
817
1014
  def call(params)
818
1015
  raise ArgumentError
819
1016
  end
820
1017
  end
821
1018
  end
822
1019
 
823
- UsersController::Create.new.call({}) # => HTTP 400
1020
+ Users::Create.new.call({}) # => HTTP 400
824
1021
 
825
- ArticlesController::Create.new.call({})
1022
+ Articles::Create.new.call({})
826
1023
  # => raises ArgumentError because we set handle_exceptions to false
827
1024
  ```
828
1025
 
@@ -850,7 +1047,7 @@ end
850
1047
  ```
851
1048
 
852
1049
  The code above defines `WebApp::Controller` and `WebApp::Action`, to be used for
853
- the `WebApp` endpoints, while `ApiApp::Controller` and `ApiApp::Action` have
1050
+ the `WebApp` endpoints, while `ApiApp::Controller` and `ApiApp::Action` have
854
1051
  a different configuration.
855
1052
 
856
1053
  ### Thread safety
@@ -877,6 +1074,9 @@ end
877
1074
  run Action
878
1075
  ```
879
1076
 
1077
+ Lotus::Controller heavely depends on class configuration, to ensure immutability
1078
+ in deployment environments, please consider of invoke `Lotus::Controller.load!`.
1079
+
880
1080
  ## Versioning
881
1081
 
882
1082
  __Lotus::Controller__ uses [Semantic Versioning 2.0.0](http://semver.org)