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
@@ -0,0 +1,128 @@
1
+ module Hanami
2
+ module Action
3
+ module Validatable
4
+ # Defines the class name for anoymous params
5
+ #
6
+ # @api private
7
+ # @since 0.3.0
8
+ PARAMS_CLASS_NAME = 'Params'.freeze
9
+
10
+ def self.included(base)
11
+ base.class_eval do
12
+ extend ClassMethods
13
+ expose :params, :errors
14
+ end
15
+ end
16
+
17
+ # Validatable API class methods
18
+ #
19
+ # @since 0.1.0
20
+ # @api private
21
+ module ClassMethods
22
+ # Whitelist valid parameters to be passed to Hanami::Action#call.
23
+ #
24
+ # This feature isn't mandatory, but higly recommended for security
25
+ # reasons.
26
+ #
27
+ # Because params come into your application from untrusted sources, it's
28
+ # a good practice to filter only the wanted keys that serve for your
29
+ # specific use case.
30
+ #
31
+ # Once whitelisted, the params are available as an Hash with symbols
32
+ # as keys.
33
+ #
34
+ #
35
+ #
36
+ # It accepts an anonymous block where all the params can be listed.
37
+ # It internally creates an inner class which inherits from
38
+ # Hanami::Action::Params.
39
+ #
40
+ #
41
+ # Alternatively, it accepts an concrete class that should inherit from
42
+ # Hanami::Action::Params.
43
+ #
44
+ # @param klass [Class,nil] a Hanami::Action::Params subclass
45
+ # @param blk [Proc] a block which defines the whitelisted params
46
+ #
47
+ # @return void
48
+ #
49
+ # @since 0.3.0
50
+ #
51
+ # @see Hanami::Action::Params
52
+ #
53
+ # @example Anonymous Block
54
+ # require 'hanami/controller'
55
+ #
56
+ # class Signup
57
+ # include Hanami::Action
58
+ #
59
+ # params do
60
+ # param :first_name
61
+ # param :last_name
62
+ # param :email
63
+ # end
64
+ #
65
+ # def call(params)
66
+ # puts params.class # => Signup::Params
67
+ # puts params.class.superclass # => Hanami::Action::Params
68
+ #
69
+ # puts params[:first_name] # => "Luca"
70
+ # puts params[:admin] # => nil
71
+ # end
72
+ # end
73
+ #
74
+ # @example Concrete class
75
+ # require 'hanami/controller'
76
+ #
77
+ # class SignupParams < Hanami::Action::Params
78
+ # param :first_name
79
+ # param :last_name
80
+ # param :email
81
+ # end
82
+ #
83
+ # class Signup
84
+ # include Hanami::Action
85
+ # params SignupParams
86
+ #
87
+ # def call(params)
88
+ # puts params.class # => SignupParams
89
+ # puts params.class.superclass # => Hanami::Action::Params
90
+ #
91
+ # params[:first_name] # => "Luca"
92
+ # params[:admin] # => nil
93
+ # end
94
+ # end
95
+ def params(klass = nil, &blk)
96
+ if block_given?
97
+ @params_class = const_set(PARAMS_CLASS_NAME,
98
+ Class.new(Params, &blk))
99
+ else
100
+ @params_class = klass
101
+ end
102
+ end
103
+
104
+ # Returns the class which defines the params
105
+ #
106
+ # Returns the class which has been provided to define the
107
+ # params. By default this will be Hanami::Action::Params.
108
+ #
109
+ # @return [Class] A params class (when whitelisted) or
110
+ # Hanami::Action::Params
111
+ #
112
+ # @api private
113
+ # @since 0.3.0
114
+ def params_class
115
+ @params_class ||= params { }
116
+ end
117
+
118
+ end
119
+ end
120
+
121
+ # Expose validation errors
122
+ #
123
+ # @since 0.3.0
124
+ def errors
125
+ params.errors
126
+ end
127
+ end
128
+ end
@@ -1,7 +1,255 @@
1
- require "hanami/controller/version"
1
+ require 'hanami/utils/class_attribute'
2
+ require 'hanami/action'
3
+ require 'hanami/controller/configuration'
4
+ require 'hanami/controller/version'
5
+ require 'hanami/controller/error'
2
6
 
3
7
  module Hanami
