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 +4 -4
- data/CHANGELOG.md +65 -127
- data/README.md +289 -89
- data/lib/lotus/action.rb +12 -6
- data/lib/lotus/action/cache.rb +174 -0
- data/lib/lotus/action/cache/cache_control.rb +70 -0
- data/lib/lotus/action/cache/conditional_get.rb +93 -0
- data/lib/lotus/action/cache/directives.rb +99 -0
- data/lib/lotus/action/cache/expires.rb +73 -0
- data/lib/lotus/action/callable.rb +3 -2
- data/lib/lotus/action/callbacks.rb +8 -0
- data/lib/lotus/action/configurable.rb +3 -2
- data/lib/lotus/action/cookies.rb +13 -12
- data/lib/lotus/action/exposable.rb +31 -4
- data/lib/lotus/action/flash.rb +141 -0
- data/lib/lotus/action/glue.rb +35 -0
- data/lib/lotus/action/mime.rb +82 -4
- data/lib/lotus/action/params.rb +87 -3
- data/lib/lotus/action/rack.rb +66 -51
- data/lib/lotus/action/redirect.rb +21 -3
- data/lib/lotus/action/session.rb +97 -0
- data/lib/lotus/action/throwable.rb +24 -4
- data/lib/lotus/action/validatable.rb +128 -0
- data/lib/lotus/controller.rb +11 -25
- data/lib/lotus/controller/configuration.rb +117 -66
- data/lib/lotus/controller/version.rb +1 -1
- data/lib/lotus/http/status.rb +5 -1
- data/lib/rack-patch.rb +8 -2
- data/lotus-controller.gemspec +6 -4
- metadata +54 -21
- data/lib/lotus/controller/dsl.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4f0f02211060856c6f853b05907e1993a23684a2
|
4
|
+
data.tar.gz: 98db51b586aa274b812def351f2960219d26a254
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a9988d2c322dba57028f60615652aa4307ef2808a1a742e7a12c15c70609defb83abec4cd8d0c31eddaf5de49e618f74a4010c97c2c03d95a4544fe459b388a
|
7
|
+
data.tar.gz: 688a5dba74eecf6c8d6a14891c9b4d0d2c8b853864e66fa0beb6f09c0abd3918b18ab2daf957e64ece73a1e72ea86f803b67ffbf6bd3a8bc1e0e433a66767f05
|
data/CHANGELOG.md
CHANGED
@@ -1,127 +1,65 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
56
|
-
They are the
|
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,
|
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__
|
75
|
-
In other words, you have the freedom
|
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,
|
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**
|
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
|
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://
|
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
|
-
|
184
|
-
|
185
|
-
|
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
|
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
|
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
|
-
|
308
|
-
|
407
|
+
module Articles
|
408
|
+
class Show
|
409
|
+
include Lotus::Action
|
309
410
|
|
310
|
-
|
311
|
-
|
312
|
-
|
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 =
|
322
|
-
action.call({id: 'unknown'}) # =>
|
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
|
-
|
451
|
+
Lotus::Controller offers convenient access to cookies.
|
352
452
|
|
353
|
-
They are read as
|
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
|
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
|
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
|
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
|
-
|
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
|
832
|
+
A Controller is nothing more than a logical group of actions: just a Ruby module.
|
631
833
|
|
632
834
|
```ruby
|
633
|
-
|
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
|
-
|
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
|
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
|
707
|
-
certain middleware.
|
708
|
-
|
709
|
-
|
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
|
-
|
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
|
-
|
717
|
-
|
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
|
-
|
733
|
-
|
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
|
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
|
-
#
|
778
|
-
#
|
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
|
-
|
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
|
-
|
976
|
+
include MyAuthentication
|
977
|
+
use SomeMiddleWare
|
978
|
+
|
979
|
+
before { authenticate! }
|
783
980
|
end
|
784
981
|
end
|
785
982
|
```
|
786
983
|
|
787
|
-
All
|
788
|
-
|
789
|
-
|
790
|
-
|
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 =>
|
993
|
+
handle_exception ArgumentError => 400
|
797
994
|
end
|
798
995
|
|
799
|
-
|
800
|
-
|
996
|
+
module Articles
|
997
|
+
class Create
|
998
|
+
include Lotus::Action
|
801
999
|
|
802
|
-
|
803
|
-
|
804
|
-
|
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
|
-
|
814
|
-
|
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
|
-
|
1020
|
+
Users::Create.new.call({}) # => HTTP 400
|
824
1021
|
|
825
|
-
|
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)
|