lotus-controller 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -19,8 +19,12 @@ module Lotus
19
19
  base.extend ClassMethods
20
20
  end
21
21
 
22
+ # Throw API class methods
23
+ #
24
+ # @since 0.1.0
25
+ # @api private
22
26
  module ClassMethods
23
- protected
27
+ private
24
28
 
25
29
  # Handle the given exception with an HTTP status code.
26
30
  #
@@ -74,8 +78,8 @@ module Lotus
74
78
  # @see Lotus::Controller#handled_exceptions
75
79
  # @see Lotus::Action::Throwable#handle_exception
76
80
  # @see Lotus::Http::Status:ALL
77
- def halt(code)
78
- status(*Http::Status.for_code(code))
81
+ def halt(code = nil)
82
+ status(*Http::Status.for_code(code)) if code
79
83
  throw :halt
80
84
  end
81
85
 
@@ -124,7 +128,23 @@ module Lotus
124
128
  # @api private
125
129
  def _handle_exception(exception)
126
130
  raise unless configuration.handle_exceptions
127
- halt configuration.exception_code(exception.class)
131
+
132
+ instance_exec(
133
+ exception,
134
+ &_exception_handler(exception)
135
+ )
136
+ end
137
+
138
+ # @since 0.3.0
139
+ # @api private
140
+ def _exception_handler(exception)
141
+ handler = configuration.exception_handler(exception)
142
+
143
+ if respond_to?(handler.to_s, true)
144
+ method(handler)
145
+ else
146
+ ->(ex) { halt handler }
147
+ end
128
148
  end
129
149
  end
130
150
  end
@@ -0,0 +1,128 @@
1
+ module Lotus
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 Lotus::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
+ # Lotus::Action::Params.
39
+ #
40
+ #
41
+ # Alternatively, it accepts an concrete class that should inherit from
42
+ # Lotus::Action::Params.
43
+ #
44
+ # @param klass [Class,nil] a Lotus::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 Lotus::Action::Params
52
+ #
53
+ # @example Anonymous Block
54
+ # require 'lotus/controller'
55
+ #
56
+ # class Signup
57
+ # include Lotus::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 # => Lotus::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 'lotus/controller'
76
+ #
77
+ # class SignupParams < Lotus::Action::Params
78
+ # param :first_name
79
+ # param :last_name
80
+ # param :email
81
+ # end
82
+ #
83
+ # class Signup
84
+ # include Lotus::Action
85
+ # params SignupParams
86
+ #
87
+ # def call(params)
88
+ # puts params.class # => SignupParams
89
+ # puts params.class.superclass # => Lotus::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 Lotus::Action::Params.
108
+ #
109
+ # @return [Class] A params class (when whitelisted) or
110
+ # Lotus::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,6 @@
1
1
  require 'lotus/utils/class_attribute'
2
2
  require 'lotus/action'
3
3
  require 'lotus/controller/configuration'
4
- require 'lotus/controller/dsl'
5
4
  require 'lotus/controller/version'
6
5
  require 'rack-patch'
7
6
 
@@ -15,14 +14,16 @@ module Lotus
15
14
  # @example
16
15
  # require 'lotus/controller'
17
16
  #
18
- # class ArticlesController
19
- # include Lotus::Controller
17
+ # module Articles
18
+ # class Index
19
+ # include Lotus::Action
20
20
  #
21
- # action 'Index' do
22
21
  # # ...
23
22
  # end
24
23
  #
25
- # action 'Show' do
24
+ # class Show
25
+ # include Lotus::Action
26
+ #
26
27
  # # ...
27
28
  # end
28
29
  # end
@@ -241,28 +242,13 @@ module Lotus
241
242
  end
242
243
  end
243
244
 
244
- # Override Ruby's hook for modules.
245
- # It includes basic Lotus::Controller modules to the given Class (or Module).
246
- # It sets a copy of the framework configuration
247
- #
248
- # @param base [Class,Module] the target controller
249
- #
250
- # @since 0.1.0
251
- # @api private
245
+ # Framework loading entry point
252
246
  #
