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