lotus-controller 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ module Lotus
2
+ module Action
3
+ # Session API
4
+ #
5
+ # This module isn't included by default.
6
+ #
7
+ # @since 0.1.0
8
+ module Session
9
+ # The key that returns raw session from the Rack env
10
+ #
11
+ # @since 0.1.0
12
+ SESSION_KEY = 'rack.session'.freeze
13
+
14
+ protected
15
+
16
+ # Gets the session from the request and expose it as an Hash.
17
+ #
18
+ # @return [Hash] the HTTP session from the request
19
+ #
20
+ # @since 0.1.0
21
+ #
22
+ # @example
23
+ # require 'lotus/controller'
24
+ # require 'lotus/action/session'
25
+ #
26
+ # class Show
27
+ # include Lotus::Action
28
+ # include Lotus::Action::Session
29
+ #
30
+ # def call(params)
31
+ # # ...
32
+ #
33
+ # # get a value
34
+ # session[:user_id] # => '23'
35
+ #
36
+ # # set a value
37
+ # session[:foo] = 'bar'
38
+ #
39
+ # # remove a value
40
+ # session[:bax] = nil
41
+ # end
42
+ # end
43
+ def session
44
+ @_env[SESSION_KEY] ||= {}
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,130 @@
1
+ require 'lotus/utils/class_attribute'
2
+ require 'lotus/http/status'
3
+
4
+ module Lotus
5
+ module Action
6
+ # Throw API
7
+ #
8
+ # @since 0.1.0
9
+ #
10
+ # @see Lotus::Action::Throwable::ClassMethods#handle_exception
11
+ # @see Lotus::Action::Throwable#throw
12
+ # @see Lotus::Action::Throwable#status
13
+ module Throwable
14
+ def self.included(base)
15
+ base.class_eval do
16
+ extend ClassMethods
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ def self.extended(base)
22
+ base.class_eval do
23
+ include Utils::ClassAttribute
24
+
25
+ # Action handled exceptions.
26
+ #
27
+ # When an handled exception is raised during #call execution, it will be
28
+ # translated into the associated HTTP status.
29
+ #
30
+ # By default there aren't handled exceptions, all the errors are threaded
31
+ # as a Server Side Error (500).
32
+ #
33
+ # @api private
34
+ # @since 0.1.0
35
+ #
36
+ # @see Lotus::Controller.handled_exceptions
37
+ # @see Lotus::Action::Throwable.handle_exception
38
+ class_attribute :handled_exceptions
39
+ self.handled_exceptions = Controller.handled_exceptions.dup
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ # Handle the given exception with an HTTP status code.
46
+ #
47
+ # When the exception is raise during #call execution, it will be
48
+ # translated into the associated HTTP status.
49
+ #
50
+ # This is a fine grained control, for a global configuration see
51
+ # Lotus::Action.handled_exceptions
52
+ #
53
+ # @param exception [Class] the exception class
54
+ # @param status [Fixmun] a valid HTTP status
55
+ #
56
+ # @since 0.1.0
57
+ #
58
+ # @see Lotus::Action.handled_exceptions
59
+ #
60
+ # @example
61
+ # require 'lotus/controller'
62
+ #
63
+ # class Show
64
+ # include Lotus::Action
65
+ # handle_exception RecordNotFound, 404
66
+ #
67
+ # def call(params)
68
+ # # ...
69
+ # raise RecordNotFound.new
70
+ # end
71
+ # end
72
+ #
73
+ # Show.new.call({id: 1}) # => [404, {}, ['Not Found']]
74
+ def handle_exception(exception, status)
75
+ self.handled_exceptions[exception] = status
76
+ end
77
+ end
78
+
79
+ protected
80
+
81
+ # Throw the given HTTP status code.
82
+ #
83
+ # When used, the execution of a callback or of an action is interrupted
84
+ # and the control returns to the framework, that decides how to handle
85
+ # the event.
86
+ #
87
+ # It also sets the response body with the message associated to the code
88
+ # (eg 404 will set `"Not Found"`).
89
+ #
90
+ # @param code [Fixnum] a valid HTTP status code
91
+ #
92
+ # @since 0.1.0
93
+ #
94
+ # @see Lotus::Controller#handled_exceptions
95
+ # @see Lotus::Action::Throwable#handle_exception
96
+ # @see Lotus::Http::Status:ALL
97
+ def throw(code)
98
+ status(*Http::Status.for_code(code))
99
+ super :halt
100
+ end
101
+
102
+ # Sets the given code and message for the response
103
+ #
104
+ # @param code [Fixnum] a valid HTTP status code
105
+ # @param message [String] the response body
106
+ #
107
+ # @since 0.1.0
108
+ # @see Lotus::Http::Status:ALL
109
+ def status(code, message)
110
+ self.status = code
111
+ self.body = message
112
+ end
113
+
114
+ private
115
+ def _rescue
116
+ catch :halt do
117
+ begin
118
+ yield
119
+ rescue => exception
120
+ _handle_exception(exception)
121
+ end
122
+ end
123
+ end
124
+
125
+ def _handle_exception(exception)
126
+ throw self.class.handled_exceptions.fetch(exception.class, 500)
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,7 +1,70 @@
1
- require "lotus/controller/version"
1
+ require 'lotus/utils/class_attribute'
2
+ require 'lotus/action'
3
+ require 'lotus/controller/dsl'
4
+ require 'lotus/controller/version'
5
+ require 'rack-patch'
2
6
 
