hanami-controller 0.0.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +1180 -9
  5. data/hanami-controller.gemspec +19 -12
  6. data/lib/hanami-controller.rb +1 -0
  7. data/lib/hanami/action.rb +85 -0
  8. data/lib/hanami/action/cache.rb +174 -0
  9. data/lib/hanami/action/cache/cache_control.rb +70 -0
  10. data/lib/hanami/action/cache/conditional_get.rb +93 -0
  11. data/lib/hanami/action/cache/directives.rb +99 -0
  12. data/lib/hanami/action/cache/expires.rb +73 -0
  13. data/lib/hanami/action/callable.rb +94 -0
  14. data/lib/hanami/action/callbacks.rb +210 -0
  15. data/lib/hanami/action/configurable.rb +49 -0
  16. data/lib/hanami/action/cookie_jar.rb +181 -0
  17. data/lib/hanami/action/cookies.rb +85 -0
  18. data/lib/hanami/action/exposable.rb +115 -0
  19. data/lib/hanami/action/flash.rb +182 -0
  20. data/lib/hanami/action/glue.rb +66 -0
  21. data/lib/hanami/action/head.rb +122 -0
  22. data/lib/hanami/action/mime.rb +493 -0
  23. data/lib/hanami/action/params.rb +285 -0
  24. data/lib/hanami/action/rack.rb +270 -0
  25. data/lib/hanami/action/rack/callable.rb +47 -0
  26. data/lib/hanami/action/rack/file.rb +33 -0
  27. data/lib/hanami/action/redirect.rb +59 -0
  28. data/lib/hanami/action/request.rb +86 -0
  29. data/lib/hanami/action/session.rb +154 -0
  30. data/lib/hanami/action/throwable.rb +194 -0
  31. data/lib/hanami/action/validatable.rb +128 -0
  32. data/lib/hanami/controller.rb +250 -2
  33. data/lib/hanami/controller/configuration.rb +705 -0
  34. data/lib/hanami/controller/error.rb +7 -0
  35. data/lib/hanami/controller/version.rb +4 -1
  36. data/lib/hanami/http/status.rb +62 -0
  37. metadata +124 -16
  38. data/.gitignore +0 -9
  39. data/Gemfile +0 -4
  40. data/Rakefile +0 -2
  41. data/bin/console +0 -14
  42. data/bin/setup +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9b882cec7ffc28e71c3b39a1107c33e36bc53a4d
4
- data.tar.gz: 634865a8c1fc74e07e22fcd952a46fcc80bcb59a
3
+ metadata.gz: 6aa09c8d919968060861da431f0ba61eb8c58cac
4
+ data.tar.gz: 6b46109f745c50ee376811c08469792ed00eb053
5
5
  SHA512:
