lotus-controller 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,510 @@
1
+ require 'lotus/utils/class'
2
+ require 'lotus/utils/kernel'
3
+ require 'lotus/utils/string'
4
+
5
+ module Lotus
6
+ module Controller
7
+ # Configuration for the framework, controllers and actions.
8
+ #
9
+ # Lotus::Controller has its own global configuration that can be manipulated
10
+ # via `Lotus::Controller.configure`.
11
+ #
12
+ # Every time that `Lotus::Controller` and `Lotus::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 Lotus::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 [Lotus::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 'lotus/controller'
56
+ #
57
+ # class Show
58
+ # include Lotus::Action
59
+ # end
60
+ #
61
+ # Lotus::Controller::Configuration.for(Show)
62
+ # # => will duplicate from Lotus::Controller
63
+ #
64
+ # @example Multiple instances of the framework
65
+ # require 'lotus/controller'
66
+ #
67
+ # module MyApp
68
+ # Controller = Lotus::Controller.duplicate(self)
69
+ #
70
+ # module Controllers::Dashboard
71
+ # include MyApp::Controller
72
+ #
73
+ # action 'Index' do
74
+ # def call(params)
75
+ # # ...
76
+ # end
77
+ # end
78
+ # end
79
+ # end
80
+ #
81
+ # class Show
82
+ # include Lotus::Action
83
+ # end
84
+ #
85
+ # Lotus::Controller::Configuration.for(Show)
86
+ # # => will duplicate from Lotus::Controller
87
+ #
88
+ # Lotus::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!("(#{namespace}|Lotus)::Controller")
93
+ framework.configuration.duplicate
94
+ end
95
+
96
+ # Initialize a configuration instance
97
+ #
98
+ # @return [Lotus::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] Decide if handle
107
+ # exceptions with an HTTP status or not
108
+ #
109
+ # @since 0.2.0
110
+ #
111
+ # @see Lotus::Controller::Configuration#handle_exceptions
112
+ attr_writer :handle_exceptions
113
+
114
+ # Decide if handle exceptions with an HTTP status or let them uncaught
115
+ #
116
+ # If this value is set to `true`, the configured exceptions will return
117
+ # the specified HTTP status, the rest of them with `500`.
118
+ #
119
+ # If this value is set to `false`, the exceptions won't be caught.
120
+ #
121
+ # This is part of a DSL, for this reason when this method is called with
122
+ # an argument, it will set the corresponding instance variable. When
123
+ # called without, it will return the already set value, or the default.
124
+ #
125
+ # @overload handle_exceptions(value)
126
+ # Sets the given value
127
+ # @param value [TrueClass, FalseClass] true or false, default to true
128
+ #
129
+ # @overload handle_exceptions
130
+ # Gets the value
131
+ # @return [TrueClass, FalseClass]
132
+ #
133
+ # @since 0.2.0
134
+ #
135
+ # @see Lotus::Controller::Configuration#handle_exception
136
+ # @see Lotus::Controller#configure
137
+ # @see Lotus::Action::Throwable
138
+ # @see http://httpstatus.es/500
139
+ #
140
+ # @example Getting the value
141
+ # require 'lotus/controller'
142
+ #
143
+ # Lotus::Controller.configuration.handle_exceptions # => true
144
+ #
145
+ # @example Setting the value
146
+ # require 'lotus/controller'
147
+ #
148
+ # Lotus::Controller.configure do
149
+ # handle_exceptions false
150
+ # end
151
+ def handle_exceptions(value = nil)
152
+ if value.nil?
153
+ @handle_exceptions
154
+ else
155
+ @handle_exceptions = value
156
+ end
157
+ end
158
+
159
+ # Specify how to handle an exception with an HTTP status
160
+ #
161
+ # Raised exceptions will return the configured HTTP status, only if
162
+ # `handled_exceptions` is set on `true`.
163
+ #
164
+ # @param exception [Hash] the exception class must be the key and the HTTP
165
+ # status the value
166
+ #
167
+ # @since 0.2.0
168
+ #
169
+ # @see Lotus::Controller::Configuration#handle_exceptions
170
+ # @see Lotus::Controller#configure
171
+ # @see Lotus::Action::Throwable
172
+ #
173
+ # @example
174
+ # require 'lotus/controller'
175
+ #
176
+ # Lotus::Controller.configure do
177
+ # handle_exception ArgumentError => 400
178
+ # end
179
+ def handle_exception(exception)
180
+ @handled_exceptions.merge!(exception)
181
+ end
182
+
183
+ # Return the HTTP status for the given exception
184
+ #
185
+ # Raised exceptions will return the configured HTTP status, only if
186
+ # `handled_exceptions` is set on `true`.
187
+ #
188
+ # @param exception [Hash] the exception class must be the key and the HTTP
189
+ # status the value of the hash
190
+ #
191
+ # @since 0.2.0
192
+ # @api private
193
+ #
194
+ # @see Lotus::Controller::Configuration#handle_exception
195
+ def exception_code(exception)
196
+ @handled_exceptions.fetch(exception) { DEFAULT_ERROR_CODE }
197
+ end
198
+
199
+ # Specify which is the default action module to be included when we use
200
+ # the `Lotus::Controller.action` method.
201
+ #
202
+ # This setting is useful when we use multiple instances of the framework
203
+ # in the same process, so we want to ensure that the actions will include
204
+ # `MyApp::Action`, rather than `AnotherApp::Action`.
205
+ #
206
+ # If not set, the default value is `Lotus::Action`
207
+ #
208
+ # This is part of a DSL, for this reason when this method is called with
209
+ # an argument, it will set the corresponding instance variable. When
210
+ # called without, it will return the already set value, or the default.
211
+ #
212
+ # @overload action_module(value)
213
+ # Sets the given value
214
+ # @param value [Module] the module to be included in all the actions
215
+ #
216
+ # @overload action_module
217
+ # Gets the value
218
+ # @return [Module]
219
+ #
220
+ # @since 0.2.0
221
+ #
222
+ # @see Lotus::Controller::Dsl#action
223
+ # @see Lotus::Controller#duplicate
224
+ #
225
+ # @example Getting the value
226
+ # require 'lotus/controller'
227
+ #
228
+ # Lotus::Controller.configuration.action_module # => Lotus::Action
229
+ #
230
+ # @example Setting the value
231
+ # require 'lotus/controller'
232
+ #
233
+ # module MyAction
234
+ # end
235
+ #
236
+ # Lotus::Controller.configure do
237
+ # action_module MyAction
238
+ # end
239
+ #
240
+ # class DashboardController
241
+ # include Lotus::Controller
242
+ #
243
+ # # It includes MyAction, instead of Lotus::Action
244
+ # action 'Index' do
245
+ # def call(params)
246
+ # # ...
247
+ # end
248
+ # end
249
+ # end
250
+ #
251
+ # @example Duplicated framework
252
+ # require 'lotus/controller'
253
+ #
254
+ # module MyApp
255
+ # Controller = Lotus::Controller.duplicate(self)
256
+ #
257
+ # module Controllers::Dashboard
258
+ # include MyApp::Controller
259
+ #
260
+ # # It includes MyApp::Action, instead of Lotus::Action
261
+ # action 'Index' do
262
+ # def call(params)
263
+ # # ...
264
+ # end
265
+ # end
266
+ # end
267
+ # end
268
+ def action_module(value = nil)
269
+ if value.nil?
270
+ @action_module
271
+ else
272
+ @action_module = value
273
+ end
274
+ end
275
+
276
+ # Specify the default modules to be included when `Lotus::Action` (or the
277
+ # `action_module`) is included. This also works with
278
+ # `Lotus::Controller.action`.
279
+ #
280
+ # If not set, this option will be ignored.
281
+ #
282
+ # This is part of a DSL, for this reason when this method is called with
283
+ # an argument, it will set the corresponding instance variable. When
284
+ # called without, it will return the already set value, or the default.
285
+ #
286
+ # @overload modules(blk)
287
+ # Adds the given block
288
+ # @param value [Proc] specify the modules to be included
289
+ #
290
+ # @overload modules
291
+ # Gets the value
292
+ # @return [Array] the list of the specified procs
293
+ #
294
+ # @since 0.2.0
295
+ #
296
+ # @see Lotus::Controller::Dsl#action
297
+ # @see Lotus::Controller#duplicate
298
+ #
299
+ # @example Getting the value
300
+ # require 'lotus/controller'
301
+ #
302
+ # Lotus::Controller.configuration.modules # => []
303
+ #
304
+ # @example Setting the value
305
+ # require 'lotus/controller'
306
+ # require 'lotus/action/cookies'
307
+ # require 'lotus/action/session'
308
+ #
309
+ # Lotus::Controller.configure do
310
+ # modules do
311
+ # include Lotus::Action::Cookies
312
+ # include Lotus::Action::Session
313
+ # end
314
+ # end
315
+ #
316
+ # class DashboardController
317
+ # include Lotus::Controller
318
+ #
319
+ # # It includes:
320
+ # # * Lotus::Action
321
+ # # * Lotus::Action::Cookies
322
+ # # * Lotus::Action::Session
323
+ # action 'Index' do
324
+ # def call(params)
325
+ # # ...
326
+ # end
327
+ # end
328
+ # end
329
+ def modules(&blk)
330
+ if block_given?
331
+ @modules.push(blk)
332
+ else
333
+ @modules
334
+ end
335
+ end
336
+
337
+ # Register a format
338
+ #
339
+ # @param hash [Hash] the symbol format must be the key and the mime type
340
+ # string must be the value of the hash
341
+ #
342
+ # @since 0.2.0
343
+ #
344
+ # @see Lotus::Action::Mime
345
+ #
346
+ # @example
347
+ # require 'lotus/controller'
348
+ #
349
+ # Lotus::Controller.configure do
350
+ # format custom: 'application/custom'
351
+ # end
352
+ #
353
+ # class ArticlesController
354
+ # include Lotus::Controller
355
+ #
356
+ # action 'Index' do
357
+ # def call(params)
358
+ # # ...
359
+ # end
360
+ # end
361
+ #
362
+ # action 'Show' do
363
+ # def call(params)
364
+ # # ...
365
+ # self.format = :custom
366
+ # end
367
+ # end
368
+ # end
369
+ #
370
+ # action = ArticlesController::Index.new
371
+ #
372
+ # action.call({ 'HTTP_ACCEPT' => 'text/html' })
373
+ # # => Content-Type "text/html"
374
+ # action.format # => :html
375
+ #
376
+ # action.call({ 'HTTP_ACCEPT' => 'application/custom' })
377
+ # # => Content-Type "application/custom"
378
+ # action.format # => :custom
379
+ #
380
+ #
381
+ #
382
+ # action = ArticlesController::Show.new
383
+ #
384
+ # action.call({ 'HTTP_ACCEPT' => 'text/html' })
385
+ # # => Content-Type "application/custom"
386
+ # action.format # => :custom
387
+ def format(hash)
388
+ symbol, mime_type = *Utils::Kernel.Array(hash)
389
+
390
+ @formats.merge! Utils::Kernel.String(mime_type) =>
391
+ Utils::Kernel.Symbol(symbol)
392
+ end
393
+
394
+ # Set a format as default fallback for all the requests without a strict
395
+ # requirement for the mime type.
396
+ #
397
+ # The given format must be coercible to a symbol, and be a valid mime type
398
+ # alias. If it isn't, at the runtime the framework will raise a
399
+ # `Lotus::Controller::UnknownFormatError`.
400
+ #
401
+ # By default this value is nil.
402
+ #
403
+ # This is part of a DSL, for this reason when this method is called with
404
+ # an argument, it will set the corresponding instance variable. When
405
+ # called without, it will return the already set value, or the default.
406
+ #
407
+ # @overload default_format(format)
408
+ # Sets the given value
409
+ # @param format [#to_sym] the symbol format
410
+ # @raise [TypeError] if it cannot be coerced to a symbol
411
+ #
412
+ # @overload default_format
413
+ # Gets the value
414
+ # @return [Symbol,nil]
415
+ #
416
+ # @since 0.2.0
417
+ #
418
+ # @see Lotus::Action::Mime
419
+ #
420
+ # @example Getting the value
421
+ # require 'lotus/controller'
422
+ #
423
+ # Lotus::Controller.configuration.default_format # => nil
424
+ #
425
+ # @example Setting the value
426
+ # require 'lotus/controller'
427
+ #
428
+ # Lotus::Controller.configure do
429
+ # default_format :html
430
+ # end
431
+ def default_format(format = nil)
432
+ if format
433
+ @default_format = Utils::Kernel.Symbol(format)
434
+ else
435
+ @default_format
436
+ end
437
+ end
438
+
439
+ # Returns a format for the given mime type
440
+ #
441
+ # @param mime_type [#to_s,#to_str] A mime type
442
+ #
443
+ # @return [Symbol,nil] the corresponding format, if present
444
+ #
445
+ # @see Lotus::Controller::Configuration#format
446
+ #
447
+ # @since 0.2.0
448
+ # @api private
449
+ def format_for(mime_type)
450
+ @formats[mime_type]
451
+ end
452
+
453
+ # Returns a mime type for the given format
454
+ #
455
+ # @param format [#to_sym] a format
456
+ #
457
+ # @return [String,nil] the corresponding mime type, if present
458
+ def mime_type_for(format)
459
+ @formats.key(format)
460
+ end
461
+
462
+ # Duplicate by copying the settings in a new instance.
463
+ #
464
+ # @return [Lotus::Controller::Configuration] a copy of the configuration
465
+ #
466
+ # @since 0.2.0
467
+ # @api private
468
+ def duplicate
469
+ Configuration.new.tap do |c|
470
+ c.handle_exceptions = handle_exceptions
471
+ c.handled_exceptions = handled_exceptions.dup
472
+ c.action_module = action_module
473
+ c.modules = modules.dup
474
+ c.formats = formats.dup
475
+ c.default_format = default_format
476
+ end
477
+ end
478
+
479
+ # Reset all the values to the defaults
480
+ #
481
+ # @since 0.2.0
482
+ # @api private
483
+ def reset!
484
+ @handle_exceptions = true
485
+ @handled_exceptions = {}
486
+ @modules = []
487
+ @formats = DEFAULT_FORMATS.dup
488
+ @default_format = nil
489
+ @action_module = ::Lotus::Action
490
+ end
491
+
492
+ # Load the configuration for the given action
493
+ #
494
+ # @since 0.2.0
495
+ # @api private
496
+ def load!(base)
497
+ modules.each do |mod|
498
+ base.class_eval(&mod)
499
+ end
500
+ end
501
+
502
+ protected
503
+ attr_accessor :handled_exceptions
504
+ attr_accessor :formats
505
+ attr_writer :action_module
506
+ attr_writer :modules
507
+ attr_writer :default_format
508
+ end
509
+ end
510
+ end