3
7
  module Lotus
8
+ # A set of logically grouped actions
9
+ #
10
+ # @since 0.1.0
11
+ #
12
+ # @see Lotus::Action
13
+ #
14
+ # @example
15
+ # require 'lotus/controller'
16
+ #
17
+ # class ArticlesController
18
+ # include Lotus::Controller
19
+ #
20
+ # action 'Index' do
21
+ # # ...
22
+ # end
23
+ #
24
+ # action 'Show' do
25
+ # # ...
26
+ # end
27
+ # end
4
28
  module Controller
5
- # Your code goes here...
29
+ include Utils::ClassAttribute
30
+
31
+ # Global handled exceptions.
32
+ # When an handled exception is raised during #call execution, it will be
33
+ # translated into the associated HTTP status.
34
+ #
35
+ # By default there aren't handled exceptions, all the errors are threaded
36
+ # as a Server Side Error (500).
37
+ #
38
+ # **Important:** Be sure to set this configuration, **before** the actions
39
+ # and controllers of your application are loaded.
40
+ #
41
+ # @since 0.1.0
42
+ #
43
+ # @see Lotus::Action::Throwable
44
+ #
45
+ # @example
46
+ # require 'lotus/controller'
47
+ #
48
+ # Lotus::Controller.handled_exceptions = { RecordNotFound => 404 }
49
+ #
50
+ # class Show
51
+ # include Lotus::Action
52
+ #
53
+ # def call(params)
54
+ # # ...
55
+ # raise RecordNotFound.new
56
+ # end
57
+ # end
58
+ #
59
+ # Show.new.call({id: 1}) # => [404, {}, ['Not Found']]
60
+ class_attribute :handled_exceptions
61
+ self.handled_exceptions = {}
62
+
63
+ def self.included(base)
64
+ base.class_eval do
65
+ include Dsl
66
+ end
67
+ end
6
68
  end
7
69
  end
