hanami-controller 1.3.2 → 2.0.0.alpha3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +299 -537
  5. data/hanami-controller.gemspec +5 -4
  6. data/lib/hanami/action/application_action.rb +112 -0
  7. data/lib/hanami/action/application_configuration/cookies.rb +29 -0
  8. data/lib/hanami/action/application_configuration/sessions.rb +46 -0
  9. data/lib/hanami/action/application_configuration.rb +92 -0
  10. data/lib/hanami/action/base_params.rb +2 -2
  11. data/lib/hanami/action/cache/cache_control.rb +4 -4
  12. data/lib/hanami/action/cache/conditional_get.rb +3 -1
  13. data/lib/hanami/action/cache/directives.rb +1 -1
  14. data/lib/hanami/action/cache/expires.rb +3 -3
  15. data/lib/hanami/action/cache.rb +1 -139
  16. data/lib/hanami/action/configuration.rb +428 -0
  17. data/lib/hanami/action/cookie_jar.rb +3 -3
  18. data/lib/hanami/action/cookies.rb +3 -62
  19. data/lib/hanami/action/csrf_protection.rb +214 -0
  20. data/lib/hanami/action/flash.rb +102 -207
  21. data/lib/hanami/action/glue.rb +5 -31
  22. data/lib/hanami/action/halt.rb +12 -0
  23. data/lib/hanami/action/mime.rb +78 -485
  24. data/lib/hanami/action/params.rb +2 -2
  25. data/lib/hanami/action/rack/file.rb +1 -1
  26. data/lib/hanami/action/request.rb +30 -20
  27. data/lib/hanami/action/response.rb +193 -0
  28. data/lib/hanami/action/session.rb +11 -128
  29. data/lib/hanami/action/standalone_action.rb +579 -0
  30. data/lib/hanami/action/validatable.rb +1 -1
  31. data/lib/hanami/action/view_name_inferrer.rb +46 -0
  32. data/lib/hanami/action.rb +129 -73
  33. data/lib/hanami/controller/version.rb +1 -1
  34. data/lib/hanami/controller.rb +0 -227
  35. data/lib/hanami/http/status.rb +2 -2
  36. metadata +45 -27
  37. data/lib/hanami/action/callable.rb +0 -92
  38. data/lib/hanami/action/callbacks.rb +0 -214
  39. data/lib/hanami/action/configurable.rb +0 -50
  40. data/lib/hanami/action/exposable/guard.rb +0 -104
  41. data/lib/hanami/action/exposable.rb +0 -126
  42. data/lib/hanami/action/head.rb +0 -121
  43. data/lib/hanami/action/rack/callable.rb +0 -47
  44. data/lib/hanami/action/rack.rb +0 -399
  45. data/lib/hanami/action/redirect.rb +0 -59
  46. data/lib/hanami/action/throwable.rb +0 -196
  47. data/lib/hanami/controller/configuration.rb +0 -763
  48. data/lib/hanami-controller.rb +0 -1
