hanami-controller 1.3.3 → 2.0.0.alpha4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +299 -537
  5. data/hanami-controller.gemspec +4 -3
  6. data/lib/hanami/action/application_action.rb +131 -0
  7. data/lib/hanami/action/application_configuration/content_security_policy.rb +118 -0
  8. data/lib/hanami/action/application_configuration/cookies.rb +29 -0
  9. data/lib/hanami/action/application_configuration/sessions.rb +46 -0
  10. data/lib/hanami/action/application_configuration.rb +90 -0
  11. data/lib/hanami/action/base_params.rb +2 -2
  12. data/lib/hanami/action/cache/cache_control.rb +4 -4
  13. data/lib/hanami/action/cache/conditional_get.rb +3 -1
  14. data/lib/hanami/action/cache/directives.rb +1 -1
  15. data/lib/hanami/action/cache/expires.rb +3 -3
  16. data/lib/hanami/action/cache.rb +1 -139
  17. data/lib/hanami/action/configuration.rb +428 -0
  18. data/lib/hanami/action/cookie_jar.rb +3 -3
  19. data/lib/hanami/action/cookies.rb +3 -62
  20. data/lib/hanami/action/csrf_protection.rb +214 -0
  21. data/lib/hanami/action/flash.rb +102 -207
  22. data/lib/hanami/action/glue.rb +5 -31
  23. data/lib/hanami/action/halt.rb +12 -0
  24. data/lib/hanami/action/mime.rb +78 -485
  25. data/lib/hanami/action/params.rb +2 -2
  26. data/lib/hanami/action/rack/file.rb +1 -1
  27. data/lib/hanami/action/request.rb +30 -20
  28. data/lib/hanami/action/response.rb +193 -0
  29. data/lib/hanami/action/session.rb +11 -128
  30. data/lib/hanami/action/standalone_action.rb +578 -0
  31. data/lib/hanami/action/validatable.rb +1 -1
  32. data/lib/hanami/action/view_name_inferrer.rb +46 -0
  33. data/lib/hanami/action.rb +129 -73
  34. data/lib/hanami/controller/version.rb +1 -1
  35. data/lib/hanami/controller.rb +0 -227
  36. data/lib/hanami/http/status.rb +2 -2
  37. metadata +44 -26
  38. data/lib/hanami/action/callable.rb +0 -92
  39. data/lib/hanami/action/callbacks.rb +0 -214
  40. data/lib/hanami/action/configurable.rb +0 -50
  41. data/lib/hanami/action/exposable/guard.rb +0 -104
  42. data/lib/hanami/action/exposable.rb +0 -126
  43. data/lib/hanami/action/head.rb +0 -121
  44. data/lib/hanami/action/rack/callable.rb +0 -47
  45. data/lib/hanami/action/rack/errors.rb +0 -53
  46. data/lib/hanami/action/rack.rb +0 -411
  47. data/lib/hanami/action/redirect.rb +0 -59
  48. data/lib/hanami/action/throwable.rb +0 -169
  49. data/lib/hanami/controller/configuration.rb +0 -763
  50. data/lib/hanami-controller.rb +0 -1
