lotus-controller 0.0.0 → 0.1.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/.gitignore +7 -14
- data/.travis.yml +5 -0
- data/.yardopts +4 -0
- data/Gemfile +13 -2
- data/README.md +627 -6
- data/Rakefile +17 -1
- data/lib/lotus-controller.rb +1 -0
- data/lib/lotus/action.rb +55 -0
- data/lib/lotus/action/callable.rb +93 -0
- data/lib/lotus/action/callbacks.rb +138 -0
- data/lib/lotus/action/cookie_jar.rb +97 -0
- data/lib/lotus/action/cookies.rb +60 -0
- data/lib/lotus/action/exposable.rb +81 -0
- data/lib/lotus/action/mime.rb +190 -0
- data/lib/lotus/action/params.rb +53 -0
- data/lib/lotus/action/rack.rb +97 -0
- data/lib/lotus/action/redirect.rb +34 -0
- data/lib/lotus/action/session.rb +48 -0
- data/lib/lotus/action/throwable.rb +130 -0
- data/lib/lotus/controller.rb +65 -2
- data/lib/lotus/controller/dsl.rb +54 -0
- data/lib/lotus/controller/version.rb +4 -1
- data/lib/lotus/http/status.rb +103 -0
- data/lib/rack-patch.rb +20 -0
- data/lotus-controller.gemspec +16 -11
- data/test/action/callbacks_test.rb +99 -0
- data/test/action/params_test.rb +29 -0
- data/test/action_test.rb +31 -0
- data/test/controller_test.rb +24 -0
- data/test/cookies_test.rb +36 -0
- data/test/fixtures.rb +501 -0
- data/test/integration/mime_type_test.rb +175 -0
- data/test/integration/routing_test.rb +141 -0
- data/test/integration/sessions_test.rb +63 -0
- data/test/redirect_test.rb +20 -0
- data/test/session_test.rb +19 -0
- data/test/test_helper.rb +24 -0
- data/test/throw_test.rb +93 -0
- data/test/version_test.rb +7 -0
- metadata +112 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fac98fda3029902773f0dc45d886942f4e3fae87
|
4
|
+
data.tar.gz: b2a40a8361d84bb0625aaf8d60925952999704ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8f3c1d85c792b625120de32d1210dcd080501a6b041a5ccc3ee9d6788cb44bbe0354162c097f9488acdc25ddd841fa1378e9d24547832ba4b61b83edb4e44251
|
7
|
+
data.tar.gz: 5080fe24a7c4260054730dddf65ebcf10803c45ddd4f95981984971cc81a9e635ef723fcffbc19150226125fc34ce3d9c9003b57ce59fbdd3f9f330b4b8c8104
|
data/.gitignore
CHANGED
@@ -1,17 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
.bundle
|
4
|
-
.config
|
5
|
-
.yardoc
|
1
|
+
.devnotes
|
2
|
+
.greenbar
|
6
3
|
Gemfile.lock
|
7
|
-
|
8
|
-
|
4
|
+
tags
|
5
|
+
.bundle
|
9
6
|
coverage
|
10
|
-
doc/
|
11
|
-
lib/bundler/man
|
12
|
-
pkg
|
13
|
-
rdoc
|
14
|
-
spec/reports
|
15
|
-
test/tmp
|
16
|
-
test/version_tmp
|
17
7
|
tmp
|
8
|
+
doc/
|
9
|
+
.yardoc
|
10
|
+
lotus-controller-0.1.0.gem
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,15 @@
|
|
1
|
-
source '
|
1
|
+
source 'http://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in lotus-controller.gemspec
|
4
3
|
gemspec
|
4
|
+
|
5
|
+
if !ENV['TRAVIS']
|
6
|
+
gem 'debugger', require: false
|
7
|
+
gem 'yard', require: false
|
8
|
+
gem 'lotus-utils', require: false, path: '../lotus-utils'
|
9
|
+
gem 'lotus-router', require: false, path: '../lotus-router'
|
10
|
+
else
|
11
|
+
gem 'lotus-router', require: false
|
12
|
+
end
|
13
|
+
|
14
|
+
gem 'simplecov', require: false
|
15
|
+
gem 'coveralls', require: false
|
data/README.md
CHANGED
@@ -1,29 +1,650 @@
|
|
1
1
|
# Lotus::Controller
|
2
2
|
|
3
|
-
|
3
|
+
A Rack compatible Controller layer for [Lotus](http://lotusrb.org).
|
4
|
+
|
5
|
+
## Status
|
6
|
+
|
7
|
+
[](http://badge.fury.io/rb/lotus-controller)
|
8
|
+
[](http://travis-ci.org/lotus/controller?branch=master)
|
9
|
+
[](https://coveralls.io/r/lotus/controller)
|
10
|
+
[](https://codeclimate.com/github/lotus/controller)
|
11
|
+
[](https://gemnasium.com/lotus/controller)
|
12
|
+
|
13
|
+
## Contact
|
14
|
+
|
15
|
+
* Home page: http://lotusrb.org
|
16
|
+
* Mailing List: http://lotusrb.org/mailing-list
|
17
|
+
* API Doc: http://rdoc.info/gems/lotus-controller
|
18
|
+
* Bugs/Issues: https://github.com/lotus/controller/issues
|
19
|
+
* Support: http://stackoverflow.com/questions/tagged/lotusrb
|
20
|
+
|
21
|
+
## Rubies
|
22
|
+
|
23
|
+
__Lotus::Controller__ supports Ruby (MRI) 2+
|
4
24
|
|
5
25
|
## Installation
|
6
26
|
|
7
27
|
Add this line to your application's Gemfile:
|
8
28
|
|
9
|
-
|
29
|
+
```ruby
|
30
|
+
gem 'lotus-controller'
|
31
|
+
```
|
10
32
|
|
11
33
|
And then execute:
|
12
34
|
|
13
|
-
|
35
|
+
```shell
|
36
|
+
$ bundle
|
37
|
+
```
|
14
38
|
|
15
39
|
Or install it yourself as:
|
16
40
|
|
17
|
-
|
41
|
+
```shell
|
42
|
+
$ gem install lotus-controller
|
43
|
+
```
|
18
44
|
|
19
45
|
## Usage
|
20
46
|
|
21
|
-
|
47
|
+
Lotus::Controller is a thin layer (**275 LOCs**) for MVC web frameworks.
|
48
|
+
It works beautifully with [Lotus::Router](https://github.com/lotus/router), but it can be employed everywhere.
|
49
|
+
It's designed to be fast and testable.
|
50
|
+
|
51
|
+
### Actions
|
52
|
+
|
53
|
+
The core of this frameworks are the actions.
|
54
|
+
They are the endpoint that responds to incoming HTTP requests.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class Show
|
58
|
+
include Lotus::Action
|
59
|
+
|
60
|
+
def call(params)
|
61
|
+
@article = Article.find params[:id]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
The usage of `Lotus::Action` follows the Lotus philosophy: include a module and implement a minimal interface.
|
67
|
+
In this case, it's only one method: `#call(params)`.
|
68
|
+
|
69
|
+
Lotus is designed to not interfere with inheritance.
|
70
|
+
This is important, because you can implement your own initialization strategy.
|
71
|
+
|
72
|
+
__An action is an object__ after all, it's important that __you have the full control on it__.
|
73
|
+
In other words, you have the freedom of instantiate, inject dependencies and test it, both with unit and integration.
|
74
|
+
|
75
|
+
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.
|
76
|
+
__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__.
|
77
|
+
Imagine how **fast** can be a unit test like this.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
class Show
|
81
|
+
include Lotus::Action
|
82
|
+
|
83
|
+
def initialize(repository = Article)
|
84
|
+
@repository = repository
|
85
|
+
end
|
86
|
+
|
87
|
+
def call(params)
|
88
|
+
@article = @repository.find params[:id]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
action = Show.new(MemoryArticleRepository)
|
93
|
+
action.call({ id: 23 })
|
94
|
+
```
|
95
|
+
|
96
|
+
### Params
|
97
|
+
|
98
|
+
The request params are passed as an argument to the `#call` method.
|
99
|
+
If routed with *Lotus::Router*, it extracts the relevant bits from the Rack `env` (eg the requested `:id`).
|
100
|
+
Otherwise everything it's passed as it is: the full Rack `env` in production, and the given `Hash` for unit tests.
|
101
|
+
|
102
|
+
With Lotus::Router:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
class Show
|
106
|
+
include Lotus::Action
|
107
|
+
|
108
|
+
def call(params)
|
109
|
+
# ...
|
110
|
+
puts params # => { id: 23 } extracted from Rack env
|
111
|
+
end
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
Standalone:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
class Show
|
119
|
+
include Lotus::Action
|
120
|
+
|
121
|
+
def call(params)
|
122
|
+
# ...
|
123
|
+
puts params # => { :"rack.version"=>[1, 2], :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
Unit Testing:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
class Show
|
132
|
+
include Lotus::Action
|
133
|
+
|
134
|
+
def call(params)
|
135
|
+
# ...
|
136
|
+
puts params # => { id: 23, key: 'value' } passed as it is from testing
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
action = Show.new
|
141
|
+
response = action.call({ id: 23, key: 'value' })
|
142
|
+
```
|
143
|
+
|
144
|
+
### Response
|
145
|
+
|
146
|
+
The output of `#call` is a serialized Rack::Response (see [#finish](http://rack.rubyforge.org/doc/classes/Rack/Response.html#M000182)):
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
class Show
|
150
|
+
include Lotus::Action
|
151
|
+
|
152
|
+
def call(params)
|
153
|
+
# ...
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
action = Show.new
|
158
|
+
action.call({}) # => [200, {}, [""]]
|
159
|
+
```
|
160
|
+
|
161
|
+
It has private accessors to explicitly set status, headers and body:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
class Show
|
165
|
+
include Lotus::Action
|
166
|
+
|
167
|
+
def call(params)
|
168
|
+
self.status = 201
|
169
|
+
self.body = 'Hi!'
|
170
|
+
self.headers.merge!({ 'X-Custom' => 'OK' })
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
action = Show.new
|
175
|
+
action.call({}) # => [201, { "X-Custom" => "OK" }, ["Hi!"]]
|
176
|
+
```
|
177
|
+
### Exposures
|
178
|
+
|
179
|
+
We know that actions are objects and Lotus::Action respects one of the pillars of OOP: __encapsulation__.
|
180
|
+
Other frameworks extract instance variables (`@ivar`) and make them available to the view context.
|
181
|
+
The solution of Lotus::Action is a simple and powerful DSL: `expose`.
|
182
|
+
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_.
|
183
|
+
Exposures (`#exposures`) is set of exposed attributes, so that the view context can have the information needed to render a page.
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
class Show
|
187
|
+
include Lotus::Action
|
188
|
+
|
189
|
+
expose :article
|
190
|
+
|
191
|
+
def call(params)
|
192
|
+
@article = Article.find params[:id]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
action = Show.new
|
197
|
+
action.call({ id: 23 })
|
198
|
+
|
199
|
+
assert_equal 23, action.article.id
|
200
|
+
|
201
|
+
puts action.exposures # => { article: <Article:0x007f965c1d0318 @id=23> }
|
202
|
+
```
|
203
|
+
|
204
|
+
### Callbacks
|
205
|
+
|
206
|
+
It offers powerful, inheritable callbacks chain which is executed before and/or after your `#call` method invocation:
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
class Show
|
210
|
+
include Lotus::Action
|
211
|
+
|
212
|
+
before :authenticate, :set_article
|
213
|
+
|
214
|
+
def call(params)
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
def authenticate
|
219
|
+
# ...
|
220
|
+
end
|
221
|
+
|
222
|
+
# `params` in the method signature is optional
|
223
|
+
def set_article(params)
|
224
|
+
@article = Article.find params[:id]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
Callbacks can also be expressed as anonymous lambdas:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class Show
|
233
|
+
include Lotus::Action
|
234
|
+
|
235
|
+
before { ... } # do some authentication stuff
|
236
|
+
before {|params| @article = Article.find params[:id] }
|
237
|
+
|
238
|
+
def call(params)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
```
|
242
|
+
|
243
|
+
### Exceptions management
|
244
|
+
|
245
|
+
When an exception is raised, it automatically sets the HTTP status to [500](http://httpstatus.es/500):
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
class Show
|
249
|
+
include Lotus::Action
|
250
|
+
|
251
|
+
def call(params)
|
252
|
+
raise
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
action = Show.new
|
257
|
+
action.call({}) # => [500, {}, ["Internal Server Error"]]
|
258
|
+
```
|
259
|
+
|
260
|
+
You can define how a specific raised exception should be transformed in an HTTP status.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class Show
|
264
|
+
include Lotus::Action
|
265
|
+
handle_exception RecordNotFound, 404
|
266
|
+
|
267
|
+
def call(params)
|
268
|
+
@article = Article.find params[:id]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
action = Show.new
|
273
|
+
action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
|
274
|
+
```
|
275
|
+
|
276
|
+
Exception policies can be defined globally, **before** the controllers/actions
|
277
|
+
are loaded.
|
278
|
+
|
279
|
+
```ruby
|
280
|
+
Lotus::Controller.handled_exceptions = { RecordNotFound => 404 }
|
281
|
+
|
282
|
+
class Show
|
283
|
+
include Lotus::Action
|
284
|
+
|
285
|
+
def call(params)
|
286
|
+
@article = Article.find params[:id]
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
action = Show.new
|
291
|
+
action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
|
292
|
+
```
|
293
|
+
|
294
|
+
### Throwable HTTP statuses
|
295
|
+
|
296
|
+
When [#throw](http://ruby-doc.org/core-2.1.0/Kernel.html#method-i-throw) is used with a valid HTTP code, it stops the execution and sets the proper status and body for the response:
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
class Show
|
300
|
+
include Lotus::Action
|
301
|
+
|
302
|
+
before :authenticate!
|
303
|
+
|
304
|
+
def call(params)
|
305
|
+
# ...
|
306
|
+
end
|
307
|
+
|
308
|
+
private
|
309
|
+
def authenticate!
|
310
|
+
throw 401 unless authenticated?
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
action = Show.new
|
315
|
+
action.call({}) # => [401, {}, ["Unauthorized"]]
|
316
|
+
```
|
317
|
+
|
318
|
+
### Cookies
|
319
|
+
|
320
|
+
It offers convenient access to cookies.
|
321
|
+
|
322
|
+
They are read as an Hash from Rack env:
|
323
|
+
|
324
|
+
```ruby
|
325
|
+
require 'lotus/controller'
|
326
|
+
require 'lotus/action/cookies'
|
327
|
+
|
328
|
+
class ReadCookiesFromRackEnv
|
329
|
+
include Lotus::Action
|
330
|
+
include Lotus::Action::Cookies
|
331
|
+
|
332
|
+
def call(params)
|
333
|
+
# ...
|
334
|
+
cookies[:foo] # => 'bar'
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
action = ReadCookiesFromRackEnv.new
|
339
|
+
action.call({'HTTP_COOKIE' => 'foo=bar'})
|
340
|
+
```
|
341
|
+
|
342
|
+
They are set like an Hash:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
require 'lotus/controller'
|
346
|
+
require 'lotus/action/cookies'
|
347
|
+
|
348
|
+
class SetCookies
|
349
|
+
include Lotus::Action
|
350
|
+
include Lotus::Action::Cookies
|
351
|
+
|
352
|
+
def call(params)
|
353
|
+
# ...
|
354
|
+
cookies[:foo] = 'bar'
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
action = SetCookies.new
|
359
|
+
action.call({}) # => [200, {'Set-Cookie' => 'foo=bar'}, '...']
|
360
|
+
```
|
361
|
+
|
362
|
+
They are removed by setting their value to `nil`:
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
require 'lotus/controller'
|
366
|
+
require 'lotus/action/cookies'
|
367
|
+
|
368
|
+
class RemoveCookies
|
369
|
+
include Lotus::Action
|
370
|
+
include Lotus::Action::Cookies
|
371
|
+
|
372
|
+
def call(params)
|
373
|
+
# ...
|
374
|
+
cookies[:foo] = nil
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
action = SetCookies.new
|
379
|
+
action.call({}) # => [200, {'Set-Cookie' => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"}, '...']
|
380
|
+
```
|
381
|
+
|
382
|
+
### Sessions
|
383
|
+
|
384
|
+
It has builtin support for Rack sessions:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
require 'lotus/controller'
|
388
|
+
require 'lotus/action/session'
|
389
|
+
|
390
|
+
class ReadSessionFromRackEnv
|
391
|
+
include Lotus::Action
|
392
|
+
include Lotus::Action::Session
|
393
|
+
|
394
|
+
def call(params)
|
395
|
+
# ...
|
396
|
+
session[:age] # => '31'
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
action = ReadSessionFromRackEnv.new
|
401
|
+
action.call({ 'rack.session' => { 'age' => '31' }})
|
402
|
+
```
|
403
|
+
|
404
|
+
Values can be set like an Hash:
|
405
|
+
|
406
|
+
```ruby
|
407
|
+
require 'lotus/controller'
|
408
|
+
require 'lotus/action/session'
|
409
|
+
|
410
|
+
class SetSession
|
411
|
+
include Lotus::Action
|
412
|
+
include Lotus::Action::Session
|
413
|
+
|
414
|
+
def call(params)
|
415
|
+
# ...
|
416
|
+
session[:age] = 31
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
action = SetSession.new
|
421
|
+
action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."]
|
422
|
+
```
|
423
|
+
|
424
|
+
Values can be removed like an Hash:
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
require 'lotus/controller'
|
428
|
+
require 'lotus/action/session'
|
429
|
+
|
430
|
+
class RemoveSession
|
431
|
+
include Lotus::Action
|
432
|
+
include Lotus::Action::Session
|
433
|
+
|
434
|
+
def call(params)
|
435
|
+
# ...
|
436
|
+
session[:age] = nil
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
action = RemoveSession.new
|
441
|
+
action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."] it removes that value from the session
|
442
|
+
```
|
443
|
+
|
444
|
+
While Lotus::Controller supports sessions natively, it's __session store agnostic__.
|
445
|
+
You have to specify the session store in your Rack middleware configuration (eg `config.ru`).
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
use Rack::Session::Cookie, secret: SecureRandom.hex(64)
|
449
|
+
run Show.new
|
450
|
+
```
|
451
|
+
|
452
|
+
### Redirect
|
453
|
+
|
454
|
+
If you need to redirect the client to another resource, use `#redirect_to`:
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
class Create
|
458
|
+
include Lotus::Action
|
459
|
+
|
460
|
+
def call(params)
|
461
|
+
# ...
|
462
|
+
redirect_to 'http://example.com/articles/23'
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
action = Create.new
|
467
|
+
action.call({ article: { title: 'Hello' }}) # => [302, {'Location' => '/articles/23'}, '']
|
468
|
+
```
|
469
|
+
|
470
|
+
### Mime types
|
471
|
+
|
472
|
+
Lotus::Action automatically sets the mime type, according to the request headers.
|
473
|
+
However, you can override this value:
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
class Show
|
477
|
+
include Lotus::Action
|
478
|
+
|
479
|
+
def call(params)
|
480
|
+
# ...
|
481
|
+
self.content_type = 'application/json'
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
action = Show.new
|
486
|
+
action.call({ id: 23 }) # => [200, {'Content-Type' => 'application/json'}, '...']
|
487
|
+
```
|
488
|
+
|
489
|
+
You can restrict the accepted mime types:
|
490
|
+
|
491
|
+
```ruby
|
492
|
+
class Show
|
493
|
+
include Lotus::Action
|
494
|
+
accept :html, :json
|
495
|
+
|
496
|
+
def call(params)
|
497
|
+
# ...
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# When called with "\*/\*" => 200
|
502
|
+
# When called with "text/html" => 200
|
503
|
+
# When called with "application/json" => 200
|
504
|
+
# When called with "application/xml" => 406
|
505
|
+
```
|
506
|
+
|
507
|
+
You can check if the requested mime type is accepted by the client.
|
508
|
+
|
509
|
+
```ruby
|
510
|
+
class Show
|
511
|
+
include Lotus::Action
|
512
|
+
|
513
|
+
def call(params)
|
514
|
+
# ...
|
515
|
+
# @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'
|
516
|
+
|
517
|
+
accept?('text/html') # => true
|
518
|
+
accept?('application/xml') # => true
|
519
|
+
accept?('application/json') # => false
|
520
|
+
|
521
|
+
|
522
|
+
|
523
|
+
# @_env['HTTP_ACCEPT'] # => '*/*'
|
524
|
+
|
525
|
+
accept?('text/html') # => true
|
526
|
+
accept?('application/xml') # => true
|
527
|
+
accept?('application/json') # => true
|
528
|
+
end
|
529
|
+
end
|
530
|
+
```
|
531
|
+
|
532
|
+
### No rendering, please
|
533
|
+
|
534
|
+
Lotus::Controller is designed to be a pure HTTP endpoint, rendering belongs to other layers of MVC.
|
535
|
+
You can set the body directly (see [response](#response)), or use [Lotus::View](https://github.com/lotus/view).
|
536
|
+
|
537
|
+
### Controllers
|
538
|
+
|
539
|
+
A Controller is nothing more than a logical group for actions.
|
540
|
+
|
541
|
+
```ruby
|
542
|
+
class ArticlesController
|
543
|
+
class Index
|
544
|
+
include Lotus::Action
|
545
|
+
|
546
|
+
# ...
|
547
|
+
end
|
548
|
+
|
549
|
+
class Show
|
550
|
+
include Lotus::Action
|
551
|
+
|
552
|
+
# ...
|
553
|
+
end
|
554
|
+
end
|
555
|
+
```
|
556
|
+
|
557
|
+
Which is a bit verboses. Instead, just do:
|
558
|
+
|
559
|
+
```ruby
|
560
|
+
class ArticlesController
|
561
|
+
include Lotus::Controller
|
562
|
+
|
563
|
+
action 'Index' do
|
564
|
+
# ...
|
565
|
+
end
|
566
|
+
|
567
|
+
action 'Show' do
|
568
|
+
# ...
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
ArticlesController::Index.new.call({})
|
573
|
+
```
|
574
|
+
|
575
|
+
## Lotus::Router integration
|
576
|
+
|
577
|
+
While Lotus::Router works great with this framework, Lotus::Controller doesn't depend from it.
|
578
|
+
You, as developer, are free to choose your own routing system.
|
579
|
+
|
580
|
+
But, if you use them together, the **only constraint is that an action must support _arity 0_ in its constructor**.
|
581
|
+
The following examples are valid constructors:
|
582
|
+
|
583
|
+
```ruby
|
584
|
+
def initialize
|
585
|
+
end
|
586
|
+
|
587
|
+
def initialize(repository = Article)
|
588
|
+
end
|
589
|
+
|
590
|
+
def initialize(repository: Article)
|
591
|
+
end
|
592
|
+
|
593
|
+
def initialize(options = {})
|
594
|
+
end
|
595
|
+
|
596
|
+
def initialize(*args)
|
597
|
+
end
|
598
|
+
```
|
599
|
+
|
600
|
+
__Please note that this is subject to change: we're working to remove this constraint.__
|
601
|
+
|
602
|
+
Lotus::Router supports lazy loading for controllers. While this policy can be a
|
603
|
+
convenient fallback, you should know that it's the slower option. **Be sure of
|
604
|
+
loading your controllers before you initialize the router.**
|
605
|
+
|
606
|
+
|
607
|
+
## Rack integration
|
608
|
+
|
609
|
+
Lotus::Controller is compatible with Rack. However, it doesn't mount any middleware.
|
610
|
+
While a Lotus application's architecture is more web oriented, this framework is designed to build pure HTTP entpoints.
|
611
|
+
|
612
|
+
## Thread safety
|
613
|
+
|
614
|
+
An Action is **mutable**. When used without Lotus::Router, be sure to instantiate an
|
615
|
+
action for each request.
|
616
|
+
|
617
|
+
```ruby
|
618
|
+
# config.ru
|
619
|
+
require 'lotus/controller'
|
620
|
+
|
621
|
+
class Action
|
622
|
+
include Lotus::Action
|
623
|
+
|
624
|
+
def self.call(env)
|
625
|
+
new.call(env)
|
626
|
+
end
|
627
|
+
|
628
|
+
def call(params)
|
629
|
+
self.body = object_id.to_s
|
630
|
+
end
|
631
|
+
end
|
632
|
+
|
633
|
+
run Action
|
634
|
+
```
|
635
|
+
|
636
|
+
## Versioning
|
637
|
+
|
638
|
+
__Lotus::Controller__ uses [Semantic Versioning 2.0.0](http://semver.org)
|
22
639
|
|
23
640
|
## Contributing
|
24
641
|
|
25
|
-
1. Fork it
|
642
|
+
1. Fork it
|
26
643
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
644
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
645
|
4. Push to the branch (`git push origin my-new-feature`)
|
29
646
|
5. Create new Pull Request
|
647
|
+
|
648
|
+
## Copyright
|
649
|
+
|
650
|
+
Copyright 2014 Luca Guidi – Released under MIT License
|