6
- metadata.gz: 527fdf6f007ae9eba6bbd5faa9918a1a74e43abb04c61c582a513130d81f4315d5b95f741d3796e9490899746b7c74f8f3e9d66b038f3d2de76a7944f793979d
7
- data.tar.gz: 04812442901425c86f7aed5e5e7f9de25151f243d8f8aa2fc9f202d15a4ee933faba206becb6d3f64cbae862b747a510510c3c842832e505d5f428feefd89fce
6
+ metadata.gz: 5a8524d76af843a0e61350b7b9b550e8ce2d1f7b6ff84fe7ea0cc62c7af1bddde884a8c56fdcc8b7430cd3733598a1a0a9efb35bb4ba7467dff4b01f17bf643c
7
+ data.tar.gz: 5251e851f5d42427d012532b84e26837b5dca7d0c8e1c90b4bad4ab15dff614cca508583b44aa1741b9f5926f07fb36a48647dcd7ce7ec0f921acf6fd0e96bea
data/CHANGELOG.md ADDED
@@ -0,0 +1,155 @@
1
+ # Hanami::Controller
2
+ Complete, fast and testable actions for Rack
3
+
4
+ ## v0.6.0 - 2016-01-22
5
+ ### Changed
6
+ - [Luca Guidi] Renamed the project
7
+
8
+ ## v0.5.1 - 2016-01-19
9
+ ### Fixed
10
+ - [Alfonso Uceda] Ensure `rack.session` cookie to not be sent twice when both `Lotus::Action::Cookies` and `Rack::Session::Cookie` are used together
11
+
12
+ ## v0.5.0 - 2016-01-12
13
+ ### Added
14
+ - [Luca Guidi] Reference a raised exception in Rack env's `rack.exception`. Compatibility with exception reporting SaaS.
15
+
16
+ ### Fixed
17
+ - [Cainã Costa] Ensure Rack environment to be always available for sessions unit tests
18
+ - [Luca Guidi] Ensure superclass exceptions to not shadow subclasses during exception handling (eg. `CustomError` handler will take precedence over `StandardError`)
19
+
20
+ ### Changed
21
+ - [Luca Guidi] Removed `Lotus::Controller::Configuration#default_format`
22
+ - [Cainã Costa] Made `Lotus::Action#session` a public method for improved unit testing
23
+ - [Karim Tarek] Introduced `Lotus::Controller::Error` and let all the framework exceptions to inherit from it.
24
+
25
+ ## v0.4.6 - 2015-12-04
26
+ ### Added
27
+ - [Luca Guidi] Allow to force custom headers for responses that according to RFC shouldn't include them (eg 204). Override `#keep_response_header?(header)` in action
28
+
29
+ ## v0.4.5 - 2015-09-30
30
+ ### Added
31
+ - [Theo Felippe] Added configuration entries: `#default_request_format` and `default_response_format`.
32
+ - [Wellington Santos] Error handling to take account of inherited exceptions.
33
+
34
+ ### Changed
35
+ - [Theo Felippe] Deprecated `#default_format` in favor of: `#default_request_format`.
36
+
37
+ ## v0.4.4 - 2015-06-23
38
+ ### Added
39
+ - [Luca Guidi] Security protection against Cross Site Request Forgery (CSRF).
40
+
41
+ ### Fixed
42
+ - [Matthew Bellantoni] Ensure nested params to be correctly coerced to Hash.
43
+
44
+ ## v0.4.3 - 2015-05-22
45
+ ### Added
46
+ - [Alfonso Uceda Pompa & Luca Guidi] Introduced `Lotus::Action#send_file`
47
+ - [Alfonso Uceda Pompa] Set automatically `Expires` option for cookies when it's missing but `Max-Age` is present. Compatibility with old browsers.
48
+
49
+ ## v0.4.2 - 2015-05-15
50
+ ### Fixed
51
+ - [Luca Guidi] Ensure `Lotus::Action::Params#to_h` to return `::Hash` at the top level
52
+
53
+ ## v0.4.1 - 2015-05-15
54
+ ### Fixed
55
+ - [Luca Guidi] Ensure proper automatic `Content-Type` working well with Internet Explorer.
56
+ - [Luca Guidi] Ensure `Lotus::Action#redirect_to` to return `::String` for Rack servers compatibility.
57
+
58
+ ### Changed
59
+ - [Alfonso Uceda Pompa] Prevent `Content-Type` and `Content-Lenght` to be sent when status code requires no body (eg. `204`).
60
+ This is for compatibility with `Rack::Lint`, not with RFC 2016.
61
+ - [Luca Guidi] Ensure `Lotus::Action::Params#to_h` to return `::Hash`
62
+
63
+ ## v0.4.0 - 2015-03-23
64
+ ### Added
65
+ - [Erol Fornoles] `Action.use` now accepts a block
66
+ - [Alfonso Uceda Pompa] Introduced `Lotus::Controller::Configuration#cookies` as default cookie options.
67
+ - [Alfonso Uceda Pompa] Introduced `Lotus::Controller::Configuration#default_headers` as default HTTP headers to return in all the responses.
68
+ - [Luca Guidi] Introduced `Lotus::Action::Params#get` as a safe API to access nested params.
69
+
70
+ ### Changed
71
+ - [Alfonso Uceda Pompa] `redirect_to` now is a flow control method: it terminates the execution of an action, including the callbacks.
72
+
73
+ ## v0.3.2 - 2015-01-30
74
+ ### Added
75
+ - [Alfonso Uceda Pompa] Callbacks: introduced `append_before` (alias of `before`), `append_after` (alias of `after`), `prepend_before` and `prepend_after`.
76
+ - [Alfonso Uceda Pompa] Introduced `Lotus::Action::Params#raw` which returns unfiltered data as it comes from an HTTP request.
77
+ - [Alfonso Uceda Pompa] `Lotus::Action::Rack.use` now fully supports Rack middleware, by mounting an internal `Rack::Builder` instance.
78
+ - [Simone Carletti] `Lotus::Action::Throwable#halt` now accepts an optional message. If missing it falls back to the corresponding HTTP status message.
79
+ - [Steve Hodgkiss] Nested params validation
80
+
81
+ ### Fixed
82
+ - [Luca Guidi] Ensure HEAD requests will return empty body
83
+ - [Stefano Verna] Ensure HTTP status codes with empty body won't send body and non-entity headers.
84
+ - [Luca Guidi] Only dump exceptions in `rack.errors` if handling is turned off, or the raised exception is not managed.
85
+ - [Luca Guidi] Ensure params will return coerced values
86
+
87
+ ## v0.3.1 - 2015-01-08
88
+ ### Added
89
+ - [Lasse Skindstad Ebert] Introduced `Action#request` which returns an instance a `Rack::Request` compliant object: `Lotus::Action::Request`.
90
+
91
+ ### Fixed
92
+ - [Steve Hodgkiss] Ensure params to return coerced values
93
+
94
+ ## v0.3.0 - 2014-12-23
95
+ ### Added
96
+ - [Luca Guidi] Introduced `Action#request_id` as unique identifier for an incoming HTTP request
97
+ - [Luca Guidi] Introduced `Lotus::Controller.load!` as loading framework entry point
98
+ - [Kir Shatrov] Allow to define a default charset (`default_charset` configuration)
99
+ - [Kir Shatrov] Automatic content type with charset (eg `Content-Type: text/html; charset=utf-8`)
100
+ - [Michał Krzyżanowski] Allow to specify custom exception handlers: procs or methods (`exception_handler` configuration)
101
+ - [Karl Freeman & Lucas Souza] Introduced HTTP caching (`Cache-Control`, `Last-Modified`, ETAG, Conditional GET, expires)
102
+ - [Satoshi Amemiya] Introduced `Action::Params#to_h` and `#to_hash`
103
+ - [Luca Guidi] Added `#params` and `#errors` as default exposures
104
+ - [Luca Guidi] Introduced complete params validations
105
+ - [Luca Guidi & Matthew Bellantoni] Allow to whitelist params
106
+ - [Luca Guidi & Matthew Bellantoni] Allow to define custom classes for params via `Action.params`
107
+ - [Krzysztof Zalewski] Introduced `Action#format` as query method to introspect the requested mime type
108
+ - [Luca Guidi] Official support for Ruby 2.2
109
+
110
+ ### Changed
111
+ - [Trung Lê] Renamed `Configuration#modules` to `#prepare`
112
+ - [Luca Guidi] Update HTTP status codes to IETF RFC 7231
113
+ - [Luca Guidi] When `Lotus::Controller` is included, don't inject code
114
+ - [Luca Guidi] Removed `Controller.action` as a DSL to define actions
115
+ - [Krzysztof Zalewski] Removed `Action#content_type` in favor of `#format=` which accepts a symbol (eg. `:json`)
116
+ - [Fuad Saud] Reduce method visibility where possible (Ruby `private` and `protected`)
117
+
118
+ ### Fixed
119
+ - [Luca Guidi] Don't let exposures definition to override existing methods
120
+
121
+ ## v0.2.0 - 2014-06-23
122
+ ### Added
123
+ - [Luca Guidi] Introduced `Controller.configure` and `Controller.duplicate`
124
+ - [Luca Guidi] Introduced `Action.use`, that let to use a Rack middleware as a before callback
125
+ – [Luca Guidi] Allow to define a default mime type when the request is `Accept: */*` (`default_format` configuration)
126
+ – [Luca Guidi] Allow to register custom mime types and associate them to a symbol (`format` configuration)
127
+ - [Luca Guidi] Introduced `Configuration#handle_exceptions` to associate exceptions to HTTP statuses
128
+ - [Damir Zekic] Allow developers to toggle exception handling (`handle_exceptions` configuration)
129
+ - [Luca Guidi] Introduced `Controller::Configuration`
130
+ - [Luca Guidi] Official support for Ruby 2.1
131
+
132
+ ### Changed
133
+ - [Luca Guidi] `Lotus::Action::Params` doesn't inherit from `Lotus::Utils::Hash` anymore
134
+ - [Luca Guidi] `Lotus::Action::CookieJar` doesn't inherit from `Lotus::Utils::Hash` anymore
135
+ - [Luca Guidi] Make HTTP status messages compliant with IANA and Rack
136
+ - [Damir Zekic] Moved `#throw` override logic into `#halt`, which keeps the same semantic
137
+
138
+ ### Fixed
139
+ - [Krzysztof Zalewski] Reference exception in `rack.errors`
140
+
141
+ ## v0.1.0 - 2014-02-23
142
+ ### Added
143
+ - [Luca Guidi] Introduced `Action.accept` to whitelist accepted mime types
144
+ - [Luca Guidi] Introduced `Action#accept?` as a query method for the current request
145
+ - [Luca Guidi] Allow to whitelist handled exceptions and associate them to an HTTP status
146
+ - [Luca Guidi] Automatic `Content-Type`
147
+ - [Luca Guidi] Use `throw` as a control flow which understands HTTP status
148
+ - [Luca Guidi] Introduced opt-in support for HTTP/Rack cookies
149
+ - [Luca Guidi] Introduced opt-in support for HTTP/Rack sessions
150
+ - [Luca Guidi] Introduced HTTP redirect API
151
+ - [Luca Guidi] Introduced callbacks for actions: before and after
152
+ - [Luca Guidi] Introduced exceptions handling with HTTP statuses
153
+ - [Luca Guidi] Introduced exposures
154
+ - [Luca Guidi] Introduced basic actions compatible with Rack
155
+ - [Luca Guidi] Official support for Ruby 2.0
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright © 2014-2016 Luca Guidi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,28 @@
1
1
  # Hanami::Controller
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/hanami/controller`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Complete, fast and testable actions for Rack and [Hanami](http://hanamirb.org)
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ ## Status
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/hanami-controller.png)](http://badge.fury.io/rb/hanami-controller)
8
+ [![Build Status](https://secure.travis-ci.org/hanami/controller.png?branch=master)](http://travis-ci.org/hanami/controller?branch=master)
9
+ [![Coverage](https://coveralls.io/repos/hanami/controller/badge.png?branch=master)](https://coveralls.io/r/hanami/controller)
10
+ [![Code Climate](https://codeclimate.com/github/hanami/controller.png)](https://codeclimate.com/github/hanami/controller)
11
+ [![Dependencies](https://gemnasium.com/hanami/controller.png)](https://gemnasium.com/hanami/controller)
12
+ [![Inline docs](http://inch-ci.org/github/hanami/controller.png)](http://inch-ci.org/github/hanami/controller)
13
+
14
+ ## Contact
15
+
16
+ * Home page: http://hanamirb.org
17
+ * Mailing List: http://hanamirb.org/mailing-list
18
+ * API Doc: http://rdoc.info/gems/hanami-controller
19
+ * Bugs/Issues: https://github.com/hanami/controller/issues
20
+ * Chat: http://chat.hanamirb.org
21
+ * Chat: https://gitter.im/hanami/chat
22
+
23
+ ## Rubies
24
+
25
+ __Hanami::Controller__ supports Ruby (MRI) 2.2+
6
26
 
7
27
  ## Installation
8
28
 
@@ -14,23 +34,1174 @@ gem 'hanami-controller'
14
34
 
15
35
  And then execute:
16
36
 
17
- $ bundle
37
+ ```shell
38
+ $ bundle
39
+ ```
18
40
 
19
41
  Or install it yourself as:
20
42
 
21
- $ gem install hanami-controller
43
+ ```shell
44
+ $ gem install hanami-controller
45
+ ```
22
46
 
23
47
  ## Usage
24
48
 
25
- TODO: Write usage instructions here
49
+ Hanami::Controller is a micro library for web frameworks.
50
+ It works beautifully with [Hanami::Router](https://github.com/hanami/router), but it can be employed everywhere.
51
+ It's designed to be fast and testable.
52
+
53
+ ### Actions
54
+
55
+ The core of this framework are the actions.
56
+ They are the endpoints that respond to incoming HTTP requests.
57
+
58
+ ```ruby
59
+ class Show
60
+ include Hanami::Action
61
+
62
+ def call(params)
63
+ @article = Article.find params[:id]
64
+ end
65
+ end
66
+ ```
67
+
68
+ The usage of `Hanami::Action` follows the Hanami philosophy: include a module and implement a minimal interface.
69
+ In this case, the interface is one method: `#call(params)`.
70
+
71
+ Hanami is designed to not interfere with inheritance.
72
+ This is important, because you can implement your own initialization strategy.
73
+
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
+
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
+ __We're avoiding HTTP calls__, we're also going to avoid hitting the database (it depends on the stubbed repository), __we're just dealing with message passing__.
79
+ Imagine how **fast** the unit test could be.
80
+
81
+ ```ruby
82
+ class Show
83
+ include Hanami::Action
84
+
85
+ def initialize(repository = Article)
86
+ @repository = repository
87
+ end
88
+
89
+ def call(params)
90
+ @article = @repository.find params[:id]
91
+ end
92
+ end
93
+
94
+ action = Show.new(MemoryArticleRepository)
95
+ action.call({ id: 23 })
96
+ ```
97
+
98
+ ### Params
99
+
100
+ The request params are passed as an argument to the `#call` method.
101
+ If routed with *Hanami::Router*, it extracts the relevant bits from the Rack `env` (eg the requested `:id`).
102
+ Otherwise everything is passed as is: the full Rack `env` in production, and the given `Hash` for unit tests.
103
+
104
+ With Hanami::Router:
105
+
106
+ ```ruby
107
+ class Show
108
+ include Hanami::Action
109
+
110
+ def call(params)
111
+ # ...
112
+ puts params # => { id: 23 } extracted from Rack env
113
+ end
114
+ end
115
+ ```
116
+
117
+ Standalone:
118
+
119
+ ```ruby
120
+ class Show
121
+ include Hanami::Action
122
+
123
+ def call(params)
124
+ # ...
125
+ puts params # => { :"rack.version"=>[1, 2], :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
126
+ end
127
+ end
128
+ ```
129
+
130
+ Unit Testing:
131
+
132
+ ```ruby
133
+ class Show
134
+ include Hanami::Action
135
+
136
+ def call(params)
137
+ # ...
138
+ puts params # => { id: 23, key: 'value' } passed as it is from testing
139
+ end
140
+ end
141
+
142
+ action = Show.new
143
+ response = action.call({ id: 23, key: 'value' })
144
+ ```
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 'hanami/controller'
153
+
154
+ class Signup
155
+ include Hanami::Action
156
+
157
+ params do
158
+ param :first_name
159
+ param :last_name
160
+ param :email
161
+
162
+ param :address do
163
+ param :line_one
164
+ param :state
165
+ param :country
166
+ end
167
+ end
168
+
169
+ def call(params)
170
+ # Describe inheritance hierarchy
171
+ puts params.class # => Signup::Params
172
+ puts params.class.superclass # => Hanami::Action::Params
173
+
174
+ # Whitelist :first_name, but not :admin
175
+ puts params[:first_name] # => "Luca"
176
+ puts params[:admin] # => nil
177
+
178
+ # Whitelist nested params [:address][:line_one], not [:address][:line_two]
179
+ puts params[:address][:line_one] # => '69 Tender St'
180
+ puts params[:address][:line_two] # => nil
181
+ end
182
+ end
183
+ ```
184
+
185
+ #### Validations & Coercions
26
186
 
27
- ## Development
187
+ Because params are a well defined set of data required to fulfill a feature
188
+ in your application, you can validate them. So you can avoid hitting lower MVC layers
189
+ when params are invalid.
28
190
 
29
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
191
+ If you specify the `:type` option, the param will be coerced.
192
+
193
+ ```ruby
194
+ require 'hanami/controller'
195
+
196
+ class Signup
197
+ MEGABYTE = 1024 ** 2
198
+ include Hanami::Action
199
+
200
+ params do
201
+ param :first_name, presence: true
202
+ param :last_name, presence: true
203
+ param :email, presence: true, format: /@/, confirmation: true
204
+ param :password, presence: true, confirmation: true
205
+ param :terms_of_service, acceptance: true
206
+ param :avatar, size: 0..(MEGABYTE * 3)
207
+ param :age, type: Integer, size: 18..99
208
+ end
209
+
210
+ def call(params)
211
+ halt 400 unless params.valid?
212
+ # ...
213
+ end
214
+ end
215
+
216
+ action = Signup.new
217
+
218
+ action.call(valid_params) # => [200, {}, ...]
219
+ action.errors.empty? # => true
220
+
221
+ action.call(invalid_params) # => [400, {}, ...]
222
+ action.errors # => #<Hanami::Validations::Errors:0x007fabe4b433d0 @errors={...}>
223
+
224
+ action.errors.for(:email)
225
+ # => [#<Hanami::Validations::Error:0x007fabe4b432e0 @attribute=:email, @validation=:presence, @expected=true, @actual=nil>]
226
+ ```
227
+
228
+ ### Response
229
+
230
+ The output of `#call` is a serialized Rack::Response (see [#finish](http://rubydoc.info/github/rack/rack/master/Rack/Response#finish-instance_method)):
231
+
232
+ ```ruby
233
+ class Show
234
+ include Hanami::Action
235
+
236
+ def call(params)
237
+ # ...
238
+ end
239
+ end
240
+
241
+ action = Show.new
242
+ action.call({}) # => [200, {}, [""]]
243
+ ```
244
+
245
+ It has private accessors to explicitly set status, headers, and body:
246
+
247
+ ```ruby
248
+ class Show
249
+ include Hanami::Action
250
+
251
+ def call(params)
252
+ self.status = 201
253
+ self.body = 'Hi!'
254
+ self.headers.merge!({ 'X-Custom' => 'OK' })
255
+ end
256
+ end
257
+
258
+ action = Show.new
259
+ action.call({}) # => [201, { "X-Custom" => "OK" }, ["Hi!"]]
260
+ ```
261
+
262
+ ### Exposures
263
+
264
+ We know that actions are objects and `Hanami::Action` respects one of the pillars of OOP: __encapsulation__.
265
+ Other frameworks extract instance variables (`@ivar`) and make them available to the view context.
266
+
267
+ `Hanami::Action`'s solution is the simple and powerful DSL: `expose`.
268
+ It's a thin layer on top of `attr_reader`.
269
+
270
+ Using `expose` creates a getter for the given attribute, and adds it to the _exposures_.
271
+ Exposures (`#exposures`) are a set of attributes exposed to the view.
272
+ That is to say the variables necessary for rendering a view.
273
+
274
+ By default, all `Hanami::Action` objects expose `#params` and `#errors`.
275
+
276
+ ```ruby
277
+ class Show
278
+ include Hanami::Action
279
+
280
+ expose :article
281
+
282
+ def call(params)
283
+ @article = Article.find params[:id]
284
+ end
285
+ end
286
+
287
+ action = Show.new
288
+ action.call({ id: 23 })
289
+
290
+ assert_equal 23, action.article.id
291
+
292
+ puts action.exposures # => { article: <Article:0x007f965c1d0318 @id=23> }
293
+ ```
294
+
295
+ ### Callbacks
296
+
297
+ It offers a powerful, inheritable callback chain which is executed before and/or after your `#call` method invocation:
298
+
299
+ ```ruby
300
+ class Show
301
+ include Hanami::Action
302
+
303
+ before :authenticate, :set_article
304
+
305
+ def call(params)
306
+ end
307
+
308
+ private
309
+ def authenticate
310
+ # ...
311
+ end
312
+
313
+ # `params` in the method signature is optional
314
+ def set_article(params)
315
+ @article = Article.find params[:id]
316
+ end
317
+ end
318
+ ```
319
+
320
+ Callbacks can also be expressed as anonymous lambdas:
321
+
322
+ ```ruby
323
+ class Show
324
+ include Hanami::Action
325
+
326
+ before { ... } # do some authentication stuff
327
+ before { |params| @article = Article.find params[:id] }
328
+
329
+ def call(params)
330
+ end
331
+ end
332
+ ```
30
333
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
334
+ ### Exceptions management
335
+
336
+ When an exception is raised, it automatically sets the HTTP status to [500](http://httpstatus.es/500):
337
+
338
+ ```ruby
339
+ class Show
340
+ include Hanami::Action
341
+
342
+ def call(params)
343
+ raise
344
+ end
345
+ end
346
+
347
+ action = Show.new
348
+ action.call({}) # => [500, {}, ["Internal Server Error"]]
349
+ ```
350
+
351
+ You can map a specific raised exception to a different HTTP status.
352
+
353
+ ```ruby
354
+ class Show
355
+ include Hanami::Action
356
+ handle_exception RecordNotFound => 404
357
+
358
+ def call(params)
359
+ @article = Article.find params[:id]
360
+ end
361
+ end
362
+
363
+ action = Show.new
364
+ action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
365
+ ```
366
+
367
+ You can also define custom handlers for exceptions.
368
+
369
+ ```ruby
370
+ class Create
371
+ include Hanami::Action
372
+ handle_exception ArgumentError => :my_custom_handler
373
+
374
+ def call(params)
375
+ raise ArgumentError.new("Invalid arguments")
376
+ end
377
+
378
+ private
379
+ def my_custom_handler(exception)
380
+ status 400, exception.message
381
+ end
382
+ end
383
+
384
+ action = Create.new
385
+ action.call({}) # => [400, {}, ["Invalid arguments"]]
386
+ ```
387
+
388
+ Exception policies can be defined globally, **before** the controllers/actions
389
+ are loaded.
390
+
391
+ ```ruby
392
+ Hanami::Controller.configure do
393
+ handle_exception RecordNotFound => 404
394
+ end
395
+
396
+ class Show
397
+ include Hanami::Action
398
+
399
+ def call(params)
400
+ @article = Article.find params[:id]
401
+ end
402
+ end
403
+
404
+ action = Show.new
405
+ action.call({id: 'unknown'}) # => [404, {}, ["Not Found"]]
406
+ ```
407
+
408
+ This feature can be turned off globally, in a controller or in a single action.
409
+
410
+ ```ruby
411
+ Hanami::Controller.configure do
412
+ handle_exceptions false
413
+ end
414
+
415
+ # or
416
+
417
+ module Articles
418
+ class Show
419
+ include Hanami::Action
420
+
421
+ configure do
422
+ handle_exceptions false
423
+ end
424
+
425
+ def call(params)
426
+ @article = Article.find params[:id]
427
+ end
428
+ end
429
+ end
430
+
431
+ action = Articles::Show.new
432
+ action.call({id: 'unknown'}) # => raises RecordNotFound
433
+ ```
434
+
435
+ #### Inherited Exceptions
436
+
437
+ ```ruby
438
+ class MyCustomException < StandardError
439
+ end
440
+
441
+ module Articles
442
+ class Index
443
+ include Hanami::Action
444
+
445
+ handle_exception MyCustomException => :handle_my_exception
446
+
447
+ def call(params)
448
+ raise MyCustomException
449
+ end
450
+
451
+ private
452
+
453
+ def handle_my_exception
454
+ # ...
455
+ end
456
+ end
457
+
458
+ class Show
459
+ include Hanami::Action
460
+
461
+ handle_exception StandardError => :handle_standard_error
462
+
463
+ def call(params)
464
+ raise MyCustomException
465
+ end
466
+
467
+ private
468
+
469
+ def handle_standard_error
470
+ # ...
471
+ end
472
+ end
473
+ end
474
+
475
+ Articles::Index.new.call({}) # => `handle_my_exception` will be invoked
476
+ Articles::Show.new.call({}) # => `handle_standard_error` will be invoked,
477
+ # because `MyCustomException` inherits from `StandardError`
478
+ ```
479
+
480
+ ### Throwable HTTP statuses
481
+
482
+ When `#halt` is used with a valid HTTP code, it stops the execution and sets the proper status and body for the response:
483
+
484
+ ```ruby
485
+ class Show
486
+ include Hanami::Action
487
+
488
+ before :authenticate!
489
+
490
+ def call(params)
491
+ # ...
492
+ end
493
+
494
+ private
495
+ def authenticate!
496
+ halt 401 unless authenticated?
497
+ end
498
+ end
499
+
500
+ action = Show.new
501
+ action.call({}) # => [401, {}, ["Unauthorized"]]
502
+ ```
503
+
504
+ Alternatively, you can specify a custom message.
505
+
506
+ ```ruby
507
+ class Show
508
+ include Hanami::Action
509
+
510
+ def call(params)
511
+ DroidRepository.find(params[:id]) or not_found
512
+ end
513
+
514
+ private
515
+ def not_found
516
+ halt 404, "This is not the droid you're looking for"
517
+ end
518
+ end
519
+
520
+ action = Show.new
521
+ action.call({}) # => [404, {}, ["This is not the droid you're looking for"]]
522
+ ```
523
+
524
+ ### Cookies
525
+
526
+ Hanami::Controller offers convenient access to cookies.
527
+
528
+ They are read as a Hash from Rack env:
529
+
530
+ ```ruby
531
+ require 'hanami/controller'
532
+ require 'hanami/action/cookies'
533
+
534
+ class ReadCookiesFromRackEnv
535
+ include Hanami::Action
536
+ include Hanami::Action::Cookies
537
+
538
+ def call(params)
539
+ # ...
540
+ cookies[:foo] # => 'bar'
541
+ end
542
+ end
543
+
544
+ action = ReadCookiesFromRackEnv.new
545
+ action.call({'HTTP_COOKIE' => 'foo=bar'})
546
+ ```
547
+
548
+ They are set like a Hash:
549
+
550
+ ```ruby
551
+ require 'hanami/controller'
552
+ require 'hanami/action/cookies'
553
+
554
+ class SetCookies
555
+ include Hanami::Action
556
+ include Hanami::Action::Cookies
557
+
558
+ def call(params)
559
+ # ...
560
+ cookies[:foo] = 'bar'
561
+ end
562
+ end
563
+
564
+ action = SetCookies.new
565
+ action.call({}) # => [200, {'Set-Cookie' => 'foo=bar'}, '...']
566
+ ```
567
+
568
+ They are removed by setting their value to `nil`:
569
+
570
+ ```ruby
571
+ require 'hanami/controller'
572
+ require 'hanami/action/cookies'
573
+
574
+ class RemoveCookies
575
+ include Hanami::Action
576
+ include Hanami::Action::Cookies
577
+
578
+ def call(params)
579
+ # ...
580
+ cookies[:foo] = nil
581
+ end
582
+ end
583
+
584
+ action = RemoveCookies.new
585
+ action.call({}) # => [200, {'Set-Cookie' => "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 -0000"}, '...']
586
+ ```
587
+
588
+ Default values can be set in configuration, but overriden case by case.
589
+
590
+ ```ruby
591
+ require 'hanami/controller'
592
+ require 'hanami/action/cookies'
593
+
594
+ Hanami::Controller.configure do
595
+ cookies max_age: 300 # 5 minutes
596
+ end
597
+
598
+ class SetCookies
599
+ include Hanami::Action
600
+ include Hanami::Action::Cookies
601
+
602
+ def call(params)
603
+ # ...
604
+ cookies[:foo] = { value: 'bar', max_age: 100 }
605
+ end
606
+ end
607
+
608
+ action = SetCookies.new
609
+ action.call({}) # => [200, {'Set-Cookie' => "foo=bar; max-age=100;"}, '...']
610
+ ```
611
+
612
+ ### Sessions
613
+
614
+ It has builtin support for Rack sessions:
615
+
616
+ ```ruby
617
+ require 'hanami/controller'
618
+ require 'hanami/action/session'
619
+
620
+ class ReadSessionFromRackEnv
621
+ include Hanami::Action
622
+ include Hanami::Action::Session
623
+
624
+ def call(params)
625
+ # ...
626
+ session[:age] # => '31'
627
+ end
628
+ end
629
+
630
+ action = ReadSessionFromRackEnv.new
631
+ action.call({ 'rack.session' => { 'age' => '31' }})
632
+ ```
633
+
634
+ Values can be set like a Hash:
635
+
636
+ ```ruby
637
+ require 'hanami/controller'
638
+ require 'hanami/action/session'
639
+
640
+ class SetSession
641
+ include Hanami::Action
642
+ include Hanami::Action::Session
643
+
644
+ def call(params)
645
+ # ...
646
+ session[:age] = 31
647
+ end
648
+ end
649
+
650
+ action = SetSession.new
651
+ action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."]
652
+ ```
653
+
654
+ Values can be removed like a Hash:
655
+
656
+ ```ruby
657
+ require 'hanami/controller'
658
+ require 'hanami/action/session'
659
+
660
+ class RemoveSession
661
+ include Hanami::Action
662
+ include Hanami::Action::Session
663
+
664
+ def call(params)
665
+ # ...
666
+ session[:age] = nil
667
+ end
668
+ end
669
+
670
+ action = RemoveSession.new
671
+ action.call({}) # => [200, {"Set-Cookie"=>"rack.session=..."}, "..."] it removes that value from the session
672
+ ```
673
+
674
+ While Hanami::Controller supports sessions natively, it's __session store agnostic__.
675
+ You have to specify the session store in your Rack middleware configuration (eg `config.ru`).
676
+
677
+ ```ruby
678
+ use Rack::Session::Cookie, secret: SecureRandom.hex(64)
679
+ run Show.new
680
+ ```
681
+
682
+ ### Http Cache
683
+
684
+ 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
685
+
686
+ You can easily set the Cache-Control header for your actions:
687
+
688
+ ```ruby
689
+ require 'hanami/controller'
690
+ require 'hanami/action/cache'
691
+
692
+ class HttpCacheController
693
+ include Hanami::Action
694
+ include Hanami::Action::Cache
695
+
696
+ cache_control :public, max_age: 600 # => Cache-Control: public, max-age=600
697
+
698
+ def call(params)
699
+ # ...
700
+ end
701
+ end
702
+ ```
703
+
704
+ Expires header can be specified using `expires` method:
705
+
706
+ ```ruby
707
+ require 'hanami/controller'
708
+ require 'hanami/action/cache'
709
+
710
+ class HttpCacheController
711
+ include Hanami::Action
712
+ include Hanami::Action::Cache
713
+
714
+ expires 60, :public, max_age: 600 # => Expires: Sun, 03 Aug 2014 17:47:02 GMT, Cache-Control: public, max-age=600
715
+
716
+ def call(params)
717
+ # ...
718
+ end
719
+ end
720
+ ```
721
+
722
+ ### Conditional Get
723
+
724
+ 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).
725
+
726
+ 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.
727
+
728
+ You can easily take advantage of Conditional Get using `#fresh` method:
729
+
730
+ ```ruby
731
+ require 'hanami/controller'
732
+ require 'hanami/action/cache'
733
+
734
+ class ConditionalGetController
735
+ include Hanami::Action
736
+ include Hanami::Action::Cache
737
+
738
+ def call(params)
739
+ # ...
740
+ fresh etag: @resource.cache_key
741
+ # => halt 304 with header IfNoneMatch = @resource.cache_key
742
+ end
743
+ end
744
+ ```
745
+
746
+ If `@resource.cache_key` is equal to `IfNoneMatch` header, then hanami will `halt 304`.
747
+
748
+ The same behavior is accomplished using `last_modified`:
749
+
750
+ ```ruby
751
+ require 'hanami/controller'
752
+ require 'hanami/action/cache'
753
+
754
+ class ConditionalGetController
755
+ include Hanami::Action
756
+ include Hanami::Action::Cache
757
+
758
+ def call(params)
759
+ # ...
760
+ fresh last_modified: @resource.update_at
761
+ # => halt 304 with header IfModifiedSince = @resource.update_at.httpdate
762
+ end
763
+ end
764
+ ```
765
+
766
+ If `@resource.update_at` is equal to `IfModifiedSince` header, then hanami will `halt 304`.
767
+
768
+ ### Redirect
769
+
770
+ If you need to redirect the client to another resource, use `#redirect_to`:
771
+
772
+ ```ruby
773
+ class Create
774
+ include Hanami::Action
775
+
776
+ def call(params)
777
+ # ...
778
+ redirect_to 'http://example.com/articles/23'
779
+ end
780
+ end
781
+
782
+ action = Create.new
783
+ action.call({ article: { title: 'Hello' }}) # => [302, {'Location' => '/articles/23'}, '']
784
+ ```
785
+
786
+ You can also redirect with a custom status code:
787
+
788
+ ```ruby
789
+ class Create
790
+ include Hanami::Action
791
+
792
+ def call(params)
793
+ # ...
794
+ redirect_to 'http://example.com/articles/23', status: 301
795
+ end
796
+ end
797
+
798
+ action = Create.new
799
+ action.call({ article: { title: 'Hello' }}) # => [301, {'Location' => '/articles/23'}, '']
800
+ ```
801
+
802
+ ### MIME Types
803
+
804
+ `Hanami::Action` automatically sets the `Content-Type` header, according to the request.
805
+
806
+ ```ruby
807
+ class Show
808
+ include Hanami::Action
809
+
810
+ def call(params)
811
+ end
812
+ end
813
+
814
+ action = Show.new
815
+
816
+ action.call({ 'HTTP_ACCEPT' => '*/*' }) # Content-Type "application/octet-stream"
817
+ action.format # :all
818
+
819
+ action.call({ 'HTTP_ACCEPT' => 'text/html' }) # Content-Type "text/html"
820
+ action.format # :html
821
+ ```
822
+
823
+ However, you can force this value:
824
+
825
+ ```ruby
826
+ class Show
827
+ include Hanami::Action
828
+
829
+ def call(params)
830
+ # ...
831
+ self.format = :json
832
+ end
833
+ end
834
+
835
+ action = Show.new
836
+
837
+ action.call({ 'HTTP_ACCEPT' => '*/*' }) # Content-Type "application/json"
838
+ action.format # :json
839
+
840
+ action.call({ 'HTTP_ACCEPT' => 'text/html' }) # Content-Type "application/json"
841
+ action.format # :json
842
+ ```
843
+
844
+ You can restrict the accepted MIME types:
845
+
846
+ ```ruby
847
+ class Show
848
+ include Hanami::Action
849
+ accept :html, :json
850
+
851
+ def call(params)
852
+ # ...
853
+ end
854
+ end
855
+
856
+ # When called with "\*/\*" => 200
857
+ # When called with "text/html" => 200
858
+ # When called with "application/json" => 200
859
+ # When called with "application/xml" => 406
860
+ ```
861
+
862
+ You can check if the requested MIME type is accepted by the client.
863
+
864
+ ```ruby
865
+ class Show
866
+ include Hanami::Action
867
+
868
+ def call(params)
869
+ # ...
870
+ # @_env['HTTP_ACCEPT'] # => 'text/html,application/xhtml+xml,application/xml;q=0.9'
871
+
872
+ accept?('text/html') # => true
873
+ accept?('application/xml') # => true
874
+ accept?('application/json') # => false
875
+ self.format # :html
876
+
877
+
878
+
879
+ # @_env['HTTP_ACCEPT'] # => '*/*'
880
+
881
+ accept?('text/html') # => true
882
+ accept?('application/xml') # => true
883
+ accept?('application/json') # => true
884
+ self.format # :html
885
+ end
886
+ end
887
+ ```
888
+
889
+ Hanami::Controller is shipped with an extensive list of the most common MIME types.
890
+ Also, you can register your own:
891
+
892
+ ```ruby
893
+ Hanami::Controller.configure do
894
+ format custom: 'application/custom'
895
+ end
896
+
897
+ class Index
898
+ include Hanami::Action
899
+
900
+ def call(params)
901
+ end
902
+ end
903
+
904
+ action = Index.new
905
+
906
+ action.call({ 'HTTP_ACCEPT' => 'application/custom' }) # => Content-Type 'application/custom'
907
+ action.format # => :custom
908
+
909
+ class Show
910
+ include Hanami::Action
911
+
912
+ def call(params)
913
+ # ...
914
+ self.format = :custom
915
+ end
916
+ end
917
+
918
+ action = Show.new
919
+
920
+ action.call({ 'HTTP_ACCEPT' => '*/*' }) # => Content-Type 'application/custom'
921
+ action.format # => :custom
922
+ ```
923
+
924
+ ### Streamed Responses
925
+
926
+ 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.
927
+
928
+ ```ruby
929
+ Hanami::Controller.configure do
930
+ format csv: 'text/csv'
931
+ middleware.use ::Rack::Chunked
932
+ end
933
+
934
+ class Csv
935
+ include Hanami::Action
936
+
937
+ def call(params)
938
+ self.format = :csv
939
+ self.body = Enumerator.new do |yielder|
940
+ yielder << csv_header
941
+
942
+ # Expensive operation is streamed as each line becomes available
943
+ csv_body.each_line do |line|
944
+ yielder << line
945
+ end
946
+ end
947
+ end
948
+ end
949
+ ```
950
+
951
+ Note:
952
+ * In development, Hanami' code reloading needs to be disabled for streaming to work. This is because `Shotgun` interferes with the streaming action. You can disable it like this `hanami server --code-reloading=false`
953
+ * Streaming does not work with WEBrick as it buffers its response. We recommend using `puma`, though you may find success with other servers
954
+
955
+ ### No rendering, please
956
+
957
+ Hanami::Controller is designed to be a pure HTTP endpoint, rendering belongs to other layers of MVC.
958
+ You can set the body directly (see [response](#response)), or use [Hanami::View](https://github.com/hanami/view).
959
+
960
+ ### Controllers
961
+
962
+ A Controller is nothing more than a logical group of actions: just a Ruby module.
963
+
964
+ ```ruby
965
+ module Articles
966
+ class Index
967
+ include Hanami::Action
968
+
969
+ # ...
970
+ end
971
+
972
+ class Show
973
+ include Hanami::Action
974
+
975
+ # ...
976
+ end
977
+ end
978
+
979
+ Articles::Index.new.call({})
980
+ ```
981
+
982
+ ### Hanami::Router integration
983
+
984
+ While Hanami::Router works great with this framework, Hanami::Controller doesn't depend on it.
985
+ You, the developer, are free to choose your own routing system.
986
+
987
+ But, if you use them together, the **only constraint is that an action must support _arity 0_ in its constructor**.
988
+ The following examples are valid constructors:
989
+
990
+ ```ruby
991
+ def initialize
992
+ end
993
+
994
+ def initialize(repository = Article)
995
+ end
996
+
997
+ def initialize(repository: Article)
998
+ end
999
+
1000
+ def initialize(options = {})
1001
+ end
1002
+
1003
+ def initialize(*args)
1004
+ end
1005
+ ```
1006
+
1007
+ __Please note that this is subject to change: we're working to remove this constraint.__
1008
+
1009
+ Hanami::Router supports lazy loading for controllers. While this policy can be a
1010
+ convenient fallback, you should know that it's the slower option. **Be sure of
1011
+ loading your controllers before you initialize the router.**
1012
+
1013
+
1014
+ ### Rack integration
1015
+
1016
+ Hanami::Controller is compatible with Rack. However, it doesn't mount any middleware.
1017
+ While a Hanami application's architecture is more web oriented, this framework is designed to build pure HTTP endpoints.
1018
+
1019
+ ### Rack middleware
1020
+
1021
+ Rack middleware can be configured globally in `config.ru`. However, consider that they often add
1022
+ unnecessary overhead for *all* endpoints that aren't direct users of all the configured middleware.
1023
+
1024
+ 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.
1025
+
1026
+ The solution is that an action can employ one or more Rack middleware, with `.use`.
1027
+
1028
+ ```ruby
1029
+ require 'hanami/controller'
1030
+
1031
+ module Sessions
1032
+ class Create
1033
+ include Hanami::Action
1034
+ use OmniAuth
1035
+
1036
+ def call(params)
1037
+ # ...
1038
+ end
1039
+ end
1040
+ end
1041
+ ```
1042
+
1043
+ ```ruby
1044
+ require 'hanami/controller'
1045
+
1046
+ module Sessions
1047
+ class Create
1048
+ include Hanami::Controller
1049
+
1050
+ use XMiddleware.new('x', 123)
1051
+ use YMiddleware.new
1052
+ use ZMiddleware
1053
+
1054
+ def call(params)
1055
+ # ...
1056
+ end
1057
+ end
1058
+ end
1059
+ ```
1060
+
1061
+ ### Configuration
1062
+
1063
+ Hanami::Controller can be configured with a DSL.
1064
+ It supports a few options:
1065
+
1066
+ ```ruby
1067
+ require 'hanami/controller'
1068
+
1069
+ Hanami::Controller.configure do
1070
+ # Handle exceptions with HTTP statuses (true) or don't catch them (false)
1071
+ # Argument: boolean, defaults to `true`
1072
+ #
1073
+ handle_exceptions true
1074
+
1075
+ # If the given exception is raised, return that HTTP status
1076
+ # It can be used multiple times
1077
+ # Argument: hash, empty by default
1078
+ #
1079
+ handle_exception ArgumentError => 404
1080
+
1081
+ # Register a format to MIME type mapping
1082
+ # Argument: hash, key: format symbol, value: MIME type string, empty by default
1083
+ #
1084
+ format custom: 'application/custom'
1085
+
1086
+ # Define a fallback format to detect in case of HTTP request with `Accept: */*`
1087
+ # If not defined here, it will return Rack's default: `application/octet-stream`
1088
+ # Argument: symbol, it should be already known. defaults to `nil`
1089
+ #
1090
+ default_request_format :html
1091
+
1092
+ # Define a default format to set as `Content-Type` header for response,
1093
+ # unless otherwise specified.
1094
+ # If not defined here, it will return Rack's default: `application/octet-stream`
1095
+ # Argument: symbol, it should be already known. defaults to `nil`
1096
+ #
1097
+ default_response_format :html
1098
+
1099
+ # Define a default charset to return in the `Content-Type` response header
1100
+ # If not defined here, it returns `utf-8`
1101
+ # Argument: string, defaults to `nil`
1102
+ #
1103
+ default_charset 'koi8-r'
1104
+
1105
+ # Configure the logic to be executed when Hanami::Action is included
1106
+ # This is useful to DRY code by having a single place where to configure
1107
+ # shared behaviors like authentication, sessions, cookies etc.
1108
+ # Argument: proc
1109
+ #
1110
+ prepare do
1111
+ include Hanami::Action::Sessions
1112
+ include MyAuthentication
1113
+ use SomeMiddleWare
1114
+
1115
+ before { authenticate! }
1116
+ end
1117
+ end
1118
+ ```
1119
+
1120
+ All of the global configurations can be overwritten at the controller level.
1121
+ Each controller and action has its own copy of the global configuration.
1122
+
1123
+ This means changes are inherited from the top to the bottom, but do not bubble back up.
1124
+
1125
+ ```ruby
1126
+ require 'hanami/controller'
1127
+
1128
+ Hanami::Controller.configure do
1129
+ handle_exception ArgumentError => 400
1130
+ end
1131
+
1132
+ module Articles
1133
+ class Create
1134
+ include Hanami::Action
1135
+
1136
+ configure do
1137
+ handle_exceptions false
1138
+ end
1139
+
1140
+ def call(params)
1141
+ raise ArgumentError
1142
+ end
1143
+ end
1144
+ end
1145
+
1146
+ module Users
1147
+ class Create
1148
+ include Hanami::Action
1149
+
1150
+ def call(params)
1151
+ raise ArgumentError
1152
+ end
1153
+ end
1154
+ end
1155
+
1156
+ Users::Create.new.call({}) # => HTTP 400
1157
+
1158
+ Articles::Create.new.call({})
1159
+ # => raises ArgumentError because we set handle_exceptions to false
1160
+ ```
1161
+
1162
+ ### Thread safety
1163
+
1164
+ An Action is **mutable**. When used without Hanami::Router, be sure to instantiate an
1165
+ action for each request. The same advice applies when using
1166
+ Hanami::Router but NOT routing to `mycontroller#myaction` but instead
1167
+ routing direct to a class.
1168
+
1169
+ ```ruby
1170
+ # config.ru
1171
+ require 'hanami/controller'
1172
+
1173
+ class Action
1174
+ include Hanami::Action
1175
+
1176
+ def self.call(env)
1177
+ new.call(env)
1178
+ end
1179
+
1180
+ def call(params)
1181
+ self.body = object_id.to_s
1182
+ end
1183
+ end
1184
+
1185
+ run Action
1186
+ ```
1187
+
1188
+ Hanami::Controller heavely depends on class configuration. To ensure immutability
1189
+ in deployment environments, use `Hanami::Controller.load!`.
1190
+
1191
+ ## Versioning
1192
+
1193
+ __Hanami::Controller__ uses [Semantic Versioning 2.0.0](http://semver.org)
32
1194
 
33
1195
  ## Contributing
34
1196
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/hanami-controller.
1197
+ 1. Fork it
1198
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
1199
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
1200
+ 4. Push to the branch (`git push origin my-new-feature`)
1201
+ 5. Create new Pull Request
1202
+
1203
+ ## Copyright
1204
+
1205
+ Copyright © 2014-2016 Luca Guidi – Released under MIT License
36
1206
 
1207
+ This project was formerly known as Lotus (`lotus-controller`).