data/README.md CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  Complete, fast and testable actions for Rack and [Hanami](http://hanamirb.org)
4
4
 
5
+ ## Version
6
+
7
+ **This branch contains the code for `hanami-controller` 2.x.**
8
+
5
9
  ## Status
6
10
 
7
11
  [![Gem Version](https://badge.fury.io/rb/hanami-controller.svg)](https://badge.fury.io/rb/hanami-controller)
8
- [![Build Status](https://ci.hanamirb.org/api/badges/hanami/controller/status.svg)](https://ci.hanamirb.org/hanami/controller)
9
- [![CircleCI](https://circleci.com/gh/hanami/controller/tree/master.svg?style=svg)](https://circleci.com/gh/hanami/controller/tree/master)
10
- [![Test Coverage](https://codecov.io/gh/hanami/controller/branch/master/graph/badge.svg)](https://codecov.io/gh/hanami/controller)
12
+ [![CI](https://github.com/hanami/controller/workflows/ci/badge.svg?branch=main)](https://github.com/hanami/controller/actions?query=workflow%3Aci+branch%3Amain)
13
+ [![Test Coverage](https://codecov.io/gh/hanami/controller/branch/main/graph/badge.svg)](https://codecov.io/gh/hanami/controller)
11
14
  [![Depfu](https://badges.depfu.com/badges/7cd17419fba78b726be1353118fb01de/overview.svg)](https://depfu.com/github/hanami/controller?project=Bundler)
12
15
  [![Inline Docs](http://inch-ci.org/github/hanami/controller.svg)](http://inch-ci.org/github/hanami/controller)
13
16
 
@@ -23,14 +26,14 @@ Complete, fast and testable actions for Rack and [Hanami](http://hanamirb.org)
23
26
 
24
27
  ## Rubies
25
28
 
26
- __Hanami::Controller__ supports Ruby (MRI) 2.3+ and JRuby 9.1.5.0+
29
+ __Hanami::Controller__ supports Ruby (MRI) 2.6+
27
30
 
28
31
  ## Installation
29
32
 
30
33
  Add this line to your application's Gemfile:
31
34
 
32
35
  ```ruby
33
- gem 'hanami-controller'
36
+ gem "hanami/controller"
34
37
  ```
35
38
 
36
39
  And then execute:
@@ -57,22 +60,19 @@ The core of this framework are the actions.
57
60
  They are the endpoints that respond to incoming HTTP requests.
58
61
 
59
62
  ```ruby
60
- class Show
61
- include Hanami::Action
62
-
63
- def call(params)
64
- @article = ArticleRepository.new.find(params[:id])
63
+ class Show < Hanami::Action
64
+ def handle(req, res)
65
+ res[:article] = ArticleRepository.new.find(req.params[:id])
65
66
  end
66
67
  end
67
68
  ```
68
69
 
69
- The usage of `Hanami::Action` follows the Hanami philosophy: include a module and implement a minimal interface.
70
- In this case, the interface is one method: `#call(params)`.
70
+ `Hanami::Action` follows the Hanami philosophy: a single purpose object with a minimal interface.
71
71
 
72
- Hanami is designed to not interfere with inheritance.
73
- This is important, because you can implement your own initialization strategy.
72
+ In this case, `Hanami::Action` provides the key public interface of `#call(env)`, making your actions Rack-compatible.
73
+ To provide custom behaviour when your actions are being called, you can implement `#handle(req, res)`
74
74
 
75
- __An action is an object__. That's important because __you have the full control on it__.
75
+ **An action is an object** and **you have full control over it**.
76
76
  In other words, you have the freedom to instantiate, inject dependencies and test it, both at the unit and integration level.
77
77
 
78
78
  In the example below, the default repository is `ArticleRepository`. During a unit test we can inject a stubbed version, and invoke `#call` with the params.
@@ -80,37 +80,39 @@ __We're avoiding HTTP calls__, we're also going to avoid hitting the database (i
80
80
  Imagine how **fast** the unit test could be.
81
81
 
82
82
  ```ruby
83
- class Show
84
- include Hanami::Action
85
-
86
- def initialize(repository = ArticleRepository.new)
83
+ class Show < Hanami::Action
84
+ def initialize(configuration:, repository: ArticleRepository.new)
87
85
  @repository = repository
86
+ super(configuration: configuration)
88
87
  end
89
88
 
90
- def call(params)
91
- @article = @repository.find(params[:id])
89
+ def handle(req, res)
90
+ res[:article] = repository.find(req.params[:id])
92
91
  end
92
+
93
+ private
94
+
95
+ attr_reader :repository
93
96
  end
94
97
 
95
- action = Show.new(MemoryArticleRepository.new)
96
- action.call({ id: 23 })
98
+ configuration = Hanami::Controller::Configuration.new
99
+ action = Show.new(configuration: configuration, repository: ArticleRepository.new)
100
+ action.call(id: 23)
97
101
  ```
98
102
 
99
103
  ### Params
100
104
 
101
- The request params are passed as an argument to the `#call` method.
105
+ The request params are part of the request passed as an argument to the `#handle` method.
102
106
  If routed with *Hanami::Router*, it extracts the relevant bits from the Rack `env` (eg the requested `:id`).
103
107
  Otherwise everything is passed as is: the full Rack `env` in production, and the given `Hash` for unit tests.
104
108
 
105
- With Hanami::Router:
109
+ With `Hanami::Router`:
106
110
 
107
111
  ```ruby
108
- class Show
109
- include Hanami::Action
110
-
111
- def call(params)
112
+ class Show < Hanami::Action
113
+ def handle(req, *)
112
114
  # ...
113
- puts params # => { id: 23 } extracted from Rack env
115
+ puts req.params # => { id: 23 } extracted from Rack env
114
116
  end
115
117
  end
116
118
  ```
@@ -118,12 +120,10 @@ end
118
120
  Standalone:
119
121
 
120
122
  ```ruby
121
- class Show
122
- include Hanami::Action
123
-
124
- def call(params)
123
+ class Show < Hanami::Action
124
+ def handle(req, *)
125
125
  # ...
126
- puts params # => { :"rack.version"=>[1, 2], :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
126
+ puts req.params # => { :"rack.version"=>[1, 2], :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
127
127
  end
128
128
  end
129
129
  ```
@@ -131,17 +131,15 @@ end
131
131
  Unit Testing:
132
132
 
133
133
  ```ruby
134
- class Show
135
- include Hanami::Action
136
-
137
- def call(params)
134
+ class Show < Hanami::Action
135
+ def handle(req, *)
138
136
  # ...
139
- puts params # => { id: 23, key: 'value' } passed as it is from testing
137
+ puts req.params # => { id: 23, key: "value" } passed as it is from testing
140
138
  end
141
139
  end
142
140
 
143
- action = Show.new
144
- response = action.call({ id: 23, key: 'value' })
141
+ action = Show.new(configuration: configuration)
142
+ response = action.call(id: 23, key: "value")
145
143
  ```
146
144
 
147
145
  #### Whitelisting
@@ -150,12 +148,10 @@ Params represent an untrusted input.
150
148
  For security reasons it's recommended to whitelist them.
151
149
 
152
150
  ```ruby
153
- require 'hanami/validations'
154
- require 'hanami/controller'
155
-
156
- class Signup
157
- include Hanami::Action
151
+ require "hanami/validations"
152
+ require "hanami/controller"
158
153
 
154
+ class Signup < Hanami::Action
159
155
  params do
160
156
  required(:first_name).filled(:str?)
161
157
  required(:last_name).filled(:str?)
@@ -168,18 +164,18 @@ class Signup
168
164
  end
169
165
  end
170
166
 
171
- def call(params)
167
+ def handle(req, *)
172
168
  # Describe inheritance hierarchy
173
- puts params.class # => Signup::Params
174
- puts params.class.superclass # => Hanami::Action::Params
169
+ puts req.params.class # => Signup::Params
170
+ puts req.params.class.superclass # => Hanami::Action::Params
175
171
 
176
172
  # Whitelist :first_name, but not :admin
177
- puts params[:first_name] # => "Luca"
178
- puts params[:admin] # => nil
173
+ puts req.params[:first_name] # => "Luca"
174
+ puts req.params[:admin] # => nil
179
175
 
180
176
  # Whitelist nested params [:address][:line_one], not [:address][:line_two]
181
- puts params[:address][:line_one] # => '69 Tender St'
182
- puts params[:address][:line_two] # => nil
177
+ puts req.params[:address][:line_one] # => "69 Tender St"
178
+ puts req.params[:address][:line_two] # => nil
183
179
  end
184
180
  end
185
181
  ```
@@ -193,12 +189,11 @@ when params are invalid.
193
189
  If you specify the `:type` option, the param will be coerced.
194
190
 
195
191
  ```ruby
196
- require 'hanami/validations'
197
- require 'hanami/controller'
192
+ require "hanami/validations"
193
+ require "hanami/controller"
198
194
 
199
- class Signup
195
+ class Signup < Hanami::Action
200
196
  MEGABYTE = 1024 ** 2
201
- include Hanami::Action
202
197
 
203
198
  params do
204
199
  required(:first_name).filled(:str?)
@@ -210,51 +205,33 @@ class Signup
210
205
  optional(:avatar).filled(size?: 1..(MEGABYTE * 3))
211
206
  end
212
207
 
213
- def call(params)
214
- halt 400 unless params.valid?
208
+ def handle(req, *)
209
+ halt 400 unless req.params.valid?
215
210
  # ...
216
211
  end
217
212
  end
218
-
219
- action = Signup.new
220
-
221
- action.call(valid_params) # => [200, {}, ...]
222
- action.errors.empty? # => true
223
-
224
- action.call(invalid_params) # => [400, {}, ...]
225
- action.errors.empty? # => false
226
-
227
- action.errors.fetch(:email)
228
- # => ['is missing', 'is in invalid format']
229
213
  ```
230
214
 
231
215
  ### Response
232
216
 
233
- The output of `#call` is a serialized Rack::Response (see [#finish](http://rubydoc.info/github/rack/rack/master/Rack/Response#finish-instance_method)):
217
+ The output of `#call` is a `Hanami::Action::Response`:
234
218
 
235
219
  ```ruby
236
- class Show
237
- include Hanami::Action
238
-
239
- def call(params)
240
- # ...
241
- end
220
+ class Show < Hanami::Action
242
221
  end
243
222
 
244
- action = Show.new
245
- action.call({}) # => [200, {}, [""]]
223
+ action = Show.new(configuration: configuration)
224
+ action.call({}) # => #<Hanami::Action::Response:0x00007fe8be968418 @status=200 ...>
246
225
  ```
247
226
 
248
- It has private accessors to explicitly set status, headers, and body:
227
+ This is the same `res` response object passed to `#handle`, where you can use its accessors to explicitly set status, headers, and body:
249
228
 
250
229
  ```ruby
251
- class Show
252
- include Hanami::Action
253
-
254
- def call(params)
255
- self.status = 201
256
- self.body = 'Hi!'
257
- self.headers.merge!({ 'X-Custom' => 'OK' })
230
+ class Show < Hanami::Action
231
+ def handle(*, res)
232
+ res.status = 201
233
+ res.body = "Hi!"
234
+ res.headers.merge!("X-Custom" => "OK")
258
235
  end
259
236
  end
260
237
 
@@ -264,58 +241,47 @@ action.call({}) # => [201, { "X-Custom" => "OK" }, ["Hi!"]]
264
241
 
265
242
  ### Exposures
266
243
 
267
- We know that actions are objects and `Hanami::Action` respects one of the pillars of OOP: __encapsulation__.
268
- Other frameworks extract instance variables (`@ivar`) and make them available to the view context.
269
-
270
- `Hanami::Action`'s solution is the simple and powerful DSL: `expose`.
271
- It's a thin layer on top of `attr_reader`.
272
-
273
- Using `expose` creates a getter for the given attribute, and adds it to the _exposures_.
274
- Exposures (`#exposures`) are a set of attributes exposed to the view.
275
- That is to say the variables necessary for rendering a view.
276
-
277
- By default, all `Hanami::Action` objects expose `#params` and `#errors`.
244
+ In case you need to send data from the action to other layers of your application, you can use exposures.
245
+ By default, an action exposes the received params.
278
246
 
279
247
  ```ruby
280
- class Show
281
- include Hanami::Action
282
-
283
- expose :article
284
-
285
- def call(params)
286
- @article = ArticleRepository.new.find(params[:id])
248
+ class Show < Hanami::Action
249
+ def handle(req, res)
250
+ res[:article] = ArticleRepository.new.find(req.params[:id])
287
251
  end
288
252
  end
289
253
 
290
- action = Show.new
291
- action.call({ id: 23 })
254
+ action = Show.new(configuration: configuration)
255
+ response = action.call(id: 23)
292
256
 
293
- assert_equal 23, action.article.id
257
+ article = response[:article]
258
+ article.class # => Article
259
+ article.id # => 23
294
260
 
295
- puts action.exposures # => { article: <Article:0x007f965c1d0318 @id=23> }
261
+ response.exposures.keys # => [:params, :article]
296
262
  ```
297
263
 
298
264
  ### Callbacks
299
265
 
300
- It offers a powerful, inheritable callback chain which is executed before and/or after your `#call` method invocation:
266
+ If you need to execute logic **before** or **after** `#handle` is invoked, you can use _callbacks_.
267
+ They are useful for shared logic like authentication checks.
301
268
 
302
269
  ```ruby
303
- class Show
304
- include Hanami::Action
305
-
270
+ class Show < Hanami::Action
306
271
  before :authenticate, :set_article
307
272
 
308
- def call(params)
273
+ def handle(*)
309
274
  end
310
275
 
311
276
  private
277
+
312
278
  def authenticate
313
279
  # ...
314
280
  end
315
281
 
316
- # `params` in the method signature is optional
317
- def set_article(params)
318
- @article = ArticleRepository.new.find(params[:id])
282
+ # `req` and `res` in the method signature is optional
283
+ def set_article(req, res)
284
+ res[:article] = ArticleRepository.new.find(req.params[:id])
319
285
  end
320
286
  end
321
287
  ```
@@ -323,116 +289,87 @@ end
323
289
  Callbacks can also be expressed as anonymous lambdas:
324
290
 
325
291
  ```ruby
326
- class Show
327
- include Hanami::Action
328
-
292
+ class Show < Hanami::Action
329
293
  before { ... } # do some authentication stuff
330
- before { |params| @article = ArticleRepository.new.find(params[:id]) }
294
+ before { |req, res| res[:article] = ArticleRepository.new.find(req.params[:id]) }
331
295
 
332
- def call(params)
296
+ def handle(*)
333
297
  end
334
298
  end
335
299
  ```
336
300
 
337
301
  ### Exceptions management
338
302
 
339
- When an exception is raised, it automatically sets the HTTP status to [500](http://httpstatus.es/500):
303
+ When the app raises an exception, `hanami-controller`, does **NOT** manage it.
304
+ You can write custom exception handling on per action or configuration basis.
305
+
306
+ An exception handler can be a valid HTTP status code (eg. `500`, `401`), or a `Symbol` that represents an action method.
340
307
 
341
308
  ```ruby
342
- class Show
343
- include Hanami::Action
309
+ class Show < Hanami::Action
310
+ handle_exception StandardError => 500
344
311
 
345
- def call(params)
312
+ def handle(*)
346
313
  raise
347
314
  end
348
315
  end
349
316
 
350
- action = Show.new
317
+ action = Show.new(configuration: configuration)
351
318
  action.call({}) # => [500, {}, ["Internal Server Error"]]
352
319
  ```
353
320
 
354
321
  You can map a specific raised exception to a different HTTP status.
355
322
 
356
323
  ```ruby
357
- class Show
358
- include Hanami::Action
324
+ class Show < Hanami::Action
359
325
  handle_exception RecordNotFound => 404
360
326
 
361
- def call(params)
362
- @article = ArticleRepository.new.find(params[:id])
327
+ def handle(*)
328
+ raise RecordNotFound
363
329
  end
364
330
  end
365
331
 
366
- action = Show.new
367
- action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
332
+ action = Show.new(configuration: configuration)
333
+ action.call({}) # => [404, {}, ["Not Found"]]
368
334
  ```
369
335
 
370
336
  You can also define custom handlers for exceptions.
371
337
 
372
338
  ```ruby
373
- class Create
374
- include Hanami::Action
339
+ class Create < Hanami::Action
375
340
  handle_exception ArgumentError => :my_custom_handler
376
341
 
377
- def call(params)
342
+ gle(*)
378
343
  raise ArgumentError.new("Invalid arguments")
379
344
  end
380
345
 
381
346
  private
382
- def my_custom_handler(exception)
383
- status 400, exception.message
384
- end
385
- end
386
-
387
- action = Create.new
388
- action.call({}) # => [400, {}, ["Invalid arguments"]]
389
- ```
390
-
391
- Exception policies can be defined globally, **before** the controllers/actions
392
- are loaded.
393
347
 
394
- ```ruby
395
- Hanami::Controller.configure do
396
- handle_exception RecordNotFound => 404
397
- end
398
-
399
- class Show
400
- include Hanami::Action
401
-
402
- def call(params)
403
- @article = ArticleRepository.new.find(params[:id])
348
+ def my_custom_handler(req, res, exception)
349
+ res.status = 400
350
+ res.body = exception.message
404
351
  end
405
352
  end
406
353
 
407
- action = Show.new
408
- action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
354
+ action = Create.new(configuration: configuration)
355
+ action.call({}) # => [400, {}, ["Invalid arguments"]]
409
356
  ```
410
357
 
411
- This feature can be turned off globally, in a controller or in a single action.
358
+ Exception policies can be defined globally via configuration:
412
359
 
413
360
  ```ruby
414
- Hanami::Controller.configure do
415
- handle_exceptions false
361
+ configuration = Hanami::Controller::Configuration.new do |config|
362
+ config.handle_exception RecordNotFound => 404
416
363
  end
417
364
 
418
- # or
419
-
420
- module Articles
421
- class Show
422
- include Hanami::Action
423
-
424
- configure do
425
- handle_exceptions false
426
- end
427
-
428
- def call(params)
429
- @article = ArticleRepository.new.find(params[:id])
430
- end
365
+ class Show < Hanami::Action
366
+ def handle(*)
367
+ raise RecordNotFound
431
368
  end
432
369
  end
433
370
 
434
- action = Articles::Show.new
435
- action.call({id: 'unknown'}) # => raises RecordNotFound
371
+ action = Show.new(configuration: configuration)
372
+ action.call({}) # => [404, {}, ["Not Found"]]
436
373
  ```
437
374
 
438
375
  #### Inherited Exceptions
@@ -442,34 +379,30 @@ class MyCustomException < StandardError
442
379
  end
443
380
 
444
381
  module Articles
445
- class Index
446
- include Hanami::Action
447
-
382
+ class Index < Hanami::Action
448
383
  handle_exception MyCustomException => :handle_my_exception
449
384
 
450
- def call(params)
385
+ def handle(*)
451
386
  raise MyCustomException
452
387
  end
453
388
 
454
389
  private
455
390
 
456
- def handle_my_exception
391
+ def handle_my_exception(req, res, exception)
457
392
  # ...
458
393
  end
459
394
  end
460
395
 
461
- class Show
462
- include Hanami::Action
463
-
396
+ class Show < Hanami::Action
464
397
  handle_exception StandardError => :handle_standard_error
465
398
 
466
- def call(params)
399
+ def handle(*)
467
400
  raise MyCustomException
468
401
  end
469
402
 
470
403
  private
471
404
 
472
- def handle_standard_error
405
+ def handle_standard_error(req, res, exception)
473
406
  # ...
474
407
  end
475
408
  end
@@ -485,220 +418,212 @@ Articles::Show.new.call({}) # => `handle_standard_error` will be invoked,
485
418
  When `#halt` is used with a valid HTTP code, it stops the execution and sets the proper status and body for the response:
486
419
 
487
420
  ```ruby
488
- class Show
489
- include Hanami::Action
490
-
421
+ class Show < Hanami::Action
491
422
  before :authenticate!
492
423
 
493
- def call(params)
424
+ def handle(*)
494
425
  # ...
495
426
  end
496
427
 
497
428
  private
429
+
498
430
  def authenticate!
499
431
  halt 401 unless authenticated?
500
432
  end
501
433
  end
502
434
 
503
- action = Show.new
435
+ action = Show.new(configuration: configuration)
504
436
  action.call({}) # => [401, {}, ["Unauthorized"]]
505
437
  ```
506
438
 
507
439
  Alternatively, you can specify a custom message.
508
440
 
509
441
  ```ruby
510
- class Show
511
- include Hanami::Action
512
-
513
- def call(params)
514
- DroidRepository.new.find(params[:id]) or not_found
442
+ class Show < Hanami::Action
443
+ def handle(req, res)
444
+ res[:droid] = DroidRepository.new.find(req.params[:id]) or not_found
515
445
  end
516
446
 
517
447
  private
448
+
518
449
  def not_found
519
450
  halt 404, "This is not the droid you're looking for"
520
451
  end
521
452
  end
522
453
 
523
- action = Show.new
454
+ action = Show.new(configuration: configuration)
524
455
  action.call({}) # => [404, {}, ["This is not the droid you're looking for"]]
525
456
  ```
526
457
 
527
458
  ### Cookies
528
459
 
529
- Hanami::Controller offers convenient access to cookies.
460
+ You can read the original cookies sent from the HTTP client via `req.cookies`.
461
+ If you want to send cookies in the response, use `res.cookies`.
530
462
 
531
463
  They are read as a Hash from Rack env:
532
464
 
533
465
  ```ruby
534
- require 'hanami/controller'
535
- require 'hanami/action/cookies'
466
+ require "hanami/controller"
467
+ require "hanami/action/cookies"
536
468
 
537
- class ReadCookiesFromRackEnv
538
- include Hanami::Action
469
+ class ReadCookiesFromRackEnv < Hanami::Action
539
470
  include Hanami::Action::Cookies
540
471
 
541
- def call(params)
472
+ def handle(req, *)
542
473
  # ...
543
- cookies[:foo] # => 'bar'
474
+ req.cookies[:foo] # => "bar"
544
475
  end
545
476
  end
546
477
 
547
- action = ReadCookiesFromRackEnv.new
548
- action.call({'HTTP_COOKIE' => 'foo=bar'})
478
+ action = ReadCookiesFromRackEnv.new(configuration: configuration)
479
+ action.call({"HTTP_COOKIE" => "foo=bar"})
549
480
  ```
550
481
 
551
482
  They are set like a Hash:
552
483
 
553
484
  ```ruby
554
- require 'hanami/controller'
555
- require 'hanami/action/cookies'
485
+ require "hanami/controller"
486
+ require "hanami/action/cookies"
556
487
 
557
- class SetCookies
558
- include Hanami::Action
488
+ class SetCookies < Hanami::Action
559
489
  include Hanami::Action::Cookies
560
490
 
561
- def call(params)
491
+ def handle(*, res)
562
492
  # ...
563
- cookies[:foo] = 'bar'
493
+ res.cookies[:foo] = "bar"
564
494
  end
565
495
  end
566
496
 
567
- action = SetCookies.new
568
- action.call({}) # => [200, {'Set-Cookie' => 'foo=bar'}, '...']
497
+ action = SetCookies.new(configuration: configuration)
498
+ action.call({}) # => [200, {"Set-Cookie" => "foo=bar"}, "..."]
569
499
  ```
570
500
 
571
501
  They are removed by setting their value to `nil`:
572
502
 
573
503
  ```ruby
574
- require 'hanami/controller'
575
- require 'hanami/action/cookies'
504
+ require "hanami/controller"
505
+ require "hanami/action/cookies"
576
506
 
577
- class RemoveCookies
578
- include Hanami::Action
507
+ class RemoveCookies < Hanami::Action
579
508
  include Hanami::Action::Cookies
580
509
 
581
- def call(params)
510
+ def handle(*, res)
582
511
  # ...
583
- cookies[:foo] = nil
512
+ res.cookies[:foo] = nil
584
513
  end
585
514
  end
586
515
 
587
- action = RemoveCookies.new
588
- action.call({}) # => [200, {'Set-Cookie' => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"}, '...']
516
+ action = RemoveCookies.new(configuration: configuration)
517
+ action.call({}) # => [200, {"Set-Cookie" => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"}, "..."]
589
518
  ```
590
519
 
591
520
  Default values can be set in configuration, but overridden case by case.
592
521
 
593
522
  ```ruby
594
- require 'hanami/controller'
595
- require 'hanami/action/cookies'
523
+ require "hanami/controller"
524
+ require "hanami/action/cookies"
596
525
 
597
- Hanami::Controller.configure do
598
- cookies max_age: 300 # 5 minutes
526
+ configuration = Hanami::Controller::Configuration.new do |config|
527
+ config.cookies(max_age: 300) # 5 minutes
599
528
  end
600
529
 
601
- class SetCookies
602
- include Hanami::Action
530
+ class SetCookies < Hanami::Action
603
531
  include Hanami::Action::Cookies
604
532
 
605
- def call(params)
533
+ def handle(*, res)
606
534
  # ...
607
- cookies[:foo] = { value: 'bar', max_age: 100 }
535
+ res.cookies[:foo] = { value: "bar", max_age: 100 }
608
536
  end
609
537
  end
610
538
 
611
- action = SetCookies.new
612
- action.call({}) # => [200, {'Set-Cookie' => "foo=bar; max-age=100;"}, '...']
539
+ action = SetCookies.new(configuration: configuration)
540
+ action.call({}) # => [200, {"Set-Cookie" => "foo=bar; max-age=100;"}, "..."]
613
541
  ```
614
542
 
615
543
  ### Sessions
616
544
 
617
- It has builtin support for Rack sessions:
545
+ Actions have builtin support for Rack sessions.
546
+ Similarly to cookies, you can read the session sent by the HTTP client via
547
+ `req.session`, and also manipulate it via `res.ression`.
618
548
 
619
549
  ```ruby
620
- require 'hanami/controller'
621
- require 'hanami/action/session'
550
+ require "hanami/controller"
551
+ require "hanami/action/session"
622
552
 
623
- class ReadSessionFromRackEnv
624
- include Hanami::Action
553
+ class ReadSessionFromRackEnv < Hanami::Action
625
554
  include Hanami::Action::Session
626
555
 
627
- def call(params)
556
+ def handle(req, *)
628
557
  # ...
629
- session[:age] # => '31'
558
+ req.session[:age] # => "35"
630
559
  end
631
560
  end
632
561
 
633
- action = ReadSessionFromRackEnv.new
634
- action.call({ 'rack.session' => { 'age' => '31' }})
562
+ action = ReadSessionFromRackEnv.new(configuration: configuration)
563
+ action.call({ "rack.session" => { "age" => "35" } })
635
564
  ```
636
565
 
637
566
  Values can be set like a Hash:
638
567
 
639
568
  ```ruby
640
- require 'hanami/controller'
641
- require 'hanami/action/session'
569
+ require "hanami/controller"
570
+ require "hanami/action/session"
642
571
 
643
- class SetSession
644
- include Hanami::Action
572
+ class SetSession < Hanami::Action
645
573
  include Hanami::Action::Session
646
574
 
647
- def call(params)
575
+ def handle(*, res)
648
576
  # ...
649
- session[:age] = 31
577
+ res.session[:age] = 31
650
578
  end
651
579
  end
652
580
 
653
- action = SetSession.new
581
+ action = SetSession.new(configuration: configuration)
654
582
  action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."]
655
583
  ```
656
584
 
657
585
  Values can be removed like a Hash:
658
586
 
659
587
  ```ruby
660
- require 'hanami/controller'
661
- require 'hanami/action/session'
588
+ require "hanami/controller"
589
+ require "hanami/action/session"
662
590
 
663
- class RemoveSession
664
- include Hanami::Action
591
+ class RemoveSession < Hanami::Action
665
592
  include Hanami::Action::Session
666
593
 
667
- def call(params)
594
+ def handle(*, res)
668
595
  # ...
669
- session[:age] = nil
596
+ res.session[:age] = nil
670
597
  end
671
598
  end
672
599
 
673
- action = RemoveSession.new
600
+ action = RemoveSession.new(configuration: configuration)
674
601
  action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."] it removes that value from the session
675
602
  ```
676
603
 
677
- While Hanami::Controller supports sessions natively, it's __session store agnostic__.
604
+ While Hanami::Controller supports sessions natively, it's **session store agnostic**.
678
605
  You have to specify the session store in your Rack middleware configuration (eg `config.ru`).
679
606
 
680
607
  ```ruby
681
608
  use Rack::Session::Cookie, secret: SecureRandom.hex(64)
682
- run Show.new
609
+ run Show.new(configuration: configuration)
683
610
  ```
684
611
 
685
- ### Http Cache
612
+ ### HTTP Cache
686
613
 
687
614
  Hanami::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
688
615
 
689
616
  You can easily set the Cache-Control header for your actions:
690
617
 
691
618
  ```ruby
692
- require 'hanami/controller'
693
- require 'hanami/action/cache'
619
+ require "hanami/controller"
620
+ require "hanami/action/cache"
694
621
 
695
- class HttpCacheController
696
- include Hanami::Action
622
+ class HttpCacheController < Hanami::Action
697
623
  include Hanami::Action::Cache
698
-
699
624
  cache_control :public, max_age: 600 # => Cache-Control: public, max-age=600
700
625
 
701
- def call(params)
626
+ def handle(*)
702
627
  # ...
703
628
  end
704
629
  end
@@ -707,16 +632,14 @@ end
707
632
  Expires header can be specified using `expires` method:
708
633
 
709
634
  ```ruby
710
- require 'hanami/controller'
711
- require 'hanami/action/cache'
635
+ require "hanami/controller"
636
+ require "hanami/action/cache"
712
637
 
713
- class HttpCacheController
714
- include Hanami::Action
638
+ class HttpCacheController < Hanami::Action
715
639
  include Hanami::Action::Cache
716
-
717
640
  expires 60, :public, max_age: 600 # => Expires: Sun, 03 Aug 2014 17:47:02 GMT, Cache-Control: public, max-age=600
718
641
 
719
- def call(params)
642
+ def handle(*)
720
643
  # ...
721
644
  end
722
645
  end
@@ -724,82 +647,76 @@ end
724
647
 
725
648
  ### Conditional Get
726
649
 
727
- 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).
650
+ 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 `304 (Not Modified)` response.
728
651
 
729
- 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.
652
+ 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.
730
653
 
731
654
  You can easily take advantage of Conditional Get using `#fresh` method:
732
655
 
733
656
  ```ruby
734
- require 'hanami/controller'
735
- require 'hanami/action/cache'
657
+ require "hanami/controller"
658
+ require "hanami/action/cache"
736
659
 
737
- class ConditionalGetController
738
- include Hanami::Action
660
+ class ConditionalGetController < Hanami::Action
739
661
  include Hanami::Action::Cache
740
662
 
741
- def call(params)
663
+ def handle(*)
742
664
  # ...
743
- fresh etag: @resource.cache_key
744
- # => halt 304 with header IfNoneMatch = @resource.cache_key
665
+ fresh etag: resource.cache_key
666
+ # => halt 304 with header IfNoneMatch = resource.cache_key
745
667
  end
746
668
  end
747
669
  ```
748
670
 
749
- If `@resource.cache_key` is equal to `IfNoneMatch` header, then hanami will `halt 304`.
671
+ If `resource.cache_key` is equal to `IfNoneMatch` header, then hanami will `halt 304`.
750
672
 
751
- The same behavior is accomplished using `last_modified`:
673
+ An alterative to hashing based check, is the time based check:
752
674
 
753
675
  ```ruby
754
- require 'hanami/controller'
755
- require 'hanami/action/cache'
676
+ require "hanami/controller"
677
+ require "hanami/action/cache"
756
678
 
757
- class ConditionalGetController
758
- include Hanami::Action
679
+ class ConditionalGetController < Hanami::Action
759
680
  include Hanami::Action::Cache
760
681
 
761
- def call(params)
682
+ def handle(*)
762
683
  # ...
763
- fresh last_modified: @resource.update_at
764
- # => halt 304 with header IfModifiedSince = @resource.update_at.httpdate
684
+ fresh last_modified: resource.update_at
685
+ # => halt 304 with header IfModifiedSince = resource.update_at.httpdate
765
686
  end
766
687
  end
767
688
  ```
768
689
 
769
- If `@resource.update_at` is equal to `IfModifiedSince` header, then hanami will `halt 304`.
690
+ If `resource.update_at` is equal to `IfModifiedSince` header, then hanami will `halt 304`.
770
691
 
771
692
  ### Redirect
772
693
 
773
- If you need to redirect the client to another resource, use `#redirect_to`:
694
+ If you need to redirect the client to another resource, use `res.redirect_to`:
774
695
 
775
696
  ```ruby
776
- class Create
777
- include Hanami::Action
778
-
779
- def call(params)
697
+ class Create < Hanami::Action
698
+ def handle(*, res)
780
699
  # ...
781
- redirect_to 'http://example.com/articles/23'
700
+ res.redirect_to "http://example.com/articles/23"
782
701
  end
783
702
  end
784
703
 
785
- action = Create.new
786
- action.call({ article: { title: 'Hello' }}) # => [302, {'Location' => '/articles/23'}, '']
704
+ action = Create.new(configuration: configuration)
705
+ action.call({ article: { title: "Hello" }}) # => [302, {"Location" => "/articles/23"}, ""]
787
706
  ```
788
707
 
789
708
  You can also redirect with a custom status code:
790
709
 
791
710
  ```ruby
792
- class Create
793
- include Hanami::Action
794
-
795
- def call(params)
711
+ class Create < Hanami::Action
712
+ def handle(*, res)
796
713
  # ...
797
- redirect_to 'http://example.com/articles/23', status: 301
714
+ res.redirect_to "http://example.com/articles/23", status: 301
798
715
  end
799
716
  end
800
717
 
801
- action = Create.new
802
- action.call({ article: { title: 'Hello' }}) # => [301, {'Location' => '/articles/23'}, '']
718
+ action = Create.new(configuration: configuration)
719
+ action.call({ article: { title: "Hello" }}) # => [301, {"Location" => "/articles/23"}, ""]
803
720
  ```
804
721
 
805
722
  ### MIME Types
@@ -807,51 +724,46 @@ action.call({ article: { title: 'Hello' }}) # => [301, {'Location' => '/articles
807
724
  `Hanami::Action` automatically sets the `Content-Type` header, according to the request.
808
725
 
809
726
  ```ruby
810
- class Show
811
- include Hanami::Action
812
-
813
- def call(params)
727
+ class Show < Hanami::Action
728
+ def handle(*)
814
729
  end
815
730
  end
816
731
 
817
- action = Show.new
732
+ action = Show.new(configuration: configuration)
818
733
 
819
- action.call({ 'HTTP_ACCEPT' => '*/*' }) # Content-Type "application/octet-stream"
820
- action.format # :all
734
+ response = action.call({ "HTTP_ACCEPT" => "*/*" }) # Content-Type "application/octet-stream"
735
+ response.format # :all
821
736
 
822
- action.call({ 'HTTP_ACCEPT' => 'text/html' }) # Content-Type "text/html"
823
- action.format # :html
737
+ response = action.call({ "HTTP_ACCEPT" => "text/html" }) # Content-Type "text/html"
738
+ response.format # :html
824
739
  ```
825
740
 
826
741
  However, you can force this value:
827
742
 
828
743
  ```ruby
829
- class Show
830
- include Hanami::Action
831
-
832
- def call(params)
744
+ class Show < Hanami::Action
745
+ def handle(*, res)
833
746
  # ...
834
- self.format = :json
747
+ res.format = format(:json)
835
748
  end
836
749
  end
837
750
 
838
- action = Show.new
751
+ action = Show.new(configuration: configuration)
839
752
 
840
- action.call({ 'HTTP_ACCEPT' => '*/*' }) # Content-Type "application/json"
841
- action.format # :json
753
+ response = action.call({ "HTTP_ACCEPT" => "*/*" }) # Content-Type "application/json"
754
+ response.format # :json
842
755
 
843
- action.call({ 'HTTP_ACCEPT' => 'text/html' }) # Content-Type "application/json"
844
- action.format # :json
756
+ response = action.call({ "HTTP_ACCEPT" => "text/html" }) # Content-Type "application/json"
757
+ response.format # :json
845
758
  ```
846
759
 
847
760
  You can restrict the accepted MIME types:
848
761
 
849
762
  ```ruby
850
- class Show
851
- include Hanami::Action
763
+ class Show < Hanami::Action
852
764
  accept :html, :json
853
765
 
854
- def call(params)
766
+ def handle(*)
855
767
  # ...
856
768
  end
857
769
  end
@@ -865,26 +777,24 @@ end
865
777
  You can check if the requested MIME type is accepted by the client.
866
778
 
867
779
  ```ruby
868
- class Show
869
- include Hanami::Action
870
-
871
- def call(params)
780
+ class Show < Hanami::Action
781
+ def handle(req, res)
872
782
  # ...
873
- # @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'
783
+ # @_env["HTTP_ACCEPT"] # => "text/html,application/xhtml+xml,application/xml;q=0.9"
874
784
 
875
- accept?('text/html') # => true
876
- accept?('application/xml') # => true
877
- accept?('application/json') # => false
878
- self.format # :html
785
+ req.accept?("text/html") # => true
786
+ req.accept?("application/xml") # => true
787
+ req.accept?("application/json") # => false
788
+ res.format # :html
879
789
 
880
790
 
881
791
 
882
- # @_env['HTTP_ACCEPT'] # => '*/*'
792
+ # @_env["HTTP_ACCEPT"] # => "*/*"
883
793
 
884
- accept?('text/html') # => true
885
- accept?('application/xml') # => true
886
- accept?('application/json') # => true
887
- self.format # :html
794
+ req.accept?("text/html") # => true
795
+ req.accept?("application/xml") # => true
796
+ req.accept?("application/json") # => true
797
+ res.format # :html
888
798
  end
889
799
  end
890
800
  ```
@@ -893,35 +803,31 @@ Hanami::Controller is shipped with an extensive list of the most common MIME typ
893
803
  Also, you can register your own:
894
804
 
895
805
  ```ruby
896
- Hanami::Controller.configure do
897
- format custom: 'application/custom'
806
+ configuration = Hanami::Controller::Configuration.new do |config|
807
+ config.format custom: "application/custom"
898
808
  end
899
809
 
900
- class Index
901
- include Hanami::Action
902
-
903
- def call(params)
810
+ class Index < Hanami::Action
811
+ def handle(*)
904
812
  end
905
813
  end
906
814
 
907
- action = Index.new
815
+ action = Index.new(configuration: configuration)
908
816
 
909
- action.call({ 'HTTP_ACCEPT' => 'application/custom' }) # => Content-Type 'application/custom'
910
- action.format # => :custom
817
+ response = action.call({ "HTTP_ACCEPT" => "application/custom" }) # => Content-Type "application/custom"
818
+ response.format # => :custom
911
819
 
912
- class Show
913
- include Hanami::Action
914
-
915
- def call(params)
820
+ class Show < Hanami::Action
821
+ def handle(*, res)
916
822
  # ...
917
- self.format = :custom
823
+ res.format = format(:custom)
918
824
  end
919
825
  end
920
826
 
921
- action = Show.new
827
+ action = Show.new(configuration: configuration)
922
828
 
923
- action.call({ 'HTTP_ACCEPT' => '*/*' }) # => Content-Type 'application/custom'
924
- action.format # => :custom
829
+ response = action.call({ "HTTP_ACCEPT" => "*/*" }) # => Content-Type "application/custom"
830
+ response.format # => :custom
925
831
  ```
926
832
 
927
833
  ### Streamed Responses
@@ -929,17 +835,14 @@ action.format # => :custom
929
835
  When the work to be done by the server takes time, it may be a good idea to stream your response. Here's an example of a streamed CSV.
930
836
 
931
837
  ```ruby
932
- Hanami::Controller.configure do
933
- format csv: 'text/csv'
934
- middleware.use ::Rack::Chunked
838
+ configuration = Hanami::Controller::Configuration.new do |config|
839
+ config.format csv: 'text/csv'
935
840
  end
936
841
 
937
- class Csv
938
- include Hanami::Action
939
-
940
- def call(params)
941
- self.format = :csv
942
- self.body = Enumerator.new do |yielder|
842
+ class Csv < Hanami::Action
843
+ def handle(*, res)
844
+ res.format = format(:csv)
845
+ res.body = Enumerator.new do |yielder|
943
846
  yielder << csv_header
944
847
 
945
848
  # Expensive operation is streamed as each line becomes available
@@ -966,230 +869,89 @@ A Controller is nothing more than a logical group of actions: just a Ruby module
966
869
 
967
870
  ```ruby
968
871
  module Articles
969
- class Index
970
- include Hanami::Action
971
-
872
+ class Index < Hanami::Action
972
873
  # ...
973
874
  end
974
875
 
975
- class Show
976
- include Hanami::Action
977
-
876
+ class Show < Hanami::Action
978
877
  # ...
979
878
  end
980
879
  end
981
880
 
982
- Articles::Index.new.call({})
881
+ Articles::Index.new(configuration: configuration).call({})
983
882
  ```
984
883
 
985
884
  ### Hanami::Router integration
986
885
 
987
- While Hanami::Router works great with this framework, Hanami::Controller doesn't depend on it.
988
- You, the developer, are free to choose your own routing system.
989
-
990
- But, if you use them together, the **only constraint is that an action must support _arity 0_ in its constructor**.
991
- The following examples are valid constructors:
992
-
993
886
  ```ruby
994
- def initialize
995
- end
996
-
997
- def initialize(repository = ArticleRepository.new)
998
- end
999
-
1000
- def initialize(repository: ArticleRepository.new)
1001
- end
887
+ require "hanami/router"
888
+ require "hanami/controller"
1002
889
 
1003
- def initialize(options = {})
890
+ module Web
891
+ module Controllers
892
+ module Books
893
+ class Show < Hanami::Action
894
+ def handle(*)
895
+ end
896
+ end
897
+ end
898
+ end
1004
899
  end
1005
900
 
1006
- def initialize(*args)
901
+ configuration = Hanami::Controller::Configuration.new
902
+ router = Hanami::Router.new(configuration: configuration, namespace: Web::Controllers) do
903
+ get "/books/:id", "books#show"
1007
904
  end
1008
905
  ```
1009
906
 
1010
- __Please note that this is subject to change: we're working to remove this constraint.__
1011
-
1012
- Hanami::Router supports lazy loading for controllers. While this policy can be a
1013
- convenient fallback, you should know that it's the slower option. **Be sure of
1014
- loading your controllers before you initialize the router.**
1015
-
1016
-
1017
907
  ### Rack integration
1018
908
 
1019
- Hanami::Controller is compatible with Rack. However, it doesn't mount any middleware.
1020
- While a Hanami application's architecture is more web oriented, this framework is designed to build pure HTTP endpoints.
1021
-
1022
- ### Rack middleware
1023
-
1024
- Rack middleware can be configured globally in `config.ru`. However, consider that they often add
1025
- unnecessary overhead for *all* endpoints that aren't direct users of all the configured middleware.
1026
-
1027
- 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.
1028
-
1029
- The solution is that an action can employ one or more Rack middleware, with `.use`.
1030
-
1031
- ```ruby
1032
- require 'hanami/controller'
1033
-
1034
- module Sessions
1035
- class Create
1036
- include Hanami::Action
1037
- use OmniAuth
1038
-
1039
- def call(params)
1040
- # ...
1041
- end
1042
- end
1043
- end
1044
- ```
1045
-
1046
- ```ruby
1047
- require 'hanami/controller'
1048
-
1049
- module Sessions
1050
- class Create
1051
- include Hanami::Controller
1052
-
1053
- use XMiddleware.new('x', 123)
1054
- use YMiddleware.new
1055
- use ZMiddleware
1056
-
1057
- def call(params)
1058
- # ...
1059
- end
1060
- end
1061
- end
1062
- ```
909
+ Hanami::Controller is compatible with Rack. If you need to use any Rack middleware, please mount them in `config.ru`.
1063
910
 
1064
911
  ### Configuration
1065
912
 
1066
- Hanami::Controller can be configured with a DSL.
913
+ Hanami::Controller can be configured via `Hanami::Controller::Configuration`.
1067
914
  It supports a few options:
1068
915
 
1069
916
  ```ruby
1070
- require 'hanami/controller'
1071
-
1072
- Hanami::Controller.configure do
1073
- # Handle exceptions with HTTP statuses (true) or don't catch them (false)
1074
- # Argument: boolean, defaults to `true`
1075
- #
1076
- handle_exceptions true
917
+ require "hanami/controller"
1077
918
 
919
+ configuration = Hanami::Controller::Configuration.new do |config|
1078
920
  # If the given exception is raised, return that HTTP status
1079
921
  # It can be used multiple times
1080
922
  # Argument: hash, empty by default
1081
923
  #
1082
- handle_exception ArgumentError => 404
924
+ config.handle_exception ArgumentError => 404
1083
925
 
1084
926
  # Register a format to MIME type mapping
1085
927
  # Argument: hash, key: format symbol, value: MIME type string, empty by default
1086
928
  #
1087
- format custom: 'application/custom'
929
+ config.format custom: "application/custom"
1088
930
 
1089
931
  # Define a fallback format to detect in case of HTTP request with `Accept: */*`
1090
932
  # If not defined here, it will return Rack's default: `application/octet-stream`
1091
933
  # Argument: symbol, it should be already known. defaults to `nil`
1092
934
  #
1093
- default_request_format :html
935
+ config.default_request_format = :html
1094
936
 
1095
937
  # Define a default format to set as `Content-Type` header for response,
1096
938
  # unless otherwise specified.
1097
939
  # If not defined here, it will return Rack's default: `application/octet-stream`
1098
940
  # Argument: symbol, it should be already known. defaults to `nil`
1099
941
  #
1100
- default_response_format :html
942
+ config.default_response_format = :html
1101
943
 
1102
944
  # Define a default charset to return in the `Content-Type` response header
1103
945
  # If not defined here, it returns `utf-8`
1104
946
  # Argument: string, defaults to `nil`
1105
947
  #
1106
- default_charset 'koi8-r'
1107
-
1108
- # Configure the logic to be executed when Hanami::Action is included
1109
- # This is useful to DRY code by having a single place where to configure
1110
- # shared behaviors like authentication, sessions, cookies etc.
1111
- # Argument: proc
1112
- #
1113
- prepare do
1114
- include Hanami::Action::Sessions
1115
- include MyAuthentication
1116
- use SomeMiddleWare
1117
-
1118
- before { authenticate! }
1119
- end
948
+ config.default_charset = "koi8-r"
1120
949
  end
1121
950
  ```
1122
951
 
1123
- All of the global configurations can be overwritten at the controller level.
1124
- Each controller and action has its own copy of the global configuration.
1125
-
1126
- This means changes are inherited from the top to the bottom, but do not bubble back up.
1127
-
1128
- ```ruby
1129
- require 'hanami/controller'
1130
-
1131
- Hanami::Controller.configure do
1132
- handle_exception ArgumentError => 400
1133
- end
1134
-
1135
- module Articles
1136
- class Create
1137
- include Hanami::Action
1138
-
1139
- configure do
1140
- handle_exceptions false
1141
- end
1142
-
1143
- def call(params)
1144
- raise ArgumentError
1145
- end
1146
- end
1147
- end
1148
-
1149
- module Users
1150
- class Create
1151
- include Hanami::Action
1152
-
1153
- def call(params)
1154
- raise ArgumentError
1155
- end
1156
- end
1157
- end
1158
-
1159
- Users::Create.new.call({}) # => HTTP 400
1160
-
1161
- Articles::Create.new.call({})
1162
- # => raises ArgumentError because we set handle_exceptions to false
1163
- ```
1164
-
1165
952
  ### Thread safety
1166
953
 
1167
- An Action is **mutable**. When used without Hanami::Router, be sure to instantiate an
1168
- action for each request. The same advice applies when using
1169
- Hanami::Router but NOT routing to `mycontroller#myaction` but instead
1170
- routing direct to a class.
1171
-
1172
- ```ruby
1173
- # config.ru
1174
- require 'hanami/controller'
1175
-
1176
- class Action
1177
- include Hanami::Action
1178
-
1179
- def self.call(env)
1180
- new.call(env)
1181
- end
1182
-
1183
- def call(params)
1184
- self.body = object_id.to_s
1185
- end
1186
- end
1187
-
1188
- run Action
1189
- ```
1190
-
1191
- Hanami::Controller heavely depends on class configuration. To ensure immutability
1192
- in deployment environments, use `Hanami::Controller.load!`.
954
+ An Action is **immutable**, it works without global state, so it's thread-safe by design.
1193
955
 
1194
956
  ## Versioning
1195
957
 
@@ -1205,6 +967,6 @@ __Hanami::Controller__ uses [Semantic Versioning 2.0.0](http://semver.org)
1205
967
 
1206
968
  ## Copyright
1207
969
 
1208
- Copyright © 2014-2017 Luca Guidi – Released under MIT License
970
+ Copyright © 2014-2021 Luca Guidi – Released under MIT License
1209
971
 
1210
972
  This project was formerly known as Lotus (`lotus-controller`).