lotus-controller 0.0.0 → 0.1.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.
@@ -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