lotus-controller 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +127 -0
- data/{LICENSE.txt → LICENSE.md} +0 -0
- data/README.md +259 -15
- data/lib/lotus/action.rb +20 -0
- data/lib/lotus/action/callbacks.rb +20 -2
- data/lib/lotus/action/configurable.rb +48 -0
- data/lib/lotus/action/cookie_jar.rb +29 -5
- data/lib/lotus/action/exposable.rb +12 -5
- data/lib/lotus/action/mime.rb +215 -32
- data/lib/lotus/action/params.rb +21 -4
- data/lib/lotus/action/rack.rb +81 -0
- data/lib/lotus/action/redirect.rb +6 -1
- data/lib/lotus/action/session.rb +1 -0
- data/lib/lotus/action/throwable.rb +37 -36
- data/lib/lotus/controller.rb +216 -17
- data/lib/lotus/controller/configuration.rb +510 -0
- data/lib/lotus/controller/dsl.rb +6 -4
- data/lib/lotus/controller/version.rb +1 -1
- data/lib/lotus/http/status.rb +5 -62
- data/lib/rack-patch.rb +4 -2
- data/lotus-controller.gemspec +3 -3
- metadata +26 -56
- data/.gitignore +0 -10
- data/.travis.yml +0 -5
- data/.yardopts +0 -4
- data/Gemfile +0 -15
- data/Rakefile +0 -17
- data/test/action/callbacks_test.rb +0 -99
- data/test/action/params_test.rb +0 -29
- data/test/action_test.rb +0 -31
- data/test/controller_test.rb +0 -24
- data/test/cookies_test.rb +0 -36
- data/test/fixtures.rb +0 -501
- data/test/integration/mime_type_test.rb +0 -175
- data/test/integration/routing_test.rb +0 -141
- data/test/integration/sessions_test.rb +0 -63
- data/test/redirect_test.rb +0 -20
- data/test/session_test.rb +0 -19
- data/test/test_helper.rb +0 -24
- data/test/throw_test.rb +0 -93
- data/test/version_test.rb +0 -7
@@ -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
|