hanami-controller 0.0.0 → 0.6.0

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 (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