8
+ # A set of logically grouped actions
9
+ #
10
+ # @since 0.1.0
11
+ #
12
+ # @see Hanami::Action
13
+ #
14
+ # @example
15
+ # require 'hanami/controller'
16
+ #
17
+ # module Articles
18
+ # class Index
19
+ # include Hanami::Action
20
+ #
21
+ # # ...
22
+ # end
23
+ #
24
+ # class Show
25
+ # include Hanami::Action
26
+ #
27
+ # # ...
28
+ # end
29
+ # end
4
30
  module Controller
5
- # Your code goes here...
31
+ # Unknown format error
32
+ #
33
+ # This error is raised when a action sets a format that it isn't recognized
34
+ # both by `Hanami::Controller::Configuration` and the list of Rack mime types
35
+ #
36
+ # @since 0.2.0
37
+ #
38
+ # @see Hanami::Action::Mime#format=
39
+ class UnknownFormatError < Hanami::Controller::Error
40
+ def initialize(format)
41
+ super("Cannot find a corresponding Mime type for '#{ format }'. Please configure it with Hanami::Controller::Configuration#format.")
42
+ end
43
+ end
44
+
45
+ include Utils::ClassAttribute
46
+
47
+ # Framework configuration
48
+ #
49
+ # @since 0.2.0
50
+ # @api private
51
+ class_attribute :configuration
52
+ self.configuration = Configuration.new
53
+
54
+ # Configure the framework.
55
+ # It yields the given block in the context of the configuration
56
+ #
57
+ # @param blk [Proc] the configuration block
58
+ #
59
+ # @since 0.2.0
60
+ #
61
+ # @see Hanami::Controller::Configuration
62
+ #
63
+ # @example
64
+ # require 'hanami/controller'
65
+ #
66
+ # Hanami::Controller.configure do
67
+ # handle_exceptions false
68
+ # end
69
+ def self.configure(&blk)
70
+ configuration.instance_eval(&blk)
71
+ end
72
+
73
+ # Duplicate Hanami::Controller in order to create a new separated instance
74
+ # of the framework.
75
+ #
76
+ # The new instance of the framework will be completely decoupled from the
77
+ # original. It will inherit the configuration, but all the changes that
78
+ # happen after the duplication, won't be reflected on the other copies.
79
+ #
80
+ # @return [Module] a copy of Hanami::Controller
81
+ #
82
+ # @since 0.2.0
83
+ # @api private
84
+ #
85
+ # @example Basic usage
86
+ # require 'hanami/controller'
87
+ #
88
+ # module MyApp
89
+ # Controller = Hanami::Controller.dupe
90
+ # end
91
+ #
92
+ # MyApp::Controller == Hanami::Controller # => false
93
+ #
94
+ # MyApp::Controller.configuration ==
95
+ # Hanami::Controller.configuration # => false
96
+ #
97
+ # @example Inheriting configuration
98
+ # require 'hanami/controller'
99
+ #
100
+ # Hanami::Controller.configure do
101
+ # handle_exceptions false
102
+ # end
103
+ #
104
+ # module MyApp
105
+ # Controller = Hanami::Controller.dupe
106
+ # end
107
+ #
108
+ # module MyApi
109
+ # Controller = Hanami::Controller.dupe
110
+ # Controller.configure do
111
+ # handle_exceptions true
112
+ # end
113
+ # end
114
+ #
115
+ # Hanami::Controller.configuration.handle_exceptions # => false
116
+ # MyApp::Controller.configuration.handle_exceptions # => false
117
+ # MyApi::Controller.configuration.handle_exceptions # => true
118
+ def self.dupe
119
+ dup.tap do |duplicated|
120
+ duplicated.configuration = configuration.duplicate
121
+ end
122
+ end
123
+
124
+ # Duplicate the framework and generate modules for the target application
125
+ #
126
+ # @param mod [Module] the Ruby namespace of the application
127
+ # @param controllers [String] the optional namespace where the application's
128
+ # controllers will live
129
+ # @param blk [Proc] an optional block to configure the framework
130
+ #
131
+ # @return [Module] a copy of Hanami::Controller
132
+ #
133
+ # @since 0.2.0
134
+ #
135
+ # @see Hanami::Controller#dupe
136
+ # @see Hanami::Controller::Configuration
137
+ # @see Hanami::Controller::Configuration#action_module
138
+ #
139
+ # @example Basic usage
140
+ # require 'hanami/controller'
141
+ #
142
+ # module MyApp
143
+ # Controller = Hanami::Controller.duplicate(self)
144
+ # end
145
+ #
146
+ # # It will:
147
+ # #
148
+ # # 1. Generate MyApp::Controller
149
+ # # 2. Generate MyApp::Action
150
+ # # 3. Generate MyApp::Controllers
151
+ # # 4. Configure MyApp::Action as the default module for actions
152
+ #
153
+ # module MyApp::Controllers::Dashboard
154
+ # include MyApp::Controller
155
+ #
156
+ # action 'Index' do # this will inject MyApp::Action
157
+ # def call(params)
158
+ # # ...
159
+ # end
160
+ # end
161
+ # end
162
+ #
163
+ # @example Compare code
164
+ # require 'hanami/controller'
165
+ #
166
+ # module MyApp
167
+ # Controller = Hanami::Controller.duplicate(self) do
168
+ # # ...
169
+ # end
170
+ # end
171
+ #
172
+ # # it's equivalent to:
173
+ #
174
+ # module MyApp
175
+ # Controller = Hanami::Controller.dupe
176
+ # Action = Hanami::Action.dup
177
+ #
178
+ # module Controllers
179
+ # end
180
+ #
181
+ # Controller.configure do
182
+ # action_module MyApp::Action
183
+ # end
184
+ #
185
+ # Controller.configure do
186
+ # # ...
187
+ # end
188
+ # end
189
+ #
190
+ # @example Custom controllers module
191
+ # require 'hanami/controller'
192
+ #
193
+ # module MyApp
194
+ # Controller = Hanami::Controller.duplicate(self, 'Ctrls')
195
+ # end
196
+ #
197
+ # defined?(MyApp::Controllers) # => nil
198
+ # defined?(MyApp::Ctrls) # => "constant"
199
+ #
200
+ # # Developers can namespace controllers under Ctrls
201
+ # module MyApp::Ctrls::Dashboard
202
+ # # ...
203
+ # end
204
+ #
205
+ # @example Nil controllers module
206
+ # require 'hanami/controller'
207
+ #
208
+ # module MyApp
209
+ # Controller = Hanami::Controller.duplicate(self, nil)
210
+ # end
211
+ #
212
+ # defined?(MyApp::Controllers) # => nil
213
+ #
214
+ # # Developers can namespace controllers under MyApp
215
+ # module MyApp::DashboardController
216
+ # # ...
217
+ # end
218
+ #
219
+ # @example Block usage
220
+ # require 'hanami/controller'
221
+ #
222
+ # module MyApp
223
+ # Controller = Hanami::Controller.duplicate(self) do
224
+ # handle_exceptions false
225
+ # end
226
+ # end
227
+ #
228
+ # Hanami::Controller.configuration.handle_exceptions # => true
229
+ # MyApp::Controller.configuration.handle_exceptions # => false
230
+ def self.duplicate(mod, controllers = 'Controllers', &blk)
231
+ dupe.tap do |duplicated|
232
+ mod.module_eval %{ module #{ controllers }; end } if controllers
233
+ mod.module_eval %{ Action = Hanami::Action.dup }
234
+
235
+ duplicated.module_eval %{
236
+ configure do
237
+ action_module #{ mod }::Action
238
+ end
239
+ }
240
+
241
+ duplicated.configure(&blk) if block_given?
242
+ end
243
+ end
244
+
245
+ # Framework loading entry point
246
+ #
247
+ # @return [void]
248
+ #
249
+ # @since 0.3.0
250
+ def self.load!
251
+ configuration.load!
252
+ end
6
253
  end
7
254
  end
255
+
@@ -0,0 +1,705 @@
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 Mime type to format mapping
30
+ #
31
+ # @since 0.2.0
32
+ # @api private
33
+ DEFAULT_FORMATS = {
34
+ 'application/octet-stream' => :all,
35
+ '*/*' => :all,
36
+ 'text/html' => :html
37
+ }.freeze
38
+
39
+ # Return a copy of the configuration of the framework instance associated
40
+ # with the given class.
41
+ #
42
+ # When multiple instances of Hanami::Controller are used in the same
43
+ # application, we want to make sure that a controller or an action will
44
+ # receive the expected configuration.
45
+ #
46
+ # @param base [Class, Module] a controller or an action
47
+ #
48
+ # @return [Hanami::Controller::Configuration] the configuration associated
49
+ # to the given class.
50
+ #
51
+ # @since 0.2.0
52
+ # @api private
53
+ #
54
+ # @example Direct usage of the framework
55
+ # require 'hanami/controller'
56
+ #
57
+ # class Show
58
+ # include Hanami::Action
59
+ # end
60
+ #
61
+ # Hanami::Controller::Configuration.for(Show)
62
+ # # => will duplicate from Hanami::Controller
63
+ #
64
+ # @example Multiple instances of the framework
65
+ # require 'hanami/controller'
66
+ #
67
+ # module MyApp
68
+ # Controller = Hanami::Controller.duplicate(self)
69
+ #
70
+ # module Controllers::Dashboard
71
+ # class Index
72
+ # include MyApp::Action
73
+ #
74
+ # def call(params)
75
+ # # ...
76
+ # end
77
+ # end
78
+ # end
79
+ # end
80
+ #
81
+ # class Show
82
+ # include Hanami::Action
83
+ # end
84
+ #
85
+ # Hanami::Controller::Configuration.for(Show)
86
+ # # => will duplicate from Hanami::Controller
87
+ #
88
+ # Hanami::Controller::Configuration.for(MyApp::Controllers::Dashboard)
89
+ # # => will duplicate from MyApp::Controller
90
+ def self.for(base)
91
+ namespace = Utils::String.new(base).namespace
92
+ framework = Utils::Class.load_from_pattern!("(#{namespace}|Hanami)::Controller")
93
+ framework.configuration.duplicate
94
+ end
95
+
96
+ # Initialize a configuration instance
97
+ #
98
+ # @return [Hanami::Controller::Configuration] a new configuration's
99
+ # instance
100
+ #
101
+ # @since 0.2.0
102
+ def initialize
103
+ reset!
104
+ end
105
+
106
+ # @attr_writer handle_exceptions [TrueClass,FalseClass] Handle exceptions
107
+ # with an HTTP status or leave them uncaught
108
+ #
109
+ # @since 0.2.0
110
+ #
111
+ # @return void
112
+ #
113
+ # @see Hanami::Controller::Configuration#handle_exceptions
114
+ attr_writer :handle_exceptions
115
+
116
+ # Handle exceptions with an HTTP status or let them uncaught
117
+ #
118
+ # If this value is set to `true`, the configured exceptions will return
119
+ # the specified HTTP status, the rest of them with `500`.
120
+ #
121
+ # If this value is set to `false`, the exceptions won't be caught.
122
+ #
123
+ # This is part of a DSL, for this reason when this method is called with
124
+ # an argument, it will set the corresponding instance variable. When
125
+ # called without, it will return the already set value, or the default.
126
+ #
127
+ # @overload handle_exceptions(value)
128
+ # Sets the given value
129
+ # @param value [TrueClass, FalseClass] true or false, default to true
130
+ #
131
+ # @overload handle_exceptions
132
+ # Gets the value
133
+ # @return [TrueClass, FalseClass]
134
+ #
135
+ # @since 0.2.0
136
+ #
137
+ # @see Hanami::Controller::Configuration#handle_exception
138
+ # @see Hanami::Controller#configure
139
+ # @see Hanami::Action::Throwable
140
+ # @see http://httpstatus.es/500
141
+ #
142
+ # @example Getting the value
143
+ # require 'hanami/controller'
144
+ #
145
+ # Hanami::Controller.configuration.handle_exceptions # => true
146
+ #
147
+ # @example Setting the value
148
+ # require 'hanami/controller'
149
+ #
150
+ # Hanami::Controller.configure do
151
+ # handle_exceptions false
152
+ # end
153
+ def handle_exceptions(value = nil)
154
+ if value.nil?
155
+ @handle_exceptions
156
+ else
157
+ @handle_exceptions = value
158
+ end
159
+ end
160
+
161
+ # Specify how to handle an exception with an HTTP status
162
+ #
163
+ # Raised exceptions will return the configured HTTP status, only if
164
+ # `handled_exceptions` is set on `true`.
165
+ #
166
+ # @param exception [Hash] the exception class must be the key and the HTTP
167
+ # status the value
168
+ #
169
+ # @since 0.2.0
170
+ #
171
+ # @see Hanami::Controller::Configuration#handle_exceptions
172
+ # @see Hanami::Controller#configure
173
+ # @see Hanami::Action::Throwable
174
+ #
175
+ # @example
176
+ # require 'hanami/controller'
177
+ #
178
+ # Hanami::Controller.configure do
179
+ # handle_exception ArgumentError => 400
180
+ # end
181
+ def handle_exception(exception)
182
+ @handled_exceptions.merge!(exception)
183
+ _sort_handled_exceptions!
184
+ end
185
+
186
+ # Return a callable handler for the given exception
187
+ #
188
+ # @param exception [Exception] an exception
189
+ #
190
+ # @since 0.3.0
191
+ # @api private
192
+ #
193
+ # @see Hanami::Controller::Configuration#handle_exception
194
+ def exception_handler(exception)
195
+ handler = nil
196
+
197
+ @handled_exceptions.each do |exception_class, h|
198
+ if exception.kind_of?(exception_class)
199
+ handler = h
200
+ break
201
+ end
202
+ end
203
+
204
+ handler || DEFAULT_ERROR_CODE
205
+ end
206
+
207
+ # Check if the given exception is handled.
208
+ #
209
+ # @param exception [Exception] an exception
210
+ #
211
+ # @since 0.3.2
212
+ # @api private
213
+ #
214
+ # @see Hanami::Controller::Configuration#handle_exception
215
+ def handled_exception?(exception)
216
+ handled_exceptions &&
217
+ !!@handled_exceptions.fetch(exception.class) { false }
218
+ end
219
+
220
+ # Specify which is the default action module to be included when we use
221
+ # the `Hanami::Controller.action` method.
222
+ #
223
+ # This setting is useful when we use multiple instances of the framework
224
+ # in the same process, so we want to ensure that the actions will include
225
+ # `MyApp::Action`, rather than `AnotherApp::Action`.
226
+ #
227
+ # If not set, the default value is `Hanami::Action`
228
+ #
229
+ # This is part of a DSL, for this reason when this method is called with
230
+ # an argument, it will set the corresponding instance variable. When
231
+ # called without, it will return the already set value, or the default.
232
+ #
233
+ # @overload action_module(value)
234
+ # Sets the given value
235
+ # @param value [Module] the module to be included in all the actions
236
+ #
237
+ # @overload action_module
238
+ # Gets the value
239
+ # @return [Module]
240
+ #
241
+ # @since 0.2.0
242
+ #
243
+ # @see Hanami::Controller::Dsl#action
244
+ # @see Hanami::Controller#duplicate
245
+ #
246
+ # @example Getting the value
247
+ # require 'hanami/controller'
248
+ #
249
+ # Hanami::Controller.configuration.action_module # => Hanami::Action
250
+ #
251
+ # @example Setting the value
252
+ # require 'hanami/controller'
253
+ #
254
+ # module MyAction
255
+ # end
256
+ #
257
+ # Hanami::Controller.configure do
258
+ # action_module MyAction
259
+ # end
260
+ #
261
+ # module Dashboard
262
+ # # It includes MyAction, instead of Hanami::Action
263
+ # class Index
264
+ # include MyAction
265
+ #
266
+ # def call(params)
267
+ # # ...
268
+ # end
269
+ # end
270
+ # end
271
+ #
272
+ # @example Duplicated framework
273
+ # require 'hanami/controller'
274
+ #
275
+ # module MyApp
276
+ # Controller = Hanami::Controller.duplicate(self)
277
+ #
278
+ # module Controllers::Dashboard
279
+ # include MyApp::Controller
280
+ #
281
+ # # It includes MyApp::Action, instead of Hanami::Action
282
+ # class Index
283
+ # include MyApp::Action
284
+ #
285
+ # def call(params)
286
+ # # ...
287
+ # end
288
+ # end
289
+ # end
290
+ # end
291
+ def action_module(value = nil)
292
+ if value.nil?
293
+ @action_module
294
+ else
295
+ @action_module = value
296
+ end
297
+ end
298
+
299
+ # Configure the logic to be executed when Hanami::Action is included
300
+ # This is useful to DRY code by having a single place where to configure
301
+ # shared behaviors like authentication, sessions, cookies etc.
302
+ #
303
+ # This method can be called multiple times.
304
+ #
305
+ # @param blk [Proc] the code block
306
+ #
307
+ # @return [void]
308
+ #
309
+ # @raise [ArgumentError] if called without passing a block
310
+ #
311
+ # @since 0.3.0
312
+ #
313
+ # @see Hanami::Controller.configure
314
+ # @see Hanami::Controller.duplicate
315
+ #
316
+ # @example Configure shared logic.
317
+ # require 'hanami/controller'
318
+ #
319
+ # Hanami::Controller.configure do
320
+ # prepare do
321
+ # include Hanami::Action::Sessions
322
+ # include MyAuthentication
323
+ # use SomeMiddleWare
324
+ #
325
+ # before { authenticate! }
326
+ # end
327
+ # end
328
+ #
329
+ # module Dashboard
330
+ # class Index
331
+ # # When Hanami::Action is included, it will:
332
+ # # * Include `Hanami::Action::Session` and `MyAuthentication`
333
+ # # * Configure to use `SomeMiddleWare`
334
+ # # * Configure a `before` callback that triggers `#authenticate!`
335
+ # include Hanami::Action
336
+ #
337
+ # def call(params)
338
+ # # ...
339
+ # end
340
+ # end
341
+ # end
342
+ def prepare(&blk)
343
+ if block_given?
344
+ @modules.push(blk)
345
+ else
346
+ raise ArgumentError.new('Please provide a block')
347
+ end
348
+ end
349
+
350
+ # Register a format
351
+ #
352
+ # @param hash [Hash] the symbol format must be the key and the mime type
353
+ # string must be the value of the hash
354
+ #
355
+ # @since 0.2.0
356
+ #
357
+ # @see Hanami::Action::Mime
358
+ #
359
+ # @example
360
+ # require 'hanami/controller'
361
+ #
362
+ # Hanami::Controller.configure do
363
+ # format custom: 'application/custom'
364
+ # end
365
+ #
366
+ # module Articles
367
+ # class Index
368
+ # include Hanami::Action
369
+ #
370
+ # def call(params)
371
+ # # ...
372
+ # end
373
+ # end
374
+ #
375
+ # class Show
376
+ # include Hanami::Action
377
+ #
378
+ # def call(params)
379
+ # # ...
380
+ # self.format = :custom
381
+ # end
382
+ # end
383
+ # end
384
+ #
385
+ # action = Articles::Index.new
386
+ #
387
+ # action.call({ 'HTTP_ACCEPT' => 'text/html' })
388
+ # # => Content-Type "text/html"
389
+ # action.format # => :html
390
+ #
391
+ # action.call({ 'HTTP_ACCEPT' => 'application/custom' })
392
+ # # => Content-Type "application/custom"
393
+ # action.format # => :custom
394
+ #
395
+ #
396
+ #
397
+ # action = Articles::Show.new
398
+ #
399
+ # action.call({ 'HTTP_ACCEPT' => 'text/html' })
400
+ # # => Content-Type "application/custom"
401
+ # action.format # => :custom
402
+ def format(hash)
403
+ symbol, mime_type = *Utils::Kernel.Array(hash)
404
+
405
+ @formats.merge! Utils::Kernel.String(mime_type) =>
406
+ Utils::Kernel.Symbol(symbol)
407
+ end
408
+
409
+ # Set a format as default fallback for all the requests without a strict
410
+ # requirement for the mime type.
411
+ #
412
+ # The given format must be coercible to a symbol, and be a valid mime type
413
+ # alias. If it isn't, at the runtime the framework will raise a
414
+ # `Hanami::Controller::UnknownFormatError`.
415
+ #
416
+ # By default this value is nil.
417
+ #
418
+ # This is part of a DSL, for this reason when this method is called with
419
+ # an argument, it will set the corresponding instance variable. When
420
+ # called without, it will return the already set value, or the default.
421
+ #
422
+ # @overload default_request_format(format)
423
+ # Sets the given value
424
+ # @param format [#to_sym] the symbol format
425
+ # @raise [TypeError] if it cannot be coerced to a symbol
426
+ #
427
+ # @overload default_request_format
428
+ # Gets the value
429
+ # @return [Symbol,nil]
430
+ #
431
+ # @since 0.5.0
432
+ #
433
+ # @see Hanami::Action::Mime
434
+ #
435
+ # @example Getting the value
436
+ # require 'hanami/controller'
437
+ #
438
+ # Hanami::Controller.configuration.default_request_format # => nil
439
+ #
440
+ # @example Setting the value
441
+ # require 'hanami/controller'
442
+ #
443
+ # Hanami::Controller.configure do
444
+ # default_request_format :html
445
+ # end
446
+ def default_request_format(format = nil)
447
+ if format
448
+ @default_request_format = Utils::Kernel.Symbol(format)
449
+ else
450
+ @default_request_format
451
+ end
452
+ end
453
+
454
+ # Set a format to be used for all responses regardless of the request type.
455
+ #
456
+ # The given format must be coercible to a symbol, and be a valid mime type
457
+ # alias. If it isn't, at the runtime the framework will raise a
458
+ # `Hanami::Controller::UnknownFormatError`.
459
+ #
460
+ # By default this value is nil.
461
+ #
462
+ # This is part of a DSL, for this reason when this method is called with
463
+ # an argument, it will set the corresponding instance variable. When
464
+ # called without, it will return the already set value, or the default.
465
+ #
466
+ # @overload default_response_format(format)
467
+ # Sets the given value
468
+ # @param format [#to_sym] the symbol format
469
+ # @raise [TypeError] if it cannot be coerced to a symbol
470
+ #
471
+ # @overload default_response_format
472
+ # Gets the value
473
+ # @return [Symbol,nil]
474
+ #
475
+ # @since 0.5.0
476
+ #
477
+ # @see Hanami::Action::Mime
478
+ #
479
+ # @example Getting the value
480
+ # require 'hanami/controller'
481
+ #
482
+ # Hanami::Controller.configuration.default_response_format # => nil
483
+ #
484
+ # @example Setting the value
485
+ # require 'hanami/controller'
486
+ #
487
+ # Hanami::Controller.configure do
488
+ # default_response_format :json
489
+ # end
490
+ def default_response_format(format = nil)
491
+ if format
492
+ @default_response_format = Utils::Kernel.Symbol(format)
493
+ else
494
+ @default_response_format
495
+ end
496
+ end
497
+
498
+ # Set a charset as default fallback for all the requests without a strict
499
+ # requirement for the charset.
500
+ #
501
+ # By default this value is nil.
502
+ #
503
+ # @since 0.3.0
504
+ #
505
+ # @see Hanami::Action::Mime
506
+ #
507
+ # @example Getting the value
508
+ # require 'hanami/controller'
509
+ #
510
+ # Hanami::Controller.configuration.default_charset # => nil
511
+ #
512
+ # @example Setting the value
513
+ # require 'hanami/controller'
514
+ #
515
+ # Hanami::Controller.configure do
516
+ # default_charset 'koi8-r'
517
+ # end
518
+ def default_charset(charset = nil)
519
+ if charset
520
+ @default_charset = charset
521
+ else
522
+ @default_charset
523
+ end
524
+ end
525
+
526
+ # Set default headers for all responses
527
+ #
528
+ # By default this value is an empty hash.
529
+ #
530
+ # @since 0.4.0
531
+ #
532
+ # @example Getting the value
533
+ # require 'hanami/controller'
534
+ #
535
+ # Hanami::Controller.configuration.default_headers # => {}
536
+ #
537
+ # @example Setting the value
538
+ # require 'hanami/controller'
539
+ #
540
+ # Hanami::Controller.configure do
541
+ # default_headers({
542
+ # 'X-Frame-Options' => 'DENY'
543
+ # })
544
+ # end
545
+ def default_headers(headers = nil)
546
+ if headers
547
+ @default_headers.merge!(
548
+ headers.reject {|_,v| v.nil? }
549
+ )
550
+ else
551
+ @default_headers
552
+ end
553
+ end
554
+
555
+ # Set default cookies options for all responses
556
+ #
557
+ # By default this value is an empty hash.
558
+ #
559
+ # @since 0.4.0
560
+ #
561
+ # @example Getting the value
562
+ # require 'hanami/controller'
563
+ #
564
+ # Hanami::Controller.configuration.cookies # => {}
565
+ #
566
+ # @example Setting the value
567
+ # require 'hanami/controller'
568
+ #
569
+ # Hanami::Controller.configure do
570
+ # cookies({
571
+ # domain: 'hanamirb.org',
572
+ # path: '/controller',
573
+ # secure: true,
574
+ # httponly: true
575
+ # })
576
+ # end
577
+ def cookies(options = nil)
578
+ if options
579
+ @cookies.merge!(
580
+ options.reject { |_, v| v.nil? }
581
+ )
582
+ else
583
+ @cookies
584
+ end
585
+ end
586
+
587
+ # Returns a format for the given mime type
588
+ #
589
+ # @param mime_type [#to_s,#to_str] A mime type
590
+ #
591
+ # @return [Symbol,nil] the corresponding format, if present
592
+ #
593
+ # @see Hanami::Controller::Configuration#format
594
+ #
595
+ # @since 0.2.0
596
+ # @api private
597
+ def format_for(mime_type)
598
+ @formats[mime_type]
599
+ end
600
+
601
+ # Returns a mime type for the given format
602
+ #
603
+ # @param format [#to_sym] a format
604
+ #
605
+ # @return [String,nil] the corresponding mime type, if present
606
+ #
607
+ # @since 0.2.0
608
+ # @api private
609
+ def mime_type_for(format)
610
+ @formats.key(format)
611
+ end
612
+
613
+ # Duplicate by copying the settings in a new instance.
614
+ #
615
+ # @return [Hanami::Controller::Configuration] a copy of the configuration
616
+ #
617
+ # @since 0.2.0
618
+ # @api private
619
+ def duplicate
620
+ Configuration.new.tap do |c|
621
+ c.handle_exceptions = handle_exceptions
622
+ c.handled_exceptions = handled_exceptions.dup
623
+ c.action_module = action_module
624
+ c.modules = modules.dup
625
+ c.formats = formats.dup
626
+ c.default_request_format = default_request_format
627
+ c.default_response_format = default_response_format
628
+ c.default_charset = default_charset
629
+ c.default_headers = default_headers.dup
630
+ c.cookies = cookies.dup
631
+ end
632
+ end
633
+
634
+ # Return included modules
635
+ #
636
+ # @return [Array<Proc>] array of included blocks
637
+ #
638
+ # @since 0.2.0
639
+ # @api private
640
+ #
641
+ # @see Hanami::Controller::Configuration#prepare
642
+ attr_reader :modules
643
+
644
+ # Reset all the values to the defaults
645
+ #
646
+ # @since 0.2.0
647
+ # @api private
648
+ def reset!
649
+ @handle_exceptions = true
650
+ @handled_exceptions = {}
651
+ @modules = []
652
+ @formats = DEFAULT_FORMATS.dup
653
+ @default_request_format = nil
654
+ @default_response_format = nil
655
+ @default_charset = nil
656
+ @default_headers = {}
657
+ @cookies = {}
658
+ @action_module = ::Hanami::Action
659
+ end
660
+
661
+ # Copy the configuration for the given action
662
+ #
663
+ # @param base [Class] the target action
664
+ #
665
+ # @return void
666
+ #
667
+ # @since 0.3.0
668
+ # @api private
669
+ #
670
+ # @see Hanami::Controller::Configurable.included
671
+ def copy!(base)
672
+ modules.each do |mod|
673
+ base.class_eval(&mod)
674
+ end
675
+ end
676
+
677
+ # Load the framework
678
+ #
679
+ # @since 0.3.0
680
+ # @api private
681
+ def load!
682
+ freeze
683
+ end
684
+
685
+ protected
686
+ # @since 0.5.0
687
+ # @api private
688
+ def _sort_handled_exceptions!
689
+ @handled_exceptions = Hash[
690
+ @handled_exceptions.sort{|(ex1,_),(ex2,_)| ex1.ancestors.include?(ex2) ? -1 : 1 }
691
+ ]
692
+ end
693
+
694
+ attr_accessor :handled_exceptions
695
+ attr_accessor :formats
696
+ attr_writer :action_module
697
+ attr_writer :modules
698
+ attr_writer :default_request_format
699
+ attr_writer :default_response_format
700
+ attr_writer :default_charset
701
+ attr_writer :default_headers
702
+ attr_writer :cookies
703
+ end
704
+ end
705
+ end