70
+
@@ -0,0 +1,54 @@
1
+ module Lotus
2
+ module Controller
3
+ # Public DSL
4
+ #
5
+ # @since 0.1.0
6
+ module Dsl
7
+ def self.included(base)
8
+ base.class_eval do
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ # Define an action for the given name.
15
+ # It generates a concrete class for the action, for this reason the name
16
+ # MUST be a valid name for Ruby.
17
+ #
18
+ # @param name [String] the name of the action
19
+ # @param blk [Proc] the code of the action
20
+ #
21
+ # @raise TypeError when the name isn't a valid Ruby name
22
+ #
23
+ # @since 0.1.0
24
+ #
25
+ # @example
26
+ # require 'lotus/controller'
27
+ #
28
+ # class ArticlesController
29
+ # include Lotus::Controller
30
+ #
31
+ # action 'Index' do
32
+ # def call(params)
33
+ # # ...
34
+ # end
35
+ # end
36
+ #
37
+ # action 'Show' do
38
+ # def call(params)
39
+ # # ...
40
+ # end
41
+ # end
42
+ # end
43
+ def action(name, &blk)
44
+ const_set(name, Class.new)
45
+
46
+ const_get(name).tap do |klass|
47
+ klass.class_eval { include ::Lotus::Action }
48
+ klass.class_eval(&blk)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,5 +1,8 @@
1
1
  module Lotus
2
2
  module Controller
3
- VERSION = "0.0.0"
3
+ # Defines the version
4
+ #
5
+ # @since 0.1.0
6
+ VERSION = '0.1.0'
4
7
  end
5
8
  end
