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.
@@ -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
- super Params.new(@_env)
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
- protected
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
  #
@@ -36,10 +36,11 @@ module Lotus
36
36
  self.configuration = config
37
37
  end
38
38
 
39
- config.load!(base)
39
+ config.copy!(base)
40
40
  end
41
41
 
42
- protected
42
+ private
43
+
43
44
  def configuration
44
45
  self.class.configuration
45
46
  end
@@ -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
- attr_reader( *names)
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 |exposure|
82
- result[exposure] = send(exposure)
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
@@ -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
- protected
63
+ private
54
64
 
55
65
  # Restrict the access to the specified mime type symbols.
56
66
  #
57
- # @param mime_types[Array<Symbol>] one or more symbols representing mime type(s)
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
- protected
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.merge! CONTENT_TYPE => content_type
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