253
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
247
+ # @return [void]
254
248
  #
255
- # @see Lotus::Controller::Dsl
256
- def self.included(base)
257
- conf = self.configuration.duplicate
258
-
259
- base.class_eval do
260
- include Dsl
261
- include Utils::ClassAttribute
262
-
263
- class_attribute :configuration
264
- self.configuration = conf
265
- end
249
+ # @since 0.3.0
250
+ def self.load!
251
+ configuration.load!
266
252
  end
267
253
  end
268
254
  end
@@ -68,9 +68,9 @@ module Lotus
68
68
  # Controller = Lotus::Controller.duplicate(self)
69
69
  #
70
70
  # module Controllers::Dashboard
71
- # include MyApp::Controller
71
+ # class Index
72
+ # include MyApp::Action
72
73
  #
73
- # action 'Index' do
74
74
  # def call(params)
75
75
  # # ...
76
76
  # end
@@ -89,7 +89,7 @@ module Lotus
89
89
  # # => will duplicate from MyApp::Controller
90
90
  def self.for(base)
91
91
  namespace = Utils::String.new(base).namespace
92
- framework = Utils::Class.load!("(#{namespace}|Lotus)::Controller")
92
+ framework = Utils::Class.load_from_pattern!("(#{namespace}|Lotus)::Controller")
93
93
  framework.configuration.duplicate
94
94
  end
95
95
 
@@ -103,15 +103,17 @@ module Lotus
103
103
  reset!
104
104
  end
105
105
 
106
- # @attr_writer handle_exceptions [TrueClass,FalseClass] Decide if handle
107
- # exceptions with an HTTP status or not
106
+ # @attr_writer handle_exceptions [TrueClass,FalseClass] Handle exceptions
107
+ # with an HTTP status or leave them uncaught
108
108
  #
109
109
  # @since 0.2.0
110
110
  #
111
+ # @return void
112
+ #
111
113
  # @see Lotus::Controller::Configuration#handle_exceptions
112
114
  attr_writer :handle_exceptions
113
115
 
114
- # Decide if handle exceptions with an HTTP status or let them uncaught
116
+ # Handle exceptions with an HTTP status or let them uncaught
115
117
  #
116
118
  # If this value is set to `true`, the configured exceptions will return
117
119
  # the specified HTTP status, the rest of them with `500`.
@@ -180,20 +182,16 @@ module Lotus
180
182
  @handled_exceptions.merge!(exception)
181
183
  end
182
184
 
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`.
185
+ # Return a callable handler for the given exception
187
186
  #
188
- # @param exception [Hash] the exception class must be the key and the HTTP
189
- # status the value of the hash
187
+ # @param exception [Exception] an exception
190
188
  #
191
- # @since 0.2.0
189
+ # @since 0.3.0
192
190
  # @api private
193
191
  #
194
192
  # @see Lotus::Controller::Configuration#handle_exception
195
- def exception_code(exception)
196
- @handled_exceptions.fetch(exception) { DEFAULT_ERROR_CODE }
193
+ def exception_handler(exception)
194
+ @handled_exceptions.fetch(exception.class) { DEFAULT_ERROR_CODE }
197
195
  end
198
196
 
199
197
  # Specify which is the default action module to be included when we use
@@ -237,11 +235,11 @@ module Lotus
237
235
  # action_module MyAction
238
236
  # end
239
237
  #
240
- # class DashboardController
241
- # include Lotus::Controller
242
- #
238
+ # module Dashboard
243
239
  # # It includes MyAction, instead of Lotus::Action
244
- # action 'Index' do
240
+ # class Index
241
+ # include MyAction
242
+ #
245
243
  # def call(params)
246
244
  # # ...
247
245
  # end
@@ -258,7 +256,9 @@ module Lotus
258
256
  # include MyApp::Controller
259
257
  #
260
258
  # # It includes MyApp::Action, instead of Lotus::Action
261
- # action 'Index' do
259
+ # class Index
260
+ # include MyApp::Action
261
+ #
262
262
  # def call(params)
263
263
  # # ...
264
264
  # end
@@ -273,64 +273,54 @@ module Lotus
273
273
  end
274
274
  end
275
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`.
276
+ # Configure the logic to be executed when Lotus::Action is included
277
+ # This is useful to DRY code by having a single place where to configure
278
+ # shared behaviors like authentication, sessions, cookies etc.
279
279
  #
