lotus-controller 0.2.0 → 0.3.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.
@@ -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