@@ -0,0 +1,103 @@
1
+ module Lotus
2
+ module Http
3
+ # An HTTP status
4
+ #
5
+ # @since 0.1.0
6
+ # @api private
7
+ class Status
8
+ # A set of standard codes and messages for HTTP statuses
9
+ #
10
+ # @since 0.1.0
11
+ # @api private
12
+ ALL = {
13
+ 100 => 'Continue',
14
+ 101 => 'Switching Protocols',
15
+ 102 => 'Processing (WebDAV) (RFC 2518)',
16
+ 103 => 'Checkpoint',
17
+ 122 => 'Request-URI too long',
18
+ 200 => 'OK',
19
+ 201 => 'Created',
20
+ 202 => 'Accepted',
21
+ 203 => 'Non-Authoritative Information',
22
+ 204 => 'No Content',
23
+ 205 => 'Reset Content',
24
+ 206 => 'Partial Content',
25
+ 207 => 'Multi-Status (WebDAV) (RFC 4918)',
26
+ 208 => 'Already Reported (WebDAV) (RFC 5842)',
27
+ 226 => 'IM Used (RFC 3229)',
28
+ 300 => 'Multiple Choices',
29
+ 301 => 'Moved Permanently',
30
+ 302 => 'Found',
31
+ 303 => 'See Other',
32
+ 304 => 'Not Modified',
33
+ 305 => 'Use Proxy',
34
+ 306 => 'Switch Proxy',
35
+ 307 => 'Temporary Redirect',
36
+ 308 => 'Resume Incomplete',
37
+ 400 => 'Bad Request',
38
+ 401 => 'Unauthorized',
39
+ 402 => 'Payment Required',
40
+ 403 => 'Forbidden',
41
+ 404 => 'Not Found',
42
+ 405 => 'Method Not Allowed',
43
+ 406 => 'Not Acceptable',
44
+ 407 => 'Proxy Authentication Required',
45
+ 408 => 'Request Timeout',
46
+ 409 => 'Conflict',
47
+ 410 => 'Gone',
48
+ 411 => 'Length Required',
49
+ 412 => 'Precondition Failed',
50
+ 413 => 'Request Entity Too Large',
51
+ 414 => 'Request-URI Too Long',
52
+ 415 => 'Unsupported Media Type',
53
+ 416 => 'Requested Range Not Satisfiable',
54
+ 417 => 'Expectation Failed',
55
+ 418 => 'I\'m a teapot (RFC 2324)',
56
+ 420 => 'Enhance Your Calm',
57
+ 422 => 'Unprocessable Entity (WebDAV) (RFC 4918)',
58
+ 423 => 'Locked (WebDAV) (RFC 4918)',
59
+ 424 => 'Failed Dependency (WebDAV) (RFC 4918)',
60
+ 426 => 'Upgrade Required (RFC 2817)',
61
+ 428 => 'Precondition Required',
62
+ 429 => 'Too Many Requests',
63
+ 431 => 'Request Header Fields Too Large',
64
+ 444 => 'No Response',
65
+ 449 => 'Retry With',
66
+ 450 => 'Blocked by Windows Parental Controls',
67
+ 451 => 'Wrong Exchange server',
68
+ 499 => 'Client Closed Request',
69
+ 500 => 'Internal Server Error',
70
+ 501 => 'Not Implemented',
71
+ 502 => 'Bad Gateway',
72
+ 503 => 'Service Unavailable',
73
+ 504 => 'Gateway Timeout',
74
+ 505 => 'HTTP Version Not Supported',
75
+ 506 => 'Variant Also Negotiates (RFC 2295)',
76
+ 507 => 'Insufficient Storage (WebDAV) (RFC 4918)',
77
+ 508 => 'Loop Detected (WebDAV) (RFC 5842)',
78
+ 509 => 'Bandwidth Limit Exceeded (Apache bw\/limited extension)',
79
+ 510 => 'Not Extended (RFC 2774)',
80
+ 511 => 'Network Authentication Required',
81
+ 598 => 'Network read timeout error',
82
+ 599 => 'Network connect timeout error'
83
+ }.freeze
84
+
85
+ # Return a status for the given code
86
+ #
87
+ # @param code [Fixnum] a valid HTTP code
88
+ #
89
+ # @return [Array] a pair of code and message for an HTTP status
90
+ #
91
+ # @since 0.1.0
92
+ # @api private
93
+ #
94
+ # @example
95
+ # require 'lotus/http/status'
96
+ #
97
+ # Lotus::Http::Status.for_code(418) # => [418, "I'm a teapot (RFC 2324)"]
98
+ def self.for_code(code)
99
+ ALL.assoc(code)
100
+ end
101
+ end
102
+ end
103
+ end
data/lib/rack-patch.rb ADDED
@@ -0,0 +1,20 @@
1
+ # see https://github.com/rack/rack/pull/659
2
+ require 'rack'
3
+
4
+ if Rack.release <= '1.5'
5
+ require 'rack/utils'
6
+
7
+ Rack::Utils.class_eval do
8
+ def self.best_q_match(q_value_header, available_mimes)
9
+ values = q_values(q_value_header)
10
+
11
+ values.map do |req_mime, quality|
12
+ match = available_mimes.find { |am| Rack::Mime.match?(am, req_mime) }
13
+ next unless match
14
+ [match, quality]
15
+ end.compact.sort_by do |match, quality|
16
+ (match.split('/', 2).count('*') * -10) + quality
17
+ end.last.first
18
+ end
19
+ end
20
+ end
@@ -4,20 +4,25 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'lotus/controller/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "lotus-controller"
7
+ spec.name = 'lotus-controller'
8
8
  spec.version = Lotus::Controller::VERSION
9
- spec.authors = ["Luca Guidi"]
10
- spec.email = ["me@lucaguidi.com"]
11
- spec.summary = %q{Controller layer for Lotus}
9
+ spec.authors = ['Luca Guidi']
10
+ spec.email = ['me@lucaguidi.com']
12
11
  spec.description = %q{Controller layer for Lotus}
13
- spec.homepage = ""
14
- spec.license = "MIT"
12
+ spec.summary = %q{Controller layer for Lotus, compatible with Rack}
13
+ spec.homepage = 'http://lotusrb.org'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
17
+ spec.executables = []
18
+ spec.test_files = spec.files.grep(%r{^(test)/})
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.5"
22
- spec.add_development_dependency "rake"
21
+ spec.add_dependency 'rack', '~> 1.5'
22
+ spec.add_dependency 'lotus-utils', '~> 0.1'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.5'
25
+ spec.add_development_dependency 'minitest', '~> 5'
26
+ spec.add_development_dependency 'rack-test', '~> 0.6'
27
+ spec.add_development_dependency 'rake', '~> 10'
23
28
  end