280
- # If not set, this option will be ignored.
280
+ # This method can be called multiple times.
281
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.
282
+ # @param blk [Proc] the code block
285
283
  #
286
- # @overload modules(blk)
287
- # Adds the given block
288
- # @param value [Proc] specify the modules to be included
284
+ # @return [void]
289
285
  #
290
- # @overload modules
291
- # Gets the value
292
- # @return [Array] the list of the specified procs
286
+ # @raise [ArgumentError] if called without passing a block
293
287
  #
294
- # @since 0.2.0
288
+ # @since 0.3.0
295
289
  #
296
- # @see Lotus::Controller::Dsl#action
297
- # @see Lotus::Controller#duplicate
298
- #
299
- # @example Getting the value
300
- # require 'lotus/controller'
290
+ # @see Lotus::Controller.configure
291
+ # @see Lotus::Controller.duplicate
301
292
  #
302
- # Lotus::Controller.configuration.modules # => []
303
- #
304
- # @example Setting the value
293
+ # @example Configure shared logic.
305
294
  # require 'lotus/controller'
306
- # require 'lotus/action/cookies'
307
- # require 'lotus/action/session'
308
295
  #
309
296
  # Lotus::Controller.configure do
310
- # modules do
311
- # include Lotus::Action::Cookies
312
- # include Lotus::Action::Session
297
+ # prepare do
298
+ # include Lotus::Action::Sessions
299
+ # include MyAuthentication
300
+ # use SomeMiddleWare
301
+ #
302
+ # before { authenticate! }
313
303
  # end
314
304
  # end
315
305
  #
316
- # class DashboardController
317
- # include Lotus::Controller
306
+ # module Dashboard
307
+ # class Index
308
+ # # When Lotus::Action is included, it will:
309
+ # # * Include `Lotus::Action::Session` and `MyAuthentication`
310
+ # # * Configure to use `SomeMiddleWare`
311
+ # # * Configure a `before` callback that triggers `#authenticate!`
312
+ # include Lotus::Action
318
313
  #
319
- # # It includes:
320
- # # * Lotus::Action
321
- # # * Lotus::Action::Cookies
322
- # # * Lotus::Action::Session
323
- # action 'Index' do
324
314
  # def call(params)
325
315
  # # ...
326
316
  # end
327
317
  # end
328
318
  # end
329
- def modules(&blk)
319
+ def prepare(&blk)
330
320
  if block_given?
331
321
  @modules.push(blk)
332
322
  else
333
- @modules
323
+ raise ArgumentError.new('Please provide a block')
334
324
  end
335
325
  end
336
326
 
@@ -350,16 +340,18 @@ module Lotus
350
340
  # format custom: 'application/custom'
351
341
  # end
352
342
  #
353
- # class ArticlesController
354
- # include Lotus::Controller
343
+ # module Articles
344
+ # class Index
345
+ # include Lotus::Action
355
346
  #
356
- # action 'Index' do
357
347
  # def call(params)
358
348
  # # ...
359
349
  # end
360
350
  # end
361
351
  #
362
- # action 'Show' do
352
+ # class Show
353
+ # include Lotus::Action
354
+ #
363
355
  # def call(params)
364
356
  # # ...
365
357
  # self.format = :custom
@@ -367,7 +359,7 @@ module Lotus
367
359
  # end
368
360
  # end
369
361
  #
370
- # action = ArticlesController::Index.new
362
+ # action = Articles::Index.new
371
363
  #
372
364
  # action.call({ 'HTTP_ACCEPT' => 'text/html' })
373
365
  # # => Content-Type "text/html"
@@ -379,7 +371,7 @@ module Lotus
379
371
  #
