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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -127
- data/README.md +289 -89
- data/lib/lotus/action.rb +12 -6
- data/lib/lotus/action/cache.rb +174 -0
- data/lib/lotus/action/cache/cache_control.rb +70 -0
- data/lib/lotus/action/cache/conditional_get.rb +93 -0
- data/lib/lotus/action/cache/directives.rb +99 -0
- data/lib/lotus/action/cache/expires.rb +73 -0
- data/lib/lotus/action/callable.rb +3 -2
- data/lib/lotus/action/callbacks.rb +8 -0
- data/lib/lotus/action/configurable.rb +3 -2
- data/lib/lotus/action/cookies.rb +13 -12
- data/lib/lotus/action/exposable.rb +31 -4
- data/lib/lotus/action/flash.rb +141 -0
- data/lib/lotus/action/glue.rb +35 -0
- data/lib/lotus/action/mime.rb +82 -4
- data/lib/lotus/action/params.rb +87 -3
- data/lib/lotus/action/rack.rb +66 -51
- data/lib/lotus/action/redirect.rb +21 -3
- data/lib/lotus/action/session.rb +97 -0
- data/lib/lotus/action/throwable.rb +24 -4
- data/lib/lotus/action/validatable.rb +128 -0
- data/lib/lotus/controller.rb +11 -25
- data/lib/lotus/controller/configuration.rb +117 -66
- data/lib/lotus/controller/version.rb +1 -1
- data/lib/lotus/http/status.rb +5 -1
- data/lib/rack-patch.rb +8 -2
- data/lotus-controller.gemspec +6 -4
- metadata +54 -21
- data/lib/lotus/controller/dsl.rb +0 -56
data/lib/lotus/action/params.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'lotus/utils/hash'
|
2
|
+
require 'lotus/validations'
|
3
|
+
require 'set'
|
2
4
|
|
3
5
|
module Lotus
|
4
6
|
module Action
|
@@ -24,6 +26,67 @@ module Lotus
|
|
24
26
|
# @since 0.1.0
|
25
27
|
ROUTER_PARAMS = 'router.params'.freeze
|
26
28
|
|
29
|
+
# Whitelist and validate a parameter
|
30
|
+
#
|
31
|
+
# @param name [#to_sym] The name of the param to whitelist
|
32
|
+
#
|
33
|
+
# @raise [ArgumentError] if one the validations is unknown, or if
|
34
|
+
# the size validator is used with an object that can't be coerced to
|
35
|
+
# integer.
|
36
|
+
#
|
37
|
+
# @return void
|
38
|
+
#
|
39
|
+
# @since 0.3.0
|
40
|
+
#
|
41
|
+
# @see http://rdoc.info/gems/lotus-validations/Lotus/Validations
|
42
|
+
#
|
43
|
+
# @example Whitelisting
|
44
|
+
# require 'lotus/controller'
|
45
|
+
#
|
46
|
+
# class SignupParams < Lotus::Action::Params
|
47
|
+
# param :email
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# params = SignupParams.new({id: 23, email: 'mjb@example.com'})
|
51
|
+
#
|
52
|
+
# params[:email] # => 'mjb@example.com'
|
53
|
+
# params[:id] # => nil
|
54
|
+
#
|
55
|
+
# @example Validation
|
56
|
+
# require 'lotus/controller'
|
57
|
+
#
|
58
|
+
# class SignupParams < Lotus::Action::Params
|
59
|
+
# param :email, presence: true
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# params = SignupParams.new({})
|
63
|
+
#
|
64
|
+
# params[:email] # => nil
|
65
|
+
# params.valid? # => false
|
66
|
+
#
|
67
|
+
# @example Unknown validation
|
68
|
+
# require 'lotus/controller'
|
69
|
+
#
|
70
|
+
# class SignupParams < Lotus::Action::Params
|
71
|
+
# param :email, unknown: true # => raise ArgumentError
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# @example Wrong size validation
|
75
|
+
# require 'lotus/controller'
|
76
|
+
#
|
77
|
+
# class SignupParams < Lotus::Action::Params
|
78
|
+
# param :email, size: 'twentythree'
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# params = SignupParams.new({})
|
82
|
+
# params.valid? # => raise ArgumentError
|
83
|
+
def self.param(name, options = {})
|
84
|
+
attribute name, options
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
|
88
|
+
include Lotus::Validations
|
89
|
+
|
27
90
|
# @attr_reader env [Hash] the Rack env
|
28
91
|
#
|
29
92
|
# @since 0.2.0
|
@@ -38,11 +101,16 @@ module Lotus
|
|
38
101
|
#
|
39
102
|
# @since 0.1.0
|
40
103
|
def initialize(env)
|
41
|
-
@env
|
42
|
-
|
104
|
+
@env = env
|
105
|
+
super(_compute_params)
|
43
106
|
freeze
|
44
107
|
end
|
45
108
|
|
109
|
+
def self.defined_attributes
|
110
|
+
result = super
|
111
|
+
return result if result.to_ary.any?
|
112
|
+
end
|
113
|
+
|
46
114
|
# Returns the object associated with the given key
|
47
115
|
#
|
48
116
|
# @param key [Symbol] the key
|
@@ -51,10 +119,26 @@ module Lotus
|
|
51
119
|
#
|
52
120
|
# @since 0.2.0
|
53
121
|
def [](key)
|
54
|
-
@
|
122
|
+
@attributes.get(key)
|
55
123
|
end
|
56
124
|
|
125
|
+
# Returns the Ruby's hash
|
126
|
+
#
|
127
|
+
# @return [Hash]
|
128
|
+
#
|
129
|
+
# @since 0.3.0
|
130
|
+
def to_h
|
131
|
+
@attributes.to_h
|
132
|
+
end
|
133
|
+
alias_method :to_hash, :to_h
|
134
|
+
|
57
135
|
private
|
136
|
+
def _compute_params
|
137
|
+
Utils::Hash.new(
|
138
|
+
_extract
|
139
|
+
).symbolize!
|
140
|
+
end
|
141
|
+
|
58
142
|
def _extract
|
59
143
|
{}.tap do |result|
|
60
144
|
if env.has_key?(RACK_INPUT)
|
data/lib/lotus/action/rack.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
1
3
|
module Lotus
|
2
4
|
module Action
|
3
5
|
# Rack integration API
|
4
6
|
#
|
5
7
|
# @since 0.1.0
|
6
8
|
module Rack
|
7
|
-
# The default session key for Rack
|
8
|
-
#
|
9
|
-
# @since 0.1.0
|
10
|
-
# @api private
|
11
|
-
SESSION_KEY = 'rack.session'.freeze
|
12
|
-
|
13
9
|
# The default HTTP response code
|
14
10
|
#
|
15
11
|
# @since 0.1.0
|
@@ -22,6 +18,14 @@ module Lotus
|
|
22
18
|
# @api private
|
23
19
|
DEFAULT_RESPONSE_BODY = []
|
24
20
|
|
21
|
+
# The default HTTP Request ID length
|
22
|
+
#
|
23
|
+
# @since 0.3.0
|
24
|
+
# @api private
|
25
|
+
#
|
26
|
+
# @see Lotus::Action::Rack#request_id
|
27
|
+
DEFAULT_REQUEST_ID_LENGTH = 16
|
28
|
+
|
25
29
|
# Override Ruby's hook for modules.
|
26
30
|
# It includes basic Lotus::Action modules to the given class.
|
27
31
|
#
|
@@ -56,10 +60,9 @@ module Lotus
|
|
56
60
|
# @example Class Middleware
|
57
61
|
# require 'lotus/controller'
|
58
62
|
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# action 'Create' do
|
63
|
+
# module Sessions
|
64
|
+
# class Create
|
65
|
+
# include Lotus::Action
|
63
66
|
# use OmniAuth
|
64
67
|
#
|
65
68
|
# def call(params)
|
@@ -71,10 +74,9 @@ module Lotus
|
|
71
74
|
# @example Instance Middleware
|
72
75
|
# require 'lotus/controller'
|
73
76
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
# action 'Create' do
|
77
|
+
# module Sessions
|
78
|
+
# class Create
|
79
|
+
# include Lotus::Controller
|
78
80
|
# use XMiddleware.new('x', 123)
|
79
81
|
#
|
80
82
|
# def call(params)
|
@@ -90,31 +92,61 @@ module Lotus
|
|
90
92
|
end
|
91
93
|
|
92
94
|
protected
|
93
|
-
|
95
|
+
|
96
|
+
# Gets the headers from the response
|
94
97
|
#
|
95
|
-
# @
|
96
|
-
# @return [void]
|
98
|
+
# @return [Hash] the HTTP headers from the response
|
97
99
|
#
|
98
100
|
# @since 0.1.0
|
99
101
|
#
|
100
102
|
# @example
|
101
103
|
# require 'lotus/controller'
|
102
104
|
#
|
103
|
-
# class
|
105
|
+
# class Show
|
104
106
|
# include Lotus::Action
|
105
107
|
#
|
106
108
|
# def call(params)
|
107
109
|
# # ...
|
108
|
-
# self.
|
110
|
+
# self.headers # => { ... }
|
111
|
+
# self.headers.merge!({'X-Custom' => 'OK'})
|
109
112
|
# end
|
110
113
|
# end
|
111
|
-
def
|
112
|
-
@
|
114
|
+
def headers
|
115
|
+
@headers
|
113
116
|
end
|
114
117
|
|
115
|
-
#
|
118
|
+
# Returns a serialized Rack response (Array), according to the current
|
119
|
+
# status code, headers, and body.
|
116
120
|
#
|
117
|
-
# @
|
121
|
+
# @return [Array] the serialized response
|
122
|
+
#
|
123
|
+
# @since 0.1.0
|
124
|
+
# @api private
|
125
|
+
#
|
126
|
+
# @see Lotus::Action::Rack::DEFAULT_RESPONSE_CODE
|
127
|
+
# @see Lotus::Action::Rack::DEFAULT_RESPONSE_BODY
|
128
|
+
# @see Lotus::Action::Rack#status=
|
129
|
+
# @see Lotus::Action::Rack#headers
|
130
|
+
# @see Lotus::Action::Rack#body=
|
131
|
+
def response
|
132
|
+
[ @_status || DEFAULT_RESPONSE_CODE, headers, @_body || DEFAULT_RESPONSE_BODY.dup ]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Calculates an unique ID for the current request
|
136
|
+
#
|
137
|
+
# @return [String] The unique ID
|
138
|
+
#
|
139
|
+
# @since 0.3.0
|
140
|
+
def request_id
|
141
|
+
# FIXME make this number configurable and document the probabilities of clashes
|
142
|
+
@request_id ||= SecureRandom.hex(DEFAULT_REQUEST_ID_LENGTH)
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# Sets the HTTP status code for the response
|
148
|
+
#
|
149
|
+
# @param status [Fixnum] an HTTP status code
|
118
150
|
# @return [void]
|
119
151
|
#
|
120
152
|
# @since 0.1.0
|
@@ -122,22 +154,22 @@ module Lotus
|
|
122
154
|
# @example
|
123
155
|
# require 'lotus/controller'
|
124
156
|
#
|
125
|
-
# class
|
157
|
+
# class Create
|
126
158
|
# include Lotus::Action
|
127
159
|
#
|
128
160
|
# def call(params)
|
129
161
|
# # ...
|
130
|
-
# self.
|
162
|
+
# self.status = 201
|
131
163
|
# end
|
132
164
|
# end
|
133
|
-
def
|
134
|
-
|
135
|
-
@_body = body
|
165
|
+
def status=(status)
|
166
|
+
@_status = status
|
136
167
|
end
|
137
168
|
|
138
|
-
#
|
169
|
+
# Sets the body of the response
|
139
170
|
#
|
140
|
-
# @
|
171
|
+
# @param body [String] the body of the response
|
172
|
+
# @return [void]
|
141
173
|
#
|
142
174
|
# @since 0.1.0
|
143
175
|
#
|
@@ -149,29 +181,12 @@ module Lotus
|
|
149
181
|
#
|
150
182
|
# def call(params)
|
151
183
|
# # ...
|
152
|
-
# self.
|
153
|
-
# self.headers.merge!({'X-Custom' => 'OK'})
|
184
|
+
# self.body = 'Hi!'
|
154
185
|
# end
|
155
186
|
# end
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# Returns a serialized Rack response (Array), according to the current
|
161
|
-
# status code, headers, and body.
|
162
|
-
#
|
163
|
-
# @return [Array] the serialized response
|
164
|
-
#
|
165
|
-
# @since 0.1.0
|
166
|
-
# @api private
|
167
|
-
#
|
168
|
-
# @see Lotus::Action::Rack::DEFAULT_RESPONSE_CODE
|
169
|
-
# @see Lotus::Action::Rack::DEFAULT_RESPONSE_BODY
|
170
|
-
# @see Lotus::Action::Rack#status=
|
171
|
-
# @see Lotus::Action::Rack#headers
|
172
|
-
# @see Lotus::Action::Rack#body=
|
173
|
-
def response
|
174
|
-
[ @_status || DEFAULT_RESPONSE_CODE, headers, @_body || DEFAULT_RESPONSE_BODY.dup ]
|
187
|
+
def body=(body)
|
188
|
+
body = Array(body) unless body.respond_to?(:each)
|
189
|
+
@_body = body
|
175
190
|
end
|
176
191
|
end
|
177
192
|
end
|
@@ -10,7 +10,7 @@ module Lotus
|
|
10
10
|
# @api private
|
11
11
|
LOCATION = 'Location'.freeze
|
12
12
|
|
13
|
-
|
13
|
+
private
|
14
14
|
|
15
15
|
# Redirect to the given URL
|
16
16
|
#
|
@@ -19,7 +19,7 @@ module Lotus
|
|
19
19
|
#
|
20
20
|
# @since 0.1.0
|
21
21
|
#
|
22
|
-
# @example
|
22
|
+
# @example With default status code (302)
|
23
23
|
# require 'lotus/controller'
|
24
24
|
#
|
25
25
|
# class Create
|
@@ -30,8 +30,26 @@ module Lotus
|
|
30
30
|
# redirect_to 'http://example.com/articles/23'
|
31
31
|
# end
|
32
32
|
# end
|
33
|
+
#
|
34
|
+
# action = Create.new
|
35
|
+
# action.call({}) # => [302, {'Location' => '/articles/23'}, '']
|
36
|
+
#
|
37
|
+
# @example With custom status code
|
38
|
+
# require 'lotus/controller'
|
39
|
+
#
|
40
|
+
# class Create
|
41
|
+
# include Lotus::Action
|
42
|
+
#
|
43
|
+
# def call(params)
|
44
|
+
# # ...
|
45
|
+
# redirect_to 'http://example.com/articles/23', status: 301
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# action = Create.new
|
50
|
+
# action.call({}) # => [301, {'Location' => '/articles/23'}, '']
|
33
51
|
def redirect_to(url, status: 302)
|
34
|
-
headers
|
52
|
+
headers[LOCATION] = url
|
35
53
|
self.status = status
|
36
54
|
end
|
37
55
|
end
|
data/lib/lotus/action/session.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'lotus/action/flash'
|
2
|
+
|
1
3
|
module Lotus
|
2
4
|
module Action
|
3
5
|
# Session API
|
@@ -12,6 +14,12 @@ module Lotus
|
|
12
14
|
# @api private
|
13
15
|
SESSION_KEY = 'rack.session'.freeze
|
14
16
|
|
17
|
+
# The key that is used by flash to transport errors
|
18
|
+
#
|
19
|
+
# @since 0.3.0
|
20
|
+
# @api private
|
21
|
+
ERRORS_KEY = :__errors
|
22
|
+
|
15
23
|
protected
|
16
24
|
|
17
25
|
# Gets the session from the request and expose it as an Hash.
|
@@ -44,6 +52,95 @@ module Lotus
|
|
44
52
|
def session
|
45
53
|
@_env[SESSION_KEY] ||= {}
|
46
54
|
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Container useful to transport data with the HTTP session
|
59
|
+
#
|
60
|
+
# @return [Lotus::Action::Flash] a Flash instance
|
61
|
+
#
|
62
|
+
# @since 0.3.0
|
63
|
+
# @api private
|
64
|
+
#
|
65
|
+
# @see Lotus::Action::Flash
|
66
|
+
def flash
|
67
|
+
@flash ||= Flash.new(session, request_id)
|
68
|
+
end
|
69
|
+
|
70
|
+
# In case of validations errors, preserve those informations after a
|
71
|
+
# redirect.
|
72
|
+
#
|
73
|
+
# @return [void]
|
74
|
+
#
|
75
|
+
# @since 0.3.0
|
76
|
+
# @api private
|
77
|
+
#
|
78
|
+
# @see Lotus::Action::Redirect#redirect_to
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# require 'lotus/controller'
|
82
|
+
#
|
83
|
+
# module Comments
|
84
|
+
# class Index
|
85
|
+
# include Lotus::Action
|
86
|
+
# include Lotus::Action::Session
|
87
|
+
#
|
88
|
+
# expose :comments
|
89
|
+
#
|
90
|
+
# def call(params)
|
91
|
+
# @comments = CommentRepository.all
|
92
|
+
# end
|
93
|
+
# end
|
94
|
+
#
|
95
|
+
# class Create
|
96
|
+
# include Lotus::Action
|
97
|
+
# include Lotus::Action::Session
|
98
|
+
#
|
99
|
+
# params do
|
100
|
+
# param :text, type: String, presence: true
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# def call(params)
|
104
|
+
# comment = Comment.new(params)
|
105
|
+
# CommentRepository.create(comment) if params.valid?
|
106
|
+
#
|
107
|
+
# redirect_to '/comments'
|
108
|
+
# end
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# # The validation errors caused by Comments::Create are available
|
113
|
+
# # **after the redirect** in the context of Comments::Index.
|
114
|
+
def redirect_to(*args)
|
115
|
+
super
|
116
|
+
flash[ERRORS_KEY] = errors.to_a unless params.valid?
|
117
|
+
end
|
118
|
+
|
119
|
+
# Read errors from flash or delegate to the superclass
|
120
|
+
#
|
121
|
+
# @return [Lotus::Validations::Errors] A collection of validation errors
|
122
|
+
#
|
123
|
+
# @since 0.3.0
|
124
|
+
# @api private
|
125
|
+
#
|
126
|
+
# @see Lotus::Action::Validatable
|
127
|
+
# @see Lotus::Action::Session#flash
|
128
|
+
def errors
|
129
|
+
flash[ERRORS_KEY] || super
|
130
|
+
end
|
131
|
+
|
132
|
+
# Finalize the response
|
133
|
+
#
|
134
|
+
# @return [void]
|
135
|
+
#
|
136
|
+
# @since 0.3.0
|
137
|
+
# @api private
|
138
|
+
#
|
139
|
+
# @see Lotus::Action#finish
|
140
|
+
def finish
|
141
|
+
super
|
142
|
+
flash.clear
|
143
|
+
end
|
47
144
|
end
|
48
145
|
end
|
49
146
|
end
|