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.
- 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
|