380
372
  #
381
373
  #
382
- # action = ArticlesController::Show.new
374
+ # action = Articles::Show.new
383
375
  #
384
376
  # action.call({ 'HTTP_ACCEPT' => 'text/html' })
385
377
  # # => Content-Type "application/custom"
@@ -395,7 +387,7 @@ module Lotus
395
387
  # requirement for the mime type.
396
388
  #
397
389
  # 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
390
+ # alias. If it isn't, at the runtime the framework will raise a
399
391
  # `Lotus::Controller::UnknownFormatError`.
400
392
  #
401
393
  # By default this value is nil.
@@ -436,6 +428,34 @@ module Lotus
436
428
  end
437
429
  end
438
430
 
431
+ # Set a charset as default fallback for all the requests without a strict
432
+ # requirement for the charset.
433
+ #
434
+ # By default this value is nil.
435
+ #
436
+ # @since 0.3.0
437
+ #
438
+ # @see Lotus::Action::Mime
439
+ #
440
+ # @example Getting the value
441
+ # require 'lotus/controller'
442
+ #
443
+ # Lotus::Controller.configuration.default_charset # => nil
444
+ #
445
+ # @example Setting the value
446
+ # require 'lotus/controller'
447
+ #
448
+ # Lotus::Controller.configure do
449
+ # default_charset 'koi8-r'
450
+ # end
451
+ def default_charset(charset = nil)
452
+ if charset
453
+ @default_charset = charset
454
+ else
455
+ @default_charset
456
+ end
457
+ end
458
+
439
459
  # Returns a format for the given mime type
440
460
  #
441
461
  # @param mime_type [#to_s,#to_str] A mime type
@@ -455,6 +475,9 @@ module Lotus
455
475
  # @param format [#to_sym] a format
456
476
  #
457
477
  # @return [String,nil] the corresponding mime type, if present
478
+ #
479
+ # @since 0.2.0
480
+ # @api private
458
481
  def mime_type_for(format)
459
482
  @formats.key(format)
460
483
  end
@@ -473,9 +496,20 @@ module Lotus
473
496
  c.modules = modules.dup
474
497
  c.formats = formats.dup
475
498
  c.default_format = default_format
499
+ c.default_charset = default_charset
476
500
  end
477
501
  end
478
502
 
503
+ # Return included modules
504
+ #
505
+ # @return [Array<Proc>] array of included blocks
506
+ #
507
+ # @since 0.2.0
508
+ # @api private
509
+ #
510
+ # @see Lotus::Controller::Configuration#prepare
511
+ attr_reader :modules
512
+
479
513
  # Reset all the values to the defaults
480
514
  #
481
515
  # @since 0.2.0
@@ -486,25 +520,42 @@ module Lotus
486
520
  @modules = []
487
521
  @formats = DEFAULT_FORMATS.dup
488
522
  @default_format = nil
523
+ @default_charset = nil
489
524
  @action_module = ::Lotus::Action
490
525
  end
491
526
 
492
- # Load the configuration for the given action
527
+ # Copy the configuration for the given action
493
528
  #
494
- # @since 0.2.0
529
+ # @param base [Class] the target action
530
+ #
531
+ # @return void
532
+ #
533
+ # @since 0.3.0
495
534
  # @api private
496
- def load!(base)
535
+ #
536
+ # @see Lotus::Controller::Configurable.included
537
+ def copy!(base)
497
538
  modules.each do |mod|
498
539
  base.class_eval(&mod)
499
540
  end
500
541
  end
501
542
 
543
+ # Load the framework
544
+ #
545
+ # @since 0.3.0
546
+ # @api private
547
+ def load!
548
+ freeze
549
+ end
550
+
502
551
  protected
552
+
503
553
  attr_accessor :handled_exceptions
504
554
  attr_accessor :formats
505
555
  attr_writer :action_module
506
556
  attr_writer :modules
507
557
  attr_writer :default_format
558
+ attr_writer :default_charset
508
559
  end
509
560
  end
510
561
  end