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
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'lotus/action/cache/cache_control'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Action
|
5
|
+
module Cache
|
6
|
+
|
7
|
+
# Module with Expires logic
|
8
|
+
#
|
9
|
+
# @since 0.3.0
|
10
|
+
# @api private
|
11
|
+
module Expires
|
12
|
+
|
13
|
+
# The HTTP header for Expires
|
14
|
+
#
|
15
|
+
# @since 0.3.0
|
16
|
+
# @api private
|
17
|
+
HEADER = 'Expires'.freeze
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend ClassMethods
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def expires(amount, *values)
|
25
|
+
@expires_directives ||= Directives.new(amount, *values)
|
26
|
+
end
|
27
|
+
|
28
|
+
def expires_directives
|
29
|
+
@expires_directives || Object.new.tap do |null_object|
|
30
|
+
def null_object.headers
|
31
|
+
Hash.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Finalize the response including default cache headers into the response
|
38
|
+
#
|
39
|
+
# @since 0.3.0
|
40
|
+
# @api private
|
41
|
+
#
|
42
|
+
# @see Lotus::Action#finish
|
43
|
+
def finish
|
44
|
+
super
|
45
|
+
headers.merge!(self.class.expires_directives.headers) unless headers.include? HEADER
|
46
|
+
end
|
47
|
+
|
48
|
+
# Class which stores Expires directives
|
49
|
+
#
|
50
|
+
# @since 0.3.0
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
class Directives
|
55
|
+
def initialize(amount, *values)
|
56
|
+
@amount = amount
|
57
|
+
@cache_control = Lotus::Action::Cache::CacheControl::Directives.new(*(values << { max_age: amount }))
|
58
|
+
end
|
59
|
+
|
60
|
+
def headers
|
61
|
+
{ HEADER => time.httpdate }.merge(@cache_control.headers)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def time
|
67
|
+
Time.now + @amount.to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -69,13 +69,14 @@ module Lotus
|
|
69
69
|
_rescue do
|
70
70
|
@_env = env
|
71
71
|
@headers = ::Rack::Utils::HeaderHash.new
|
72
|
-
|
72
|
+
@params = self.class.params_class.new(@_env)
|
73
|
+
super @params
|
73
74
|
end
|
74
75
|
|
75
76
|
finish
|
76
77
|
end
|
77
78
|
|
78
|
-
|
79
|
+
private
|
79
80
|
|
80
81
|
# Prepare the Rack response before the control is returned to the
|
81
82
|
# webserver.
|
@@ -25,6 +25,10 @@ module Lotus
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
# Callbacks API class methods
|
29
|
+
#
|
30
|
+
# @since 0.1.0
|
31
|
+
# @api private
|
28
32
|
module ClassMethods
|
29
33
|
# Override Ruby's hook for modules.
|
30
34
|
# It includes callbacks logic
|
@@ -131,6 +135,10 @@ module Lotus
|
|
131
135
|
end
|
132
136
|
end
|
133
137
|
|
138
|
+
# Callbacks API instance methods
|
139
|
+
#
|
140
|
+
# @since 0.1.0
|
141
|
+
# @api private
|
134
142
|
module InstanceMethods
|
135
143
|
# Implements the Rack/Lotus::Action protocol
|
136
144
|
#
|
data/lib/lotus/action/cookies.rb
CHANGED
@@ -10,20 +10,8 @@ module Lotus
|
|
10
10
|
#
|
11
11
|
# @see Lotus::Action::Cookies#cookies
|
12
12
|
module Cookies
|
13
|
-
|
14
13
|
protected
|
15
14
|
|
16
|
-
# Finalize the response by flushing cookies into the response
|
17
|
-
#
|
18
|
-
# @since 0.1.0
|
19
|
-
# @api private
|
20
|
-
#
|
21
|
-
# @see Lotus::Action#finish
|
22
|
-
def finish
|
23
|
-
super
|
24
|
-
cookies.finish
|
25
|
-
end
|
26
|
-
|
27
15
|
# Gets the cookies from the request and expose them as an Hash
|
28
16
|
#
|
29
17
|
# @return [Lotus::Action::CookieJar] the cookies
|
@@ -55,6 +43,19 @@ module Lotus
|
|
55
43
|
def cookies
|
56
44
|
@cookies ||= CookieJar.new(@_env.dup, headers)
|
57
45
|
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Finalize the response by flushing cookies into the response
|
50
|
+
#
|
51
|
+
# @since 0.1.0
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
# @see Lotus::Action#finish
|
55
|
+
def finish
|
56
|
+
super
|
57
|
+
cookies.finish
|
58
|
+
end
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -19,6 +19,10 @@ module Lotus
|
|
19
19
|
base.extend ClassMethods
|
20
20
|
end
|
21
21
|
|
22
|
+
# Exposures API class methods
|
23
|
+
#
|
24
|
+
# @since 0.1.0
|
25
|
+
# @api private
|
22
26
|
module ClassMethods
|
23
27
|
# Expose the given attributes on the outside of the object with
|
24
28
|
# a getter and a special method called #exposures.
|
@@ -53,7 +57,10 @@ module Lotus
|
|
53
57
|
# action.exposures # => { :article => #<Article ...>, :tags => [ ... ] }
|
54
58
|
def expose(*names)
|
55
59
|
class_eval do
|
56
|
-
|
60
|
+
names.each do |name|
|
61
|
+
attr_reader(name) unless attr_reader?(name)
|
62
|
+
end
|
63
|
+
|
57
64
|
exposures.push(*names)
|
58
65
|
end
|
59
66
|
end
|
@@ -67,6 +74,15 @@ module Lotus
|
|
67
74
|
def exposures
|
68
75
|
@exposures ||= []
|
69
76
|
end
|
77
|
+
|
78
|
+
private
|
79
|
+
# Check if the attr_reader is already defined
|
80
|
+
#
|
81
|
+
# @since 0.3.0
|
82
|
+
# @api private
|
83
|
+
def attr_reader?(name)
|
84
|
+
(instance_methods | private_instance_methods).include?(name)
|
85
|
+
end
|
70
86
|
end
|
71
87
|
|
72
88
|
# Set of exposures
|
@@ -77,12 +93,23 @@ module Lotus
|
|
77
93
|
#
|
78
94
|
# @see Lotus::Action::Exposable::ClassMethods.expose
|
79
95
|
def exposures
|
80
|
-
{}.tap do |result|
|
81
|
-
self.class.exposures.each do |
|
82
|
-
result[
|
96
|
+
@exposures ||= {}.tap do |result|
|
97
|
+
self.class.exposures.each do |name|
|
98
|
+
result[name] = send(name)
|
83
99
|
end
|
84
100
|
end
|
85
101
|
end
|
102
|
+
|
103
|
+
# Finalize the response
|
104
|
+
#
|
105
|
+
# @since 0.3.0
|
106
|
+
# @api private
|
107
|
+
#
|
108
|
+
# @see Lotus::Action#finish
|
109
|
+
def finish
|
110
|
+
super
|
111
|
+
exposures
|
112
|
+
end
|
86
113
|
end
|
87
114
|
end
|
88
115
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Action
|
3
|
+
# Container useful to transport data with the HTTP session
|
4
|
+
# It has a life span of one HTTP request or redirect.
|
5
|
+
#
|
6
|
+
# @since 0.3.0
|
7
|
+
# @api private
|
8
|
+
class Flash
|
9
|
+
# Session key where the data is stored
|
10
|
+
#
|
11
|
+
# @since 0.3.0
|
12
|
+
# @api private
|
13
|
+
SESSION_KEY = :__flash
|
14
|
+
|
15
|
+
# Initialize a new Flash instance
|
16
|
+
#
|
17
|
+
# @param session [Rack::Session::Abstract::SessionHash] the session
|
18
|
+
# @param request_id [String] the HTTP Request ID
|
19
|
+
#
|
20
|
+
# @return [Lotus::Action::Flash] the flash
|
21
|
+
#
|
22
|
+
# @see http://www.rubydoc.info/gems/rack/Rack/Session/Abstract/SessionHash
|
23
|
+
# @see Lotus::Action::Rack#session_id
|
24
|
+
def initialize(session, request_id)
|
25
|
+
@session = session
|
26
|
+
@request_id = request_id
|
27
|
+
|
28
|
+
session[SESSION_KEY] ||= {}
|
29
|
+
session[SESSION_KEY][request_id] ||= {}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set the given value for the given key
|
33
|
+
#
|
34
|
+
# @param key [#to_s] the key
|
35
|
+
# @param value [Object] the value
|
36
|
+
#
|
37
|
+
# @since 0.3.0
|
38
|
+
# @api private
|
39
|
+
def []=(key, value)
|
40
|
+
data[key] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get the value associated to the given key, if any
|
44
|
+
#
|
45
|
+
# @return [Object,NilClass] the value
|
46
|
+
#
|
47
|
+
# @since 0.3.0
|
48
|
+
# @api private
|
49
|
+
def [](key)
|
50
|
+
data.fetch(key) do
|
51
|
+
_values.find {|data| !data[key].nil? }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes entirely the flash from the session if it has stale contents
|
56
|
+
# or if empty.
|
57
|
+
#
|
58
|
+
# @return [void]
|
59
|
+
#
|
60
|
+
# @since 0.3.0
|
61
|
+
# @api private
|
62
|
+
def clear
|
63
|
+
# FIXME we're just before a release and I can't find a proper way to reproduce
|
64
|
+
# this bug that I've found via a browser.
|
65
|
+
#
|
66
|
+
# It may happen that `#flash` is nil, and those two methods will fail
|
67
|
+
unless flash.nil?
|
68
|
+
expire_stale!
|
69
|
+
remove!
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Check if there are contents stored in the flash from the current or the
|
74
|
+
# previous request.
|
75
|
+
#
|
76
|
+
# @return [TrueClass,FalseClass] the result of the check
|
77
|
+
#
|
78
|
+
# @since 0.3.0
|
79
|
+
# @api private
|
80
|
+
def empty?
|
81
|
+
_values.all?(&:empty?)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
# The flash registry that holds the data for **all** the recent requests
|
87
|
+
#
|
88
|
+
# @return [Hash] the flash
|
89
|
+
#
|
90
|
+
# @since 0.3.0
|
91
|
+
# @api private
|
92
|
+
def flash
|
93
|
+
@session[SESSION_KEY]
|
94
|
+
end
|
95
|
+
|
96
|
+
# The flash registry that holds the data **only for** the current request
|
97
|
+
#
|
98
|
+
# @return [Hash] the flash for the current request
|
99
|
+
#
|
100
|
+
# @since 0.3.0
|
101
|
+
# @api private
|
102
|
+
def data
|
103
|
+
flash[@request_id]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Expire the stale data from the previous request.
|
107
|
+
#
|
108
|
+
# @return [void]
|
109
|
+
#
|
110
|
+
# @since 0.3.0
|
111
|
+
# @api private
|
112
|
+
def expire_stale!
|
113
|
+
flash.each do |request_id, _|
|
114
|
+
flash.delete(request_id) if @request_id != request_id
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Remove the flash entirely from the session if empty.
|
119
|
+
#
|
120
|
+
# @return [void]
|
121
|
+
#
|
122
|
+
# @since 0.3.0
|
123
|
+
# @api private
|
124
|
+
#
|
125
|
+
# @see Lotus::Action::Flash#empty?
|
126
|
+
def remove!
|
127
|
+
@session.delete(SESSION_KEY) if empty?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Values from all the stored requests
|
131
|
+
#
|
132
|
+
# @return [Array]
|
133
|
+
#
|
134
|
+
# @since 0.3.0
|
135
|
+
# @api private
|
136
|
+
def _values
|
137
|
+
flash.values
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Action
|
3
|
+
# Glue code for full stack Lotus applications
|
4
|
+
#
|
5
|
+
# @api private
|
6
|
+
# @since 0.3.0
|
7
|
+
module Glue
|
8
|
+
# Rack environment key that indicates where the action instance is passed
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
# @since 0.3.0
|
12
|
+
ENV_KEY = 'lotus.action'.freeze
|
13
|
+
|
14
|
+
# Override Ruby's Module#included
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
# @since 0.3.0
|
18
|
+
def self.included(base)
|
19
|
+
base.class_eval { expose :format }
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
# Put the current instance into the Rack environment
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
# @since 0.3.0
|
27
|
+
#
|
28
|
+
# @see Lotus::Action#finish
|
29
|
+
def finish
|
30
|
+
super
|
31
|
+
@_env[ENV_KEY] = self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/lotus/action/mime.rb
CHANGED
@@ -11,23 +11,33 @@ module Lotus
|
|
11
11
|
# The key that returns accepted mime types from the Rack env
|
12
12
|
#
|
13
13
|
# @since 0.1.0
|
14
|
+
# @api private
|
14
15
|
HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze
|
15
16
|
|
16
17
|
# The header key to set the mime type of the response
|
17
18
|
#
|
18
19
|
# @since 0.1.0
|
20
|
+
# @api private
|
19
21
|
CONTENT_TYPE = 'Content-Type'.freeze
|
20
22
|
|
21
23
|
# The default mime type for an incoming HTTP request
|
22
24
|
#
|
23
25
|
# @since 0.1.0
|
26
|
+
# @api private
|
24
27
|
DEFAULT_ACCEPT = '*/*'.freeze
|
25
28
|
|
26
29
|
# The default mime type that is returned in the response
|
27
30
|
#
|
28
31
|
# @since 0.1.0
|
32
|
+
# @api private
|
29
33
|
DEFAULT_CONTENT_TYPE = 'application/octet-stream'.freeze
|
30
34
|
|
35
|
+
# The default charset that is returned in the response
|
36
|
+
#
|
37
|
+
# @since 0.3.0
|
38
|
+
# @api private
|
39
|
+
DEFAULT_CHARSET = 'utf-8'.freeze
|
40
|
+
|
31
41
|
# Override Ruby's hook for modules.
|
32
42
|
# It includes Mime types logic
|
33
43
|
#
|
@@ -50,11 +60,11 @@ module Lotus
|
|
50
60
|
raise Lotus::Controller::UnknownFormatError.new(format)
|
51
61
|
end
|
52
62
|
|
53
|
-
|
63
|
+
private
|
54
64
|
|
55
65
|
# Restrict the access to the specified mime type symbols.
|
56
66
|
#
|
57
|
-
# @param
|
67
|
+
# @param formats[Array<Symbol>] one or more symbols representing mime type(s)
|
58
68
|
#
|
59
69
|
# @raise [Lotus::Controller::UnknownFormatError] if the symbol cannot
|
60
70
|
# be converted into a mime type
|
@@ -176,7 +186,62 @@ module Lotus
|
|
176
186
|
@content_type || accepts || default_content_type || DEFAULT_CONTENT_TYPE
|
177
187
|
end
|
178
188
|
|
179
|
-
|
189
|
+
# Action charset setter, receives new charset value
|
190
|
+
#
|
191
|
+
# @return [String] the charset of the request.
|
192
|
+
#
|
193
|
+
# @since 0.3.0
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
# require 'lotus/controller'
|
197
|
+
#
|
198
|
+
# class Show
|
199
|
+
# include Lotus::Action
|
200
|
+
#
|
201
|
+
# def call(params)
|
202
|
+
# # ...
|
203
|
+
# self.charset = 'koi8-r'
|
204
|
+
# end
|
205
|
+
# end
|
206
|
+
def charset=(value)
|
207
|
+
@charset = value
|
208
|
+
end
|
209
|
+
|
210
|
+
# The charset that will be automatically set in the response.
|
211
|
+
#
|
212
|
+
# It prefers, in order:
|
213
|
+
# * Explicit set value (see #charset=)
|
214
|
+
# * Default configuration charset
|
215
|
+
# * Default content type
|
216
|
+
#
|
217
|
+
# To override the value, use <tt>#charset=</tt>
|
218
|
+
#
|
219
|
+
# @return [String] the charset of the request.
|
220
|
+
#
|
221
|
+
# @since 0.3.0
|
222
|
+
#
|
223
|
+
# @see Lotus::Action::Mime#charset=
|
224
|
+
# @see Lotus::Configuration#default_charset
|
225
|
+
# @see Lotus::Action::Mime#default_charset
|
226
|
+
# @see Lotus::Action::Mime#DEFAULT_CHARSET
|
227
|
+
#
|
228
|
+
# @example
|
229
|
+
# require 'lotus/controller'
|
230
|
+
#
|
231
|
+
# class Show
|
232
|
+
# include Lotus::Action
|
233
|
+
#
|
234
|
+
# def call(params)
|
235
|
+
# # ...
|
236
|
+
# charset # => 'text/html'
|
237
|
+
# end
|
238
|
+
# end
|
239
|
+
def charset
|
240
|
+
@charset || default_charset || DEFAULT_CHARSET
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
180
245
|
# Finalize the response by setting the current content type
|
181
246
|
#
|
182
247
|
# @since 0.1.0
|
@@ -185,7 +250,7 @@ module Lotus
|
|
185
250
|
# @see Lotus::Action#finish
|
186
251
|
def finish
|
187
252
|
super
|
188
|
-
headers
|
253
|
+
headers[CONTENT_TYPE] = content_type_with_charset
|
189
254
|
end
|
190
255
|
|
191
256
|
# Sets the given format and corresponding content type.
|
@@ -368,6 +433,19 @@ module Lotus
|
|
368
433
|
configuration.format_for(content_type) ||
|
369
434
|
::Rack::Mime::MIME_TYPES.key(content_type).gsub(/\A\./, '').to_sym
|
370
435
|
end
|
436
|
+
|
437
|
+
# @since 0.3.0
|
438
|
+
# @api private
|
439
|
+
def default_charset
|
440
|
+
configuration.default_charset
|
441
|
+
end
|
442
|
+
|
443
|
+
# @since 0.3.0
|
444
|
+
# @api private
|
445
|
+
def content_type_with_charset
|
446
|
+
"#{content_type}; charset=#{charset}"
|
447
|
+
end
|
448
|
+
|
371
449
|
end
|
372
450
|
end
|
373
451
|
end
|