lotus-controller 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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)