hanami-controller 1.3.0 → 2.0.0.alpha2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +83 -0
- data/LICENSE.md +1 -1
- data/README.md +297 -538
- data/hanami-controller.gemspec +6 -5
- data/lib/hanami/action.rb +129 -73
- data/lib/hanami/action/application_action.rb +111 -0
- data/lib/hanami/action/application_configuration.rb +92 -0
- data/lib/hanami/action/application_configuration/cookies.rb +29 -0
- data/lib/hanami/action/application_configuration/sessions.rb +46 -0
- data/lib/hanami/action/base_params.rb +2 -2
- data/lib/hanami/action/cache.rb +1 -139
- data/lib/hanami/action/cache/cache_control.rb +4 -4
- data/lib/hanami/action/cache/conditional_get.rb +7 -2
- data/lib/hanami/action/cache/directives.rb +1 -1
- data/lib/hanami/action/cache/expires.rb +3 -3
- data/lib/hanami/action/configuration.rb +430 -0
- data/lib/hanami/action/cookie_jar.rb +3 -3
- data/lib/hanami/action/cookies.rb +3 -62
- data/lib/hanami/action/csrf_protection.rb +214 -0
- data/lib/hanami/action/flash.rb +102 -207
- data/lib/hanami/action/glue.rb +5 -31
- data/lib/hanami/action/halt.rb +12 -0
- data/lib/hanami/action/mime.rb +78 -485
- data/lib/hanami/action/params.rb +3 -3
- data/lib/hanami/action/rack/file.rb +1 -1
- data/lib/hanami/action/request.rb +30 -20
- data/lib/hanami/action/response.rb +193 -0
- data/lib/hanami/action/session.rb +11 -128
- data/lib/hanami/action/standalone_action.rb +581 -0
- data/lib/hanami/action/validatable.rb +2 -2
- data/lib/hanami/action/view_name_inferrer.rb +46 -0
- data/lib/hanami/controller.rb +0 -227
- data/lib/hanami/controller/version.rb +1 -1
- data/lib/hanami/http/status.rb +2 -2
- metadata +47 -30
- data/lib/hanami-controller.rb +0 -1
- data/lib/hanami/action/callable.rb +0 -92
- data/lib/hanami/action/callbacks.rb +0 -214
- data/lib/hanami/action/configurable.rb +0 -50
- data/lib/hanami/action/exposable.rb +0 -126
- data/lib/hanami/action/exposable/guard.rb +0 -104
- data/lib/hanami/action/head.rb +0 -121
- data/lib/hanami/action/rack.rb +0 -399
- data/lib/hanami/action/rack/callable.rb +0 -47
- data/lib/hanami/action/redirect.rb +0 -59
- data/lib/hanami/action/throwable.rb +0 -196
- data/lib/hanami/controller/configuration.rb +0 -763
@@ -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,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
|
@@ -1,763 +0,0 @@
|
|
1
|
-
require 'hanami/utils/class'
|
2
|
-
require 'hanami/utils/kernel'
|
3
|
-
require 'hanami/utils/string'
|
4
|
-
|
5
|
-
module Hanami
|
6
|
-
module Controller
|
7
|
-
# Configuration for the framework, controllers and actions.
|
8
|
-
#
|
9
|
-
# Hanami::Controller has its own global configuration that can be manipulated
|
10
|
-
# via `Hanami::Controller.configure`.
|
11
|
-
#
|
12
|
-
# Every time that `Hanami::Controller` and `Hanami::Action` are included, that
|
13
|
-
# global configuration is being copied to the recipient. The copy will
|
14
|
-
# inherit all the settings from the original, but all the subsequent changes
|
15
|
-
# aren't reflected from the parent to the children, and viceversa.
|
16
|
-
#
|
17
|
-
# This architecture allows to have a global configuration that capture the
|
18
|
-
# most common cases for an application, and let controllers and single
|
19
|
-
# actions to specify exceptions.
|
20
|
-
#
|
21
|
-
# @since 0.2.0
|
22
|
-
class Configuration
|
23
|
-
# Default HTTP code for server side errors
|
24
|
-
#
|
25
|
-
# @since 0.2.0
|
26
|
-
# @api private
|
27
|
-
DEFAULT_ERROR_CODE = 500
|
28
|
-
|
29
|
-
# Default public directory
|
30
|
-
#
|
31
|
-
# It serves as base root for file downloads
|
32
|
-
#
|
33
|
-
# @since 1.0.0
|
34
|
-
# @api private
|
35
|
-
DEFAULT_PUBLIC_DIRECTORY = 'public'.freeze
|
36
|
-
|
37
|
-
# Default Mime type to format mapping
|
38
|
-
#
|
39
|
-
# @since 0.2.0
|
40
|
-
# @api private
|
41
|
-
DEFAULT_FORMATS = {
|
42
|
-
'application/octet-stream' => :all,
|
43
|
-
'*/*' => :all,
|
44
|
-
'text/html' => :html
|
45
|
-
}.freeze
|
46
|
-
|
47
|
-
# Return a copy of the configuration of the framework instance associated
|
48
|
-
# with the given class.
|
49
|
-
#
|
50
|
-
# When multiple instances of Hanami::Controller are used in the same
|
51
|
-
# application, we want to make sure that a controller or an action will
|
52
|
-
# receive the expected configuration.
|
53
|
-
#
|
54
|
-
# @param base [Class, Module] a controller or an action
|
55
|
-
#
|
56
|
-
# @return [Hanami::Controller::Configuration] the configuration associated
|
57
|
-
# to the given class.
|
58
|
-
#
|
59
|
-
# @since 0.2.0
|
60
|
-
# @api private
|
61
|
-
#
|
62
|
-
# @example Direct usage of the framework
|
63
|
-
# require 'hanami/controller'
|
64
|
-
#
|
65
|
-
# class Show
|
66
|
-
# include Hanami::Action
|
67
|
-
# end
|
68
|
-
#
|
69
|
-
# Hanami::Controller::Configuration.for(Show)
|
70
|
-
# # => will duplicate from Hanami::Controller
|
71
|
-
#
|
72
|
-
# @example Multiple instances of the framework
|
73
|
-
# require 'hanami/controller'
|
74
|
-
#
|
75
|
-
# module MyApp
|
76
|
-
# Controller = Hanami::Controller.duplicate(self)
|
77
|
-
#
|
78
|
-
# module Controllers::Dashboard
|
79
|
-
# class Index
|
80
|
-
# include MyApp::Action
|
81
|
-
#
|
82
|
-
# def call(params)
|
83
|
-
# # ...
|
84
|
-
# end
|
85
|
-
# end
|
86
|
-
# end
|
87
|
-
# end
|
88
|
-
#
|
89
|
-
# class Show
|
90
|
-
# include Hanami::Action
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# Hanami::Controller::Configuration.for(Show)
|
94
|
-
# # => will duplicate from Hanami::Controller
|
95
|
-
#
|
96
|
-
# Hanami::Controller::Configuration.for(MyApp::Controllers::Dashboard)
|
97
|
-
# # => will duplicate from MyApp::Controller
|
98
|
-
def self.for(base)
|
99
|
-
namespace = Utils::String.namespace(base.name)
|
100
|
-
framework = Utils::Class.load("#{namespace}::Controller") || Utils::Class.load!('Hanami::Controller')
|
101
|
-
framework.configuration.duplicate
|
102
|
-
end
|
103
|
-
|
104
|
-
# Initialize a configuration instance
|
105
|
-
#
|
106
|
-
# @return [Hanami::Controller::Configuration] a new configuration's
|
107
|
-
# instance
|
108
|
-
#
|
109
|
-
# @since 0.2.0
|
110
|
-
def initialize
|
111
|
-
reset!
|
112
|
-
end
|
113
|
-
|
114
|
-
# @attr_writer handle_exceptions [TrueClass,FalseClass] Handle exceptions
|
115
|
-
# with an HTTP status or leave them uncaught
|
116
|
-
#
|
117
|
-
# @since 0.2.0
|
118
|
-
#
|
119
|
-
# @return void
|
120
|
-
#
|
121
|
-
# @see Hanami::Controller::Configuration#handle_exceptions
|
122
|
-
attr_writer :handle_exceptions
|
123
|
-
|
124
|
-
# Handle exceptions with an HTTP status or let them uncaught
|
125
|
-
#
|
126
|
-
# If this value is set to `true`, the configured exceptions will return
|
127
|
-
# the specified HTTP status, the rest of them with `500`.
|
128
|
-
#
|
129
|
-
# If this value is set to `false`, the exceptions won't be caught.
|
130
|
-
#
|
131
|
-
# This is part of a DSL, for this reason when this method is called with
|
132
|
-
# an argument, it will set the corresponding instance variable. When
|
133
|
-
# called without, it will return the already set value, or the default.
|
134
|
-
#
|
135
|
-
# @overload handle_exceptions(value)
|
136
|
-
# Sets the given value
|
137
|
-
# @param value [TrueClass, FalseClass] true or false, default to true
|
138
|
-
#
|
139
|
-
# @overload handle_exceptions
|
140
|
-
# Gets the value
|
141
|
-
# @return [TrueClass, FalseClass]
|
142
|
-
#
|
143
|
-
# @since 0.2.0
|
144
|
-
#
|
145
|
-
# @see Hanami::Controller::Configuration#handle_exception
|
146
|
-
# @see Hanami::Controller#configure
|
147
|
-
# @see Hanami::Action::Throwable
|
148
|
-
# @see http://httpstatus.es/500
|
149
|
-
#
|
150
|
-
# @example Getting the value
|
151
|
-
# require 'hanami/controller'
|
152
|
-
#
|
153
|
-
# Hanami::Controller.configuration.handle_exceptions # => true
|
154
|
-
#
|
155
|
-
# @example Setting the value
|
156
|
-
# require 'hanami/controller'
|
157
|
-
#
|
158
|
-
# Hanami::Controller.configure do
|
159
|
-
# handle_exceptions false
|
160
|
-
# end
|
161
|
-
def handle_exceptions(value = nil)
|
162
|
-
if value.nil?
|
163
|
-
@handle_exceptions
|
164
|
-
else
|
165
|
-
@handle_exceptions = value
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
# Specify how to handle an exception with an HTTP status
|
170
|
-
#
|
171
|
-
# Raised exceptions will return the configured HTTP status, only if
|
172
|
-
# `handled_exceptions` is set on `true`.
|
173
|
-
#
|
174
|
-
# @param exception [Hash] the exception class must be the key and the HTTP
|
175
|
-
# status the value
|
176
|
-
#
|
177
|
-
# @since 0.2.0
|
178
|
-
#
|
179
|
-
# @see Hanami::Controller::Configuration#handle_exceptions
|
180
|
-
# @see Hanami::Controller#configure
|
181
|
-
# @see Hanami::Action::Throwable
|
182
|
-
#
|
183
|
-
# @example
|
184
|
-
# require 'hanami/controller'
|
185
|
-
#
|
186
|
-
# Hanami::Controller.configure do
|
187
|
-
# handle_exception ArgumentError => 400
|
188
|
-
# end
|
189
|
-
def handle_exception(exception)
|
190
|
-
@handled_exceptions.merge!(exception)
|
191
|
-
_sort_handled_exceptions!
|
192
|
-
end
|
193
|
-
|
194
|
-
# Return a callable handler for the given exception
|
195
|
-
#
|
196
|
-
# @param exception [Exception] an exception
|
197
|
-
#
|
198
|
-
# @since 0.3.0
|
199
|
-
# @api private
|
200
|
-
#
|
201
|
-
# @see Hanami::Controller::Configuration#handle_exception
|
202
|
-
def exception_handler(exception)
|
203
|
-
exception_handler_for(exception) || DEFAULT_ERROR_CODE
|
204
|
-
end
|
205
|
-
|
206
|
-
# Check if the given exception is handled.
|
207
|
-
#
|
208
|
-
# @param exception [Exception] an exception
|
209
|
-
#
|
210
|
-
# @since 0.3.2
|
211
|
-
# @api private
|
212
|
-
#
|
213
|
-
# @see Hanami::Controller::Configuration#handle_exception
|
214
|
-
def handled_exception?(exception)
|
215
|
-
handled_exceptions &&
|
216
|
-
!exception_handler_for(exception).nil?
|
217
|
-
end
|
218
|
-
|
219
|
-
# Finds configured handler for given exception, or nil if not found.
|
220
|
-
#
|
221
|
-
# @param exception [Exception] an exception
|
222
|
-
#
|
223
|
-
# @since 1.0.0
|
224
|
-
# @api private
|
225
|
-
#
|
226
|
-
# @see Hanami::Controller::Configuration#handle_exception
|
227
|
-
def exception_handler_for(exception)
|
228
|
-
@handled_exceptions.each do |exception_class, handler|
|
229
|
-
return handler if exception.kind_of?(exception_class)
|
230
|
-
end
|
231
|
-
|
232
|
-
nil
|
233
|
-
end
|
234
|
-
|
235
|
-
# Specify which is the default action module to be included when we use
|
236
|
-
# the `Hanami::Controller.action` method.
|
237
|
-
#
|
238
|
-
# This setting is useful when we use multiple instances of the framework
|
239
|
-
# in the same process, so we want to ensure that the actions will include
|
240
|
-
# `MyApp::Action`, rather than `AnotherApp::Action`.
|
241
|
-
#
|
242
|
-
# If not set, the default value is `Hanami::Action`
|
243
|
-
#
|
244
|
-
# This is part of a DSL, for this reason when this method is called with
|
245
|
-
# an argument, it will set the corresponding instance variable. When
|
246
|
-
# called without, it will return the already set value, or the default.
|
247
|
-
#
|
248
|
-
# @overload action_module(value)
|
249
|
-
# Sets the given value
|
250
|
-
# @param value [Module] the module to be included in all the actions
|
251
|
-
#
|
252
|
-
# @overload action_module
|
253
|
-
# Gets the value
|
254
|
-
# @return [Module]
|
255
|
-
#
|
256
|
-
# @since 0.2.0
|
257
|
-
#
|
258
|
-
# @see Hanami::Controller::Dsl#action
|
259
|
-
# @see Hanami::Controller#duplicate
|
260
|
-
#
|
261
|
-
# @example Getting the value
|
262
|
-
# require 'hanami/controller'
|
263
|
-
#
|
264
|
-
# Hanami::Controller.configuration.action_module # => Hanami::Action
|
265
|
-
#
|
266
|
-
# @example Setting the value
|
267
|
-
# require 'hanami/controller'
|
268
|
-
#
|
269
|
-
# module MyAction
|
270
|
-
# end
|
271
|
-
#
|
272
|
-
# Hanami::Controller.configure do
|
273
|
-
# action_module MyAction
|
274
|
-
# end
|
275
|
-
#
|
276
|
-
# module Dashboard
|
277
|
-
# # It includes MyAction, instead of Hanami::Action
|
278
|
-
# class Index
|
279
|
-
# include MyAction
|
280
|
-
#
|
281
|
-
# def call(params)
|
282
|
-
# # ...
|
283
|
-
# end
|
284
|
-
# end
|
285
|
-
# end
|
286
|
-
#
|
287
|
-
# @example Duplicated framework
|
288
|
-
# require 'hanami/controller'
|
289
|
-
#
|
290
|
-
# module MyApp
|
291
|
-
# Controller = Hanami::Controller.duplicate(self)
|
292
|
-
#
|
293
|
-
# module Controllers::Dashboard
|
294
|
-
# include MyApp::Controller
|
295
|
-
#
|
296
|
-
# # It includes MyApp::Action, instead of Hanami::Action
|
297
|
-
# class Index
|
298
|
-
# include MyApp::Action
|
299
|
-
#
|
300
|
-
# def call(params)
|
301
|
-
# # ...
|
302
|
-
# end
|
303
|
-
# end
|
304
|
-
# end
|
305
|
-
# end
|
306
|
-
def action_module(value = nil)
|
307
|
-
if value.nil?
|
308
|
-
@action_module
|
309
|
-
else
|
310
|
-
@action_module = value
|
311
|
-
end
|
312
|
-
end
|
313
|
-
|
314
|
-
# Configure the logic to be executed when Hanami::Action is included
|
315
|
-
# This is useful to DRY code by having a single place where to configure
|
316
|
-
# shared behaviors like authentication, sessions, cookies etc.
|
317
|
-
#
|
318
|
-
# This method can be called multiple times.
|
319
|
-
#
|
320
|
-
# @param blk [Proc] the code block
|
321
|
-
#
|
322
|
-
# @return [void]
|
323
|
-
#
|
324
|
-
# @raise [ArgumentError] if called without passing a block
|
325
|
-
#
|
326
|
-
# @since 0.3.0
|
327
|
-
#
|
328
|
-
# @see Hanami::Controller.configure
|
329
|
-
# @see Hanami::Controller.duplicate
|
330
|
-
#
|
331
|
-
# @example Configure shared logic.
|
332
|
-
# require 'hanami/controller'
|
333
|
-
#
|
334
|
-
# Hanami::Controller.configure do
|
335
|
-
# prepare do
|
336
|
-
# include Hanami::Action::Session
|
337
|
-
# include MyAuthentication
|
338
|
-
# use SomeMiddleWare
|
339
|
-
#
|
340
|
-
# before { authenticate! }
|
341
|
-
# end
|
342
|
-
# end
|
343
|
-
#
|
344
|
-
# module Dashboard
|
345
|
-
# class Index
|
346
|
-
# # When Hanami::Action is included, it will:
|
347
|
-
# # * Include `Hanami::Action::Session` and `MyAuthentication`
|
348
|
-
# # * Configure to use `SomeMiddleWare`
|
349
|
-
# # * Configure a `before` callback that triggers `#authenticate!`
|
350
|
-
# include Hanami::Action
|
351
|
-
#
|
352
|
-
# def call(params)
|
353
|
-
# # ...
|
354
|
-
# end
|
355
|
-
# end
|
356
|
-
# end
|
357
|
-
def prepare(&blk)
|
358
|
-
if block_given?
|
359
|
-
@modules.push(blk)
|
360
|
-
else
|
361
|
-
raise ArgumentError.new('Please provide a block')
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
# Register a format
|
366
|
-
#
|
367
|
-
# @param hash [Hash] the symbol format must be the key and the mime type
|
368
|
-
# string must be the value of the hash
|
369
|
-
#
|
370
|
-
# @since 0.2.0
|
371
|
-
#
|
372
|
-
# @see Hanami::Action::Mime
|
373
|
-
#
|
374
|
-
# @example
|
375
|
-
# require 'hanami/controller'
|
376
|
-
#
|
377
|
-
# Hanami::Controller.configure do
|
378
|
-
# format custom: 'application/custom'
|
379
|
-
# end
|
380
|
-
#
|
381
|
-
# module Articles
|
382
|
-
# class Index
|
383
|
-
# include Hanami::Action
|
384
|
-
#
|
385
|
-
# def call(params)
|
386
|
-
# # ...
|
387
|
-
# end
|
388
|
-
# end
|
389
|
-
#
|
390
|
-
# class Show
|
391
|
-
# include Hanami::Action
|
392
|
-
#
|
393
|
-
# def call(params)
|
394
|
-
# # ...
|
395
|
-
# self.format = :custom
|
396
|
-
# end
|
397
|
-
# end
|
398
|
-
# end
|
399
|
-
#
|
400
|
-
# action = Articles::Index.new
|
401
|
-
#
|
402
|
-
# action.call({ 'HTTP_ACCEPT' => 'text/html' })
|
403
|
-
# # => Content-Type "text/html"
|
404
|
-
# action.format # => :html
|
405
|
-
#
|
406
|
-
# action.call({ 'HTTP_ACCEPT' => 'application/custom' })
|
407
|
-
# # => Content-Type "application/custom"
|
408
|
-
# action.format # => :custom
|
409
|
-
#
|
410
|
-
#
|
411
|
-
#
|
412
|
-
# action = Articles::Show.new
|
413
|
-
#
|
414
|
-
# action.call({ 'HTTP_ACCEPT' => 'text/html' })
|
415
|
-
# # => Content-Type "application/custom"
|
416
|
-
# action.format # => :custom
|
417
|
-
def format(hash)
|
418
|
-
symbol, mime_type = *Utils::Kernel.Array(hash)
|
419
|
-
|
420
|
-
@formats[Utils::Kernel.String(mime_type)] = Utils::Kernel.Symbol(symbol)
|
421
|
-
@mime_types = nil
|
422
|
-
end
|
423
|
-
|
424
|
-
# Return the configured format's MIME types
|
425
|
-
#
|
426
|
-
# @since 0.8.0
|
427
|
-
# @api private
|
428
|
-
#
|
429
|
-
# @see Hanami::Controller::Configuration#format
|
430
|
-
# @see Hanami::Action::Mime::MIME_TYPES
|
431
|
-
def mime_types
|
432
|
-
@mime_types ||= begin
|
433
|
-
((@formats.keys - DEFAULT_FORMATS.keys) +
|
434
|
-
Hanami::Action::Mime::MIME_TYPES.values).freeze
|
435
|
-
end
|
436
|
-
end
|
437
|
-
|
438
|
-
# Restrict the MIME types set only to the given set
|
439
|
-
#
|
440
|
-
# @param mime_types [Array] the set of MIME types
|
441
|
-
#
|
442
|
-
# @since 1.0.0
|
443
|
-
# @api private
|
444
|
-
#
|
445
|
-
# @see Hanami::Action::Mime::ClassMethods#accept
|
446
|
-
def restrict_mime_types!(mime_types)
|
447
|
-
@mime_types = self.mime_types & mime_types
|
448
|
-
end
|
449
|
-
|
450
|
-
# Set a format as default fallback for all the requests without a strict
|
451
|
-
# requirement for the mime type.
|
452
|
-
#
|
453
|
-
# The given format must be coercible to a symbol, and be a valid mime type
|
454
|
-
# alias. If it isn't, at the runtime the framework will raise a
|
455
|
-
# `Hanami::Controller::UnknownFormatError`.
|
456
|
-
#
|
457
|
-
# By default this value is nil.
|
458
|
-
#
|
459
|
-
# This is part of a DSL, for this reason when this method is called with
|
460
|
-
# an argument, it will set the corresponding instance variable. When
|
461
|
-
# called without, it will return the already set value, or the default.
|
462
|
-
#
|
463
|
-
# @overload default_request_format(format)
|
464
|
-
# Sets the given value
|
465
|
-
# @param format [#to_sym] the symbol format
|
466
|
-
# @raise [TypeError] if it cannot be coerced to a symbol
|
467
|
-
#
|
468
|
-
# @overload default_request_format
|
469
|
-
# Gets the value
|
470
|
-
# @return [Symbol,nil]
|
471
|
-
#
|
472
|
-
# @since 0.5.0
|
473
|
-
#
|
474
|
-
# @see Hanami::Action::Mime
|
475
|
-
#
|
476
|
-
# @example Getting the value
|
477
|
-
# require 'hanami/controller'
|
478
|
-
#
|
479
|
-
# Hanami::Controller.configuration.default_request_format # => nil
|
480
|
-
#
|
481
|
-
# @example Setting the value
|
482
|
-
# require 'hanami/controller'
|
483
|
-
#
|
484
|
-
# Hanami::Controller.configure do
|
485
|
-
# default_request_format :html
|
486
|
-
# end
|
487
|
-
def default_request_format(format = nil)
|
488
|
-
if format
|
489
|
-
@default_request_format = Utils::Kernel.Symbol(format)
|
490
|
-
else
|
491
|
-
@default_request_format
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
# Set a format to be used for all responses regardless of the request type.
|
496
|
-
#
|
497
|
-
# The given format must be coercible to a symbol, and be a valid mime type
|
498
|
-
# alias. If it isn't, at the runtime the framework will raise a
|
499
|
-
# `Hanami::Controller::UnknownFormatError`.
|
500
|
-
#
|
501
|
-
# By default this value is nil.
|
502
|
-
#
|
503
|
-
# This is part of a DSL, for this reason when this method is called with
|
504
|
-
# an argument, it will set the corresponding instance variable. When
|
505
|
-
# called without, it will return the already set value, or the default.
|
506
|
-
#
|
507
|
-
# @overload default_response_format(format)
|
508
|
-
# Sets the given value
|
509
|
-
# @param format [#to_sym] the symbol format
|
510
|
-
# @raise [TypeError] if it cannot be coerced to a symbol
|
511
|
-
#
|
512
|
-
# @overload default_response_format
|
513
|
-
# Gets the value
|
514
|
-
# @return [Symbol,nil]
|
515
|
-
#
|
516
|
-
# @since 0.5.0
|
517
|
-
#
|
518
|
-
# @see Hanami::Action::Mime
|
519
|
-
#
|
520
|
-
# @example Getting the value
|
521
|
-
# require 'hanami/controller'
|
522
|
-
#
|
523
|
-
# Hanami::Controller.configuration.default_response_format # => nil
|
524
|
-
#
|
525
|
-
# @example Setting the value
|
526
|
-
# require 'hanami/controller'
|
527
|
-
#
|
528
|
-
# Hanami::Controller.configure do
|
529
|
-
# default_response_format :json
|
530
|
-
# end
|
531
|
-
def default_response_format(format = nil)
|
532
|
-
if format
|
533
|
-
@default_response_format = Utils::Kernel.Symbol(format)
|
534
|
-
else
|
535
|
-
@default_response_format
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
# Set a charset as default fallback for all the requests without a strict
|
540
|
-
# requirement for the charset.
|
541
|
-
#
|
542
|
-
# By default this value is nil.
|
543
|
-
#
|
544
|
-
# @since 0.3.0
|
545
|
-
#
|
546
|
-
# @see Hanami::Action::Mime
|
547
|
-
#
|
548
|
-
# @example Getting the value
|
549
|
-
# require 'hanami/controller'
|
550
|
-
#
|
551
|
-
# Hanami::Controller.configuration.default_charset # => nil
|
552
|
-
#
|
553
|
-
# @example Setting the value
|
554
|
-
# require 'hanami/controller'
|
555
|
-
#
|
556
|
-
# Hanami::Controller.configure do
|
557
|
-
# default_charset 'koi8-r'
|
558
|
-
# end
|
559
|
-
def default_charset(charset = nil)
|
560
|
-
if charset
|
561
|
-
@default_charset = charset
|
562
|
-
else
|
563
|
-
@default_charset
|
564
|
-
end
|
565
|
-
end
|
566
|
-
|
567
|
-
# Set default headers for all responses
|
568
|
-
#
|
569
|
-
# By default this value is an empty hash.
|
570
|
-
#
|
571
|
-
# @since 0.4.0
|
572
|
-
#
|
573
|
-
# @example Getting the value
|
574
|
-
# require 'hanami/controller'
|
575
|
-
#
|
576
|
-
# Hanami::Controller.configuration.default_headers # => {}
|
577
|
-
#
|
578
|
-
# @example Setting the value
|
579
|
-
# require 'hanami/controller'
|
580
|
-
#
|
581
|
-
# Hanami::Controller.configure do
|
582
|
-
# default_headers({
|
583
|
-
# 'X-Frame-Options' => 'DENY'
|
584
|
-
# })
|
585
|
-
# end
|
586
|
-
def default_headers(headers = nil)
|
587
|
-
if headers
|
588
|
-
@default_headers.merge!(
|
589
|
-
headers.reject {|_,v| v.nil? }
|
590
|
-
)
|
591
|
-
else
|
592
|
-
@default_headers
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
# Set default cookies options for all responses
|
597
|
-
#
|
598
|
-
# By default this value is an empty hash.
|
599
|
-
#
|
600
|
-
# @since 0.4.0
|
601
|
-
#
|
602
|
-
# @example Getting the value
|
603
|
-
# require 'hanami/controller'
|
604
|
-
#
|
605
|
-
# Hanami::Controller.configuration.cookies # => {}
|
606
|
-
#
|
607
|
-
# @example Setting the value
|
608
|
-
# require 'hanami/controller'
|
609
|
-
#
|
610
|
-
# Hanami::Controller.configure do
|
611
|
-
# cookies({
|
612
|
-
# domain: 'hanamirb.org',
|
613
|
-
# path: '/controller',
|
614
|
-
# secure: true,
|
615
|
-
# httponly: true
|
616
|
-
# })
|
617
|
-
# end
|
618
|
-
def cookies(options = nil)
|
619
|
-
if options
|
620
|
-
@cookies.merge!(
|
621
|
-
options.reject { |_, v| v.nil? }
|
622
|
-
)
|
623
|
-
else
|
624
|
-
@cookies
|
625
|
-
end
|
626
|
-
end
|
627
|
-
|
628
|
-
# Returns a format for the given mime type
|
629
|
-
#
|
630
|
-
# @param mime_type [#to_s,#to_str] A mime type
|
631
|
-
#
|
632
|
-
# @return [Symbol,nil] the corresponding format, if present
|
633
|
-
#
|
634
|
-
# @see Hanami::Controller::Configuration#format
|
635
|
-
#
|
636
|
-
# @since 0.2.0
|
637
|
-
# @api private
|
638
|
-
def format_for(mime_type)
|
639
|
-
@formats[mime_type]
|
640
|
-
end
|
641
|
-
|
642
|
-
# Returns a mime type for the given format
|
643
|
-
#
|
644
|
-
# @param format [#to_sym] a format
|
645
|
-
#
|
646
|
-
# @return [String,nil] the corresponding mime type, if present
|
647
|
-
#
|
648
|
-
# @since 0.2.0
|
649
|
-
# @api private
|
650
|
-
def mime_type_for(format)
|
651
|
-
@formats.key(format)
|
652
|
-
end
|
653
|
-
|
654
|
-
# @api private
|
655
|
-
# @since 1.0.0
|
656
|
-
attr_reader :root_directory
|
657
|
-
|
658
|
-
def public_directory(value = nil)
|
659
|
-
if value.nil?
|
660
|
-
@public_directory
|
661
|
-
else
|
662
|
-
@public_directory = root_directory.join(value).to_s
|
663
|
-
end
|
664
|
-
end
|
665
|
-
|
666
|
-
# Duplicate by copying the settings in a new instance.
|
667
|
-
#
|
668
|
-
# @return [Hanami::Controller::Configuration] a copy of the configuration
|
669
|
-
#
|
670
|
-
# @since 0.2.0
|
671
|
-
# @api private
|
672
|
-
def duplicate
|
673
|
-
Configuration.new.tap do |c|
|
674
|
-
c.handle_exceptions = handle_exceptions
|
675
|
-
c.handled_exceptions = handled_exceptions.dup
|
676
|
-
c.action_module = action_module
|
677
|
-
c.modules = modules.dup
|
678
|
-
c.formats = formats.dup
|
679
|
-
c.default_request_format = default_request_format
|
680
|
-
c.default_response_format = default_response_format
|
681
|
-
c.default_charset = default_charset
|
682
|
-
c.default_headers = default_headers.dup
|
683
|
-
c.public_directory = public_directory
|
684
|
-
c.cookies = cookies.dup
|
685
|
-
end
|
686
|
-
end
|
687
|
-
|
688
|
-
# Return included modules
|
689
|
-
#
|
690
|
-
# @return [Array<Proc>] array of included blocks
|
691
|
-
#
|
692
|
-
# @since 0.2.0
|
693
|
-
# @api private
|
694
|
-
#
|
695
|
-
# @see Hanami::Controller::Configuration#prepare
|
696
|
-
attr_reader :modules
|
697
|
-
|
698
|
-
# Reset all the values to the defaults
|
699
|
-
#
|
700
|
-
# @since 0.2.0
|
701
|
-
# @api private
|
702
|
-
def reset!
|
703
|
-
@handle_exceptions = true
|
704
|
-
@handled_exceptions = {}
|
705
|
-
@modules = []
|
706
|
-
@formats = DEFAULT_FORMATS.dup
|
707
|
-
@mime_types = nil
|
708
|
-
@default_request_format = nil
|
709
|
-
@default_response_format = nil
|
710
|
-
@default_charset = nil
|
711
|
-
@default_headers = {}
|
712
|
-
@cookies = {}
|
713
|
-
@root_directory = ::Pathname.new(Dir.pwd).realpath
|
714
|
-
@public_directory = root_directory.join(DEFAULT_PUBLIC_DIRECTORY).to_s
|
715
|
-
@action_module = ::Hanami::Action
|
716
|
-
end
|
717
|
-
|
718
|
-
# Copy the configuration for the given action
|
719
|
-
#
|
720
|
-
# @param base [Class] the target action
|
721
|
-
#
|
722
|
-
# @return void
|
723
|
-
#
|
724
|
-
# @since 0.3.0
|
725
|
-
# @api private
|
726
|
-
#
|
727
|
-
# @see Hanami::Controller::Configurable.included
|
728
|
-
def copy!(base)
|
729
|
-
modules.each do |mod|
|
730
|
-
base.class_eval(&mod)
|
731
|
-
end
|
732
|
-
end
|
733
|
-
|
734
|
-
# Load the framework
|
735
|
-
#
|
736
|
-
# @since 0.3.0
|
737
|
-
# @api private
|
738
|
-
def load!
|
739
|
-
freeze
|
740
|
-
end
|
741
|
-
|
742
|
-
protected
|
743
|
-
# @since 0.5.0
|
744
|
-
# @api private
|
745
|
-
def _sort_handled_exceptions!
|
746
|
-
@handled_exceptions = Hash[
|
747
|
-
@handled_exceptions.sort{|(ex1,_),(ex2,_)| ex1.ancestors.include?(ex2) ? -1 : 1 }
|
748
|
-
]
|
749
|
-
end
|
750
|
-
|
751
|
-
attr_accessor :handled_exceptions
|
752
|
-
attr_accessor :formats
|
753
|
-
attr_writer :action_module
|
754
|
-
attr_writer :modules
|
755
|
-
attr_writer :default_request_format
|
756
|
-
attr_writer :default_response_format
|
757
|
-
attr_writer :default_charset
|
758
|
-
attr_writer :default_headers
|
759
|
-
attr_writer :cookies
|
760
|
-
attr_writer :public_directory
|
761
|
-
end
|
762
|
-
end
|
763
|
-
end
|