@@ -1,47 +0,0 @@
1
- module Hanami
2
- module Action
3
- module Rack
4
- module Callable
5
- # Callable module for actions. With this module, actions with middlewares
6
- # will be able to work with rack builder.
7
- #
8
- # @param env [Hash] the full Rack env or the params. This value may vary,
9
- # see the examples below.
10
- #
11
- # @since 0.4.0
12
- # @api private
13
- #
14
- # @see Hanami::Action::Rack::ClassMethods#rack_builder
15
- # @see Hanami::Action::Rack::ClassMethods#use
16
- #
17
- # @example
18
- # require 'hanami/controller'
19
- #
20
- # class MyMiddleware
21
- # def initialize(app)
22
- # @app = app
23
- # end
24
- #
25
- # def call(env)
26
- # #...
27
- # end
28
- # end
29
- #
30
- # class Show
31
- # include Hanami::Action
32
- # use MyMiddleware
33
- #
34
- # def call(params)
35
- # # ...
36
- # puts params # => { id: 23 } extracted from Rack env
37
- # end
38
- # end
39
- #
40
- # Show.respond_to?(:call) # => true
41
- def call(env)
42
- rack_builder.call(env)
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,399 +0,0 @@
1
- require 'securerandom'
2
- require 'hanami/action/request'
3
- require 'hanami/action/base_params'
4
- require 'hanami/action/rack/callable'
5
- require 'hanami/action/rack/file'
6
- require 'hanami/utils/deprecation'
7
-
8
- module Hanami
9
- module Action
10
- # Rack integration API
11
- #
12
- # @since 0.1.0
13
- module Rack
14
- # Rack SPEC response code
15
- #
16
- # @since 1.0.0
17
- # @api private
18
- RESPONSE_CODE = 0
19
-
20
- # Rack SPEC response headers
21
- #
22
- # @since 1.0.0
23
- # @api private
24
- RESPONSE_HEADERS = 1
25
-
26
- # Rack SPEC response body
27
- #
28
- # @since 1.0.0
29
- # @api private
30
- RESPONSE_BODY = 2
31
-
32
- # The default HTTP response code
33
- #
34
- # @since 0.1.0
35
- # @api private
36
- DEFAULT_RESPONSE_CODE = 200
37
-
38
- # Not Found
39
- #
40
- # @since 1.0.0
41
- # @api private
42
- NOT_FOUND = 404
43
-
44
- # The default Rack response body
45
- #
46
- # @since 0.1.0
47
- # @api private
48
- DEFAULT_RESPONSE_BODY = []
49
-
50
- # The default HTTP Request ID length
51
- #
52
- # @since 0.3.0
53
- # @api private
54
- #
55
- # @see Hanami::Action::Rack#request_id
56
- DEFAULT_REQUEST_ID_LENGTH = 16
57
-
58
- # The request method
59
- #
60
- # @since 0.3.2
61
- # @api private
62
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
63
-
64
- # The Content-Length HTTP header
65
- #
66
- # @since 1.0.0
67
- # @api private
68
- CONTENT_LENGTH = 'Content-Length'.freeze
69
-
70
- # The non-standard HTTP header to pass the control over when a resource
71
- # cannot be found by the current endpoint
72
- #
73
- # @since 1.0.0
74
- # @api private
75
- X_CASCADE = 'X-Cascade'.freeze
76
-
77
- # HEAD request
78
- #
79
- # @since 0.3.2
80
- # @api private
81
- HEAD = 'HEAD'.freeze
82
-
83
- # The key that returns router parsed body from the Rack env
84
- ROUTER_PARSED_BODY = 'router.parsed_body'.freeze
85
-
86
- # Override Ruby's hook for modules.
87
- # It includes basic Hanami::Action modules to the given class.
88
- #
89
- # @param base [Class] the target action
90
- #
91
- # @since 0.1.0
92
- # @api private
93
- #
94
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
95
- def self.included(base)
96
- base.class_eval do
97
- extend ClassMethods
98
- prepend InstanceMethods
99
- end
100
- end
101
-
102
- # @api private
103
- module ClassMethods
104
- # Build rack builder
105
- #
106
- # @return [Rack::Builder]
107
- # @api private
108
- def rack_builder
109
- @rack_builder ||= begin
110
- extend Hanami::Action::Rack::Callable
111
- rack_builder = ::Rack::Builder.new
112
- rack_builder.run ->(env) { self.new.call(env) }
113
- rack_builder
114
- end
115
- end
116
-
117
- # Use a Rack middleware
118
- #
119
- # The middleware will be used as it is.
120
- #
121
- # At the runtime, the middleware be invoked with the raw Rack env.
122
- #
123
- # Multiple middlewares can be employed, just by using multiple times
124
- # this method.
125
- #
126
- # @param middleware [#call] A Rack middleware
127
- # @param args [Array] Array arguments for middleware
128
- #
129
- # @since 0.2.0
130
- #
131
- # @see Hanami::Action::Callbacks::ClassMethods#before
132
- #
133
- # @example Middleware
134
- # require 'hanami/controller'
135
- #
136
- # module Sessions
137
- # class Create
138
- # include Hanami::Action
139
- # use OmniAuth
140
- #
141
- # def call(params)
142
- # # ...
143
- # end
144
- # end
145
- # end
146
- def use(middleware, *args, &block)
147
- rack_builder.use middleware, *args, &block
148
- end
149
-
150
- # Returns the class which defines the params
151
- #
152
- # Returns the class which has been provided to define the
153
- # params. By default this will be Hanami::Action::Params.
154
- #
155
- # @return [Class] A params class (when whitelisted) or
156
- # Hanami::Action::Params
157
- #
158
- # @api private
159
- # @since 0.7.0
160
- def params_class
161
- @params_class ||= BaseParams
162
- end
163
- end
164
-
165
- # @since 0.7.0
166
- # @api private
167
- module InstanceMethods
168
- # @since 0.7.0
169
- # @api private
170
- def initialize(*)
171
- super
172
- @_status = nil
173
- @_body = nil
174
- end
175
- end
176
-
177
- protected
178
- # Gets the headers from the response
179
- #
180
- # @return [Hash] the HTTP headers from the response
181
- #
182
- # @since 0.1.0
183
- #
184
- # @example
185
- # require 'hanami/controller'
186
- #
187
- # class Show
188
- # include Hanami::Action
189
- #
190
- # def call(params)
191
- # # ...
192
- # self.headers # => { ... }
193
- # self.headers.merge!({'X-Custom' => 'OK'})
194
- # end
195
- # end
196
- def headers
197
- @headers
198
- end
199
-
200
- # Returns a serialized Rack response (Array), according to the current
201
- # status code, headers, and body.
202
- #
203
- # @return [Array] the serialized response
204
- #
205
- # @since 0.1.0
206
- # @api private
207
- #
208
- # @see Hanami::Action::Rack::DEFAULT_RESPONSE_CODE
209
- # @see Hanami::Action::Rack::DEFAULT_RESPONSE_BODY
210
- # @see Hanami::Action::Rack#status=
211
- # @see Hanami::Action::Rack#headers
212
- # @see Hanami::Action::Rack#body=
213
- def response
214
- [ @_status || DEFAULT_RESPONSE_CODE, headers, @_body || DEFAULT_RESPONSE_BODY.dup ]
215
- end
216
-
217
- # Calculates an unique ID for the current request
218
- #
219
- # @return [String] The unique ID
220
- #
221
- # @since 0.3.0
222
- def request_id
223
- # FIXME make this number configurable and document the probabilities of clashes
224
- @request_id ||= SecureRandom.hex(DEFAULT_REQUEST_ID_LENGTH)
225
- end
226
-
227
- # Returns a Hanami specialized rack request
228
- #
229
- # @return [Hanami::Action::Request] The request
230
- #
231
- # @since 0.3.1
232
- #
233
- # @example
234
- # require 'hanami/controller'
235
- #
236
- # class Create
237
- # include Hanami::Action
238
- #
239
- # def call(params)
240
- # ip = request.ip
241
- # secure = request.ssl?
242
- # end
243
- # end
244
- def request
245
- @request ||= ::Hanami::Action::Request.new(@_env)
246
- end
247
-
248
- # Return parsed request body
249
- #
250
- # @deprecated
251
- def parsed_request_body
252
- Hanami::Utils::Deprecation.new('#parsed_request_body is deprecated and it will be removed in future versions')
253
- @_env.fetch(ROUTER_PARSED_BODY, nil)
254
- end
255
-
256
- private
257
-
258
- # Sets the HTTP status code for the response
259
- #
260
- # @param status [Fixnum] an HTTP status code
261
- # @return [void]
262
- #
263
- # @since 0.1.0
264
- #
265
- # @example
266
- # require 'hanami/controller'
267
- #
268
- # class Create
269
- # include Hanami::Action
270
- #
271
- # def call(params)
272
- # # ...
273
- # self.status = 201
274
- # end
275
- # end
276
- def status=(status)
277
- @_status = status
278
- end
279
-
280
- # Sets the body of the response
281
- #
282
- # @param body [String] the body of the response
283
- # @return [void]
284
- #
285
- # @since 0.1.0
286
- #
287
- # @example
288
- # require 'hanami/controller'
289
- #
290
- # class Show
291
- # include Hanami::Action
292
- #
293
- # def call(params)
294
- # # ...
295
- # self.body = 'Hi!'
296
- # end
297
- # end
298
- def body=(body)
299
- body = Array(body) unless body.respond_to?(:each)
300
- @_body = body
301
- end
302
-
303
- # Send a file as response.
304
- # <tt>This method only sends files from the public directory</tt>
305
- #
306
- # It automatically handle the following cases:
307
- #
308
- # * <tt>Content-Type</tt> and <tt>Content-Length</tt>
309
- # * File Not found (returns a 404)
310
- # * Conditional GET (via <tt>If-Modified-Since</tt> header)
311
- # * Range requests (via <tt>Range</tt> header)
312
- #
313
- # @param path [String, Pathname] the body of the response
314
- # @return [void]
315
- #
316
- # @since 0.4.3
317
- #
318
- # @example
319
- # require 'hanami/controller'
320
- #
321
- # class Show
322
- # include Hanami::Action
323
- #
324
- # def call(params)
325
- # # ...
326
- # send_file Pathname.new('path/to/file')
327
- # end
328
- # end
329
- def send_file(path)
330
- _send_file(
331
- File.new(path, self.class.configuration.public_directory).call(@_env)
332
- )
333
- end
334
-
335
- # Send a file as response from anywhere in the file system.
336
- #
337
- # @see Hanami::Action::Rack#send_file
338
- #
339
- # @param path [String, Pathname] path to the file to be sent
340
- # @return [void]
341
- #
342
- # @since 1.0.0
343
- #
344
- # @example
345
- # require 'hanami/controller'
346
- #
347
- # class Show
348
- # include Hanami::Action
349
- #
350
- # def call(params)
351
- # # ...
352
- # unsafe_send_file Pathname.new('/tmp/path/to/file')
353
- # end
354
- # end
355
- def unsafe_send_file(path)
356
- directory = self.class.configuration.root_directory if Pathname.new(path).relative?
357
-
358
- _send_file(
359
- File.new(path, directory).call(@_env)
360
- )
361
- end
362
-
363
- # Check if the current request is a HEAD
364
- #
365
- # @return [TrueClass,FalseClass] the result of the check
366
- #
367
- # @since 0.3.2
368
- def head?
369
- request_method == HEAD
370
- end
371
-
372
- # NOTE: <tt>Hanami::Action::CSRFProtection</tt> (<tt>hanamirb</tt> gem) depends on this.
373
- #
374
- # @api private
375
- # @since 0.4.4
376
- def request_method
377
- @_env[REQUEST_METHOD]
378
- end
379
-
380
- # @since 1.0.0
381
- # @api private
382
- def _send_file(response)
383
- headers.merge!(response[RESPONSE_HEADERS])
384
-
385
- if response[RESPONSE_CODE] == NOT_FOUND
386
- headers.delete(X_CASCADE)
387
- headers.delete(CONTENT_LENGTH)
388
- halt NOT_FOUND
389
- else
390
- # FIXME: this is a fix for https://github.com/hanami/controller/issues/240
391
- # It's here to maintain the backward compat with 1.1, as we can't remove `#halt`
392
- # We should review the workflow for 2.0, because I don't like callbacks to be referenced from here.
393
- _run_after_callbacks(params)
394
- halt response[RESPONSE_CODE], response[RESPONSE_BODY]
395
- end
396
- end
397
- end
398
- end
399
- end
@@ -1,59 +0,0 @@
1
- module Hanami
2
- module Action
3
- # HTTP redirect API
4
- #
5
- # @since 0.1.0
6
- module Redirect
7
- # The HTTP header for redirects
8
- #
9
- # @since 0.2.0
10
- # @api private
11
- LOCATION = 'Location'.freeze
12
-
13
- private
14
-
15
- # Redirect to the given URL and halt the request
16
- #
17
- # @param url [String] the destination URL
18
- # @param status [Fixnum] the http code
19
- #
20
- # @since 0.1.0
21
- #
22
- # @see Hanami::Action::Throwable#halt
23
- #
24
- # @example With default status code (302)
25
- # require 'hanami/controller'
26
- #
27
- # class Create
28
- # include Hanami::Action
29
- #
30
- # def call(params)
31
- # # ...
32
- # redirect_to 'http://example.com/articles/23'
33
- # end
34
- # end
35
- #
36
- # action = Create.new
37
- # action.call({}) # => [302, {'Location' => '/articles/23'}, '']
38
- #
39
- # @example With custom status code
40
- # require 'hanami/controller'
41
- #
42
- # class Create
43
- # include Hanami::Action
44
- #
45
- # def call(params)
46
- # # ...
47
- # redirect_to 'http://example.com/articles/23', status: 301
48
- # end
49
- # end
50
- #
51
- # action = Create.new
52
- # action.call({}) # => [301, {'Location' => '/articles/23'}, '']
53
- def redirect_to(url, status: 302)
54
- headers[LOCATION] = ::String.new(url)
55
- halt(status)
56
- end
57
- end
58
- end
59
- end
@@ -1,196 +0,0 @@
1
- require 'hanami/utils/class_attribute'
2
- require 'hanami/http/status'
3
-
4
- module Hanami
5
- module Action
6
- # Throw API
7
- #
8
- # @since 0.1.0
9
- #
10
- # @see Hanami::Action::Throwable::ClassMethods#handle_exception
11
- # @see Hanami::Action::Throwable#halt
12
- # @see Hanami::Action::Throwable#status
13
- module Throwable
14
- # @since 0.2.0
15
- # @api private
16
- RACK_ERRORS = 'rack.errors'.freeze
17
-
18
- # This isn't part of Rack SPEC
19
- #
20
- # Exception notifiers use <tt>rack.exception</tt> instead of
21
- # <tt>rack.errors</tt>, so we need to support it.
22
- #
23
- # @since 0.5.0
24
- # @api private
25
- #
26
- # @see Hanami::Action::Throwable::RACK_ERRORS
27
- # @see http://www.rubydoc.info/github/rack/rack/file/SPEC#The_Error_Stream
28
- # @see https://github.com/hanami/controller/issues/133
29
- RACK_EXCEPTION = 'rack.exception'.freeze
30
-
31
- # @since 0.1.0
32
- # @api private
33
- def self.included(base)
34
- base.extend ClassMethods
35
- end
36
-
37
- # Throw API class methods
38
- #
39
- # @since 0.1.0
40
- # @api private
41
- module ClassMethods
42
- private
43
-
44
- # Handle the given exception with an HTTP status code.
45
- #
46
- # When the exception is raise during #call execution, it will be
47
- # translated into the associated HTTP status.
48
- #
49
- # This is a fine grained control, for a global configuration see
50
- # Hanami::Action.handled_exceptions
51
- #
52
- # @param exception [Hash] the exception class must be the key and the
53
- # HTTP status the value of the hash
54
- #
55
- # @since 0.1.0
56
- #
57
- # @see Hanami::Action.handled_exceptions
58
- #
59
- # @example
60
- # require 'hanami/controller'
61
- #
62
- # class Show
63
- # include Hanami::Action
64
- # handle_exception RecordNotFound => 404
65
- #
66
- # def call(params)
67
- # # ...
68
- # raise RecordNotFound.new
69
- # end
70
- # end
71
- #
72
- # Show.new.call({id: 1}) # => [404, {}, ['Not Found']]
73
- def handle_exception(exception)
74
- configuration.handle_exception(exception)
75
- end
76
- end
77
-
78
- protected
79
-
80
- # Halt the action execution with the given HTTP status code and message.
81
- #
82
- # When used, the execution of a callback or of an action is interrupted
83
- # and the control returns to the framework, that decides how to handle
84
- # the event.
85
- #
86
- # If a message is provided, it sets the response body with the message.
87
- # Otherwise, it sets the response body with the default message associated
88
- # to the code (eg 404 will set `"Not Found"`).
89
- #
90
- # @param code [Fixnum] a valid HTTP status code
91
- # @param message [String] the response body
92
- #
93
- # @since 0.2.0
94
- #
95
- # @see Hanami::Controller#handled_exceptions
96
- # @see Hanami::Action::Throwable#handle_exception
97
- # @see Hanami::Http::Status:ALL
98
- #
99
- # @example Basic usage
100
- # require 'hanami/controller'
101
- #
102
- # class Show
103
- # def call(params)
104
- # halt 404
105
- # end
106
- # end
107
- #
108
- # # => [404, {}, ["Not Found"]]
109
- #
110
- # @example Custom message
111
- # require 'hanami/controller'
112
- #
113
- # class Show
114
- # def call(params)
115
- # halt 404, "This is not the droid you're looking for."
116
- # end
117
- # end
118
- #
119
- # # => [404, {}, ["This is not the droid you're looking for."]]
120
- def halt(code, message = nil)
121
- message ||= Http::Status.message_for(code)
122
- status(code, message)
123
-
124
- throw :halt
125
- end
126
-
127
- # Sets the given code and message for the response
128
- #
129
- # @param code [Fixnum] a valid HTTP status code
130
- # @param message [String] the response body
131
- #
132
- # @since 0.1.0
133
- # @see Hanami::Http::Status:ALL
134
- def status(code, message)
135
- self.status = code
136
- self.body = message
137
- end
138
-
139
- private
140
- # @since 0.1.0
141
- # @api private
142
- def _rescue
143
- catch :halt do
144
- begin
145
- yield
146
- rescue => exception
147
- _reference_in_rack_errors(exception)
148
- _handle_exception(exception)
149
- end
150
- end
151
- end
152
-
153
- # @since 0.2.0
154
- # @api private
155
- def _reference_in_rack_errors(exception)
156
- return if configuration.handled_exception?(exception)
157
-
158
- @_env[RACK_EXCEPTION] = exception
159
-
160
- if errors = @_env[RACK_ERRORS]
161
- errors.write(_dump_exception(exception))
162
- errors.flush
163
- end
164
- end
165
-
166
- # @since 0.2.0
167
- # @api private
168
- def _dump_exception(exception)
169
- [[exception.class, exception.message].compact.join(": "), *exception.backtrace].join("\n\t")
170
- end
171
-
172
- # @since 0.1.0
173
- # @api private
174
- def _handle_exception(exception)
175
- raise unless configuration.handle_exceptions
176
-
177
- instance_exec(
178
- exception,
179
- &_exception_handler(exception)
180
- )
181
- end
182
-
183
- # @since 0.3.0
184
- # @api private
185
- def _exception_handler(exception)
186
- handler = configuration.exception_handler(exception)
187
-
188
- if respond_to?(handler.to_s, true)
189
- method(handler)
190
- else
191
- ->(ex) { halt handler }
192
- end
193
- end
194
- end
195
- end
196
- end