merb 0.5.2 → 0.5.3
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.
- data/SVN_REVISION +1 -1
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
- data/app_generators/merb/templates/script/destroy +1 -0
- data/app_generators/merb/templates/script/generate +4 -0
- data/lib/merb.rb +1 -0
- data/lib/merb/abstract_controller.rb +168 -55
- data/lib/merb/assets.rb +57 -16
- data/lib/merb/assets.rb.orig +119 -0
- data/lib/merb/boot_loader.rb +55 -4
- data/lib/merb/boot_loader.rb.orig +235 -0
- data/lib/merb/controller.rb +1 -1
- data/lib/merb/cookies.rb +95 -0
- data/lib/merb/mixins/controller.rb +1 -1
- data/lib/merb/mixins/render.rb +17 -4
- data/lib/merb/server.rb +23 -6
- data/lib/merb/test/helper.rb +6 -2
- data/lib/merb/version.rb +7 -4
- data/lib/tasks/merb.rake +1 -1
- data/spec/fixtures/controllers/render_spec_controllers.rb +10 -0
- data/spec/merb/controller_filters_spec.rb +1 -1
- data/spec/merb/cookie_store_spec.rb +1 -1
- data/spec/merb/cookies_spec.rb +96 -0
- data/spec/merb/render_spec.rb +17 -0
- metadata +7 -3
@@ -0,0 +1,119 @@
|
|
1
|
+
module Merb
|
2
|
+
module Assets
|
3
|
+
|
4
|
+
# Returns true if assets should be bundled (e.g., production mode or
|
5
|
+
# :bundle_assets is explicitly enabled), false if not.
|
6
|
+
def self.bundle?
|
7
|
+
(Merb::Config[:environment].to_s == 'production') ||
|
8
|
+
(!!Merb::Config[:bundle_assets])
|
9
|
+
end
|
10
|
+
|
11
|
+
# Helpers for handling asset files.
|
12
|
+
module AssetHelpers
|
13
|
+
# :nodoc:
|
14
|
+
ASSET_FILE_EXTENSIONS = {
|
15
|
+
:javascript => ".js",
|
16
|
+
:stylesheet => ".css"
|
17
|
+
}
|
18
|
+
|
19
|
+
# Returns the URI path to a particular asset file. If +local_path+ is
|
20
|
+
# true, returns the path relative to the Merb.root, not the public
|
21
|
+
# directory. Uses the path_prefix, if any is configured.
|
22
|
+
#
|
23
|
+
# asset_path(:javascript, :dingo) #=> "/javascripts/dingo.js"
|
24
|
+
# asset_path(:javascript, :dingo, true) #=> "public/javascripts/dingo.js"
|
25
|
+
def asset_path(asset_type, filename, local_path = false)
|
26
|
+
filename = filename.to_s
|
27
|
+
if filename !~ /#{'\\' + ASSET_FILE_EXTENSIONS[asset_type]}\Z/
|
28
|
+
filename << ASSET_FILE_EXTENSIONS[asset_type]
|
29
|
+
end
|
30
|
+
filename = "/#{asset_type}s/#{filename}"
|
31
|
+
if local_path
|
32
|
+
return "public#{filename}"
|
33
|
+
else
|
34
|
+
return "#{Merb::Config[:path_prefix]}#{filename}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# An abstract class for bundling text assets into single files.
|
40
|
+
class AbstractAssetBundler
|
41
|
+
class << self
|
42
|
+
|
43
|
+
# Add a post-bundle callback, for example to run a Javascript or CSS
|
44
|
+
# compressor on the bundled file:
|
45
|
+
#
|
46
|
+
# Merb::Assets::JavascriptAssetBundler.add_callback do |filename|
|
47
|
+
# `yuicompressor #{filename}`
|
48
|
+
# end
|
49
|
+
def add_callback(&block)
|
50
|
+
callbacks << block
|
51
|
+
end
|
52
|
+
alias_method :after_bundling, :add_callback
|
53
|
+
|
54
|
+
# An array of any existing callbacks.
|
55
|
+
def callbacks
|
56
|
+
@callbacks ||= []
|
57
|
+
return @callbacks
|
58
|
+
end
|
59
|
+
|
60
|
+
# The type of asset for which the bundler is responsible.
|
61
|
+
def asset_type
|
62
|
+
raise NotImplementedError, "should return a symbol for the first argument to be passed to asset_path"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Create a new asset bundler, which will produce a bundled file containing
|
67
|
+
# the contents of +files+. If +name+ is +true+ (as in, is an instance of
|
68
|
+
# +TrueClass+), the filename is written out as "all", otherwise +name+
|
69
|
+
# is coerced into a string.
|
70
|
+
def initialize(name, *files)
|
71
|
+
@bundle_name = name == true ? :all : name
|
72
|
+
@bundle_filename = asset_path(self.class.asset_type, @bundle_name, true)
|
73
|
+
@files = files.map { |f| asset_path(self.class.asset_type, f, true) }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creates the new bundled file, executes all the callbacks, and returns
|
77
|
+
# the name of the bundled file.
|
78
|
+
def bundle!
|
79
|
+
# TODO: Move this file check out into an in-memory cache. Also, push it out to the helper level so we don't have to create the helper object.
|
80
|
+
unless File.exist?(@bundle_filename)
|
81
|
+
bundle_files(@bundle_filename, *@files)
|
82
|
+
self.class.callbacks.each { |c| c.call(@bundle_filename) }
|
83
|
+
end
|
84
|
+
return @bundle_name
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
include Merb::Assets::AssetHelpers # for asset_path
|
90
|
+
|
91
|
+
# Bundle all the filenames in +files+ into +filename+.
|
92
|
+
def bundle_files(filename, *files)
|
93
|
+
File.open(filename, "w") do |f|
|
94
|
+
files.each { |file| f.puts(File.read(file)) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# Bundles javascripts into a single file:
|
101
|
+
#
|
102
|
+
# javascripts/#{name}.js
|
103
|
+
class JavascriptAssetBundler < AbstractAssetBundler
|
104
|
+
def self.asset_type
|
105
|
+
:javascript
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Bundles stylesheets into a single file:
|
110
|
+
#
|
111
|
+
# stylesheets/#{name}.css
|
112
|
+
class StylesheetAssetBundler < AbstractAssetBundler
|
113
|
+
def self.asset_type
|
114
|
+
:stylesheet
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/merb/boot_loader.rb
CHANGED
@@ -138,12 +138,28 @@ module Merb
|
|
138
138
|
end
|
139
139
|
|
140
140
|
def load_application
|
141
|
-
|
142
|
-
|
141
|
+
|
142
|
+
#Magic Class Loading => Not failing on missing parent due to alphabetical loading
|
143
|
+
# Does a reverse alphaebetical search if classes failed in first pass
|
144
|
+
# Will continue to reverse the list of failures until the size does change between
|
145
|
+
# two passes
|
146
|
+
|
147
|
+
orphaned_paths = []
|
148
|
+
$LOAD_PATH.unshift( File.join(Merb.root,'/app/models') )
|
149
|
+
$LOAD_PATH.unshift( File.join(Merb.root,'/app/controllers') )
|
143
150
|
$LOAD_PATH.unshift( File.join(Merb.root , '/lib') )
|
144
151
|
Merb.load_paths.each do |glob|
|
145
|
-
Dir[Merb.root + glob].each
|
152
|
+
Dir[Merb.root + glob].each do |m|
|
153
|
+
begin
|
154
|
+
require m
|
155
|
+
rescue NameError
|
156
|
+
orphaned_paths.unshift(m)
|
157
|
+
end
|
158
|
+
end
|
146
159
|
end
|
160
|
+
|
161
|
+
load_classes_with_requirements(orphaned_paths)
|
162
|
+
|
147
163
|
load_action_arguments
|
148
164
|
load_controller_template_path_cache
|
149
165
|
load_inline_helpers
|
@@ -152,6 +168,41 @@ module Merb
|
|
152
168
|
(@after_app_blocks || []).each { |b| b.call }
|
153
169
|
end
|
154
170
|
|
171
|
+
|
172
|
+
def load_classes_with_requirements(orphaned_paths)
|
173
|
+
|
174
|
+
#Make the list unique
|
175
|
+
orphaned_paths.uniq!
|
176
|
+
|
177
|
+
while orphaned_paths.size > 0
|
178
|
+
#Take the size for comparison later
|
179
|
+
size_at_start = orphaned_paths.size
|
180
|
+
|
181
|
+
fail_list = [] #List of failures
|
182
|
+
|
183
|
+
# Try to load each path again, this time the order is reversed
|
184
|
+
(orphaned_paths).each do |m|
|
185
|
+
# Remove the path from the list
|
186
|
+
orphaned_paths.delete(m)
|
187
|
+
begin
|
188
|
+
require m
|
189
|
+
rescue NameError
|
190
|
+
# Add it back on if it failed to load due to NameError
|
191
|
+
fail_list.push(m)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
orphaned_paths.concat(fail_list)
|
196
|
+
|
197
|
+
# Stop processing if everything loaded (size == 0) or if the size didn't change
|
198
|
+
# (ie something couldn't be loaded)
|
199
|
+
break if(orphaned_paths.size == size_at_start || orphaned_paths.size == 0)
|
200
|
+
end
|
201
|
+
|
202
|
+
return orphaned_paths
|
203
|
+
end
|
204
|
+
|
205
|
+
|
155
206
|
def load_libraries
|
156
207
|
# Load the Sass plugin of /public/stylesheets/sass exists
|
157
208
|
begin
|
@@ -232,4 +283,4 @@ module Merb
|
|
232
283
|
|
233
284
|
end # class << self
|
234
285
|
end # BootLoader
|
235
|
-
end # Merb
|
286
|
+
end # Merb
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module Merb
|
2
|
+
module GlobalHelper;end
|
3
|
+
|
4
|
+
module BootLoader
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def initialize_merb
|
8
|
+
require 'merb'
|
9
|
+
@mtime = Time.now if Merb::Config[:reloader] == true
|
10
|
+
# Register session types before merb_init.rb so that any additional
|
11
|
+
# session stores will be added to the end of the list and become the
|
12
|
+
# default.
|
13
|
+
register_session_type('memory',
|
14
|
+
Merb.framework_root / "merb" / "session" / "memory_session",
|
15
|
+
"Using in-memory sessions; sessions will be lost whenever the server stops.")
|
16
|
+
register_session_type('mem_cache',
|
17
|
+
Merb.framework_root / "merb" / "session" / "mem_cache_session",
|
18
|
+
"Using MemCache distributed memory sessions")
|
19
|
+
register_session_type('cookie', # Last session type becomes the default
|
20
|
+
Merb.framework_root / "merb" / "session" / "cookie_store",
|
21
|
+
"Using 'share-nothing' cookie sessions (4kb limit per client)")
|
22
|
+
require Merb.root / 'config/merb_init.rb'
|
23
|
+
add_controller_mixins
|
24
|
+
end
|
25
|
+
|
26
|
+
def max_mtime( files = [] )
|
27
|
+
files.map{ |file| File.mtime(file) rescue @mtime }.max
|
28
|
+
end
|
29
|
+
|
30
|
+
def register_session_type(name, file, description = nil)
|
31
|
+
@registered_session_types ||= YAML::Omap.new
|
32
|
+
@registered_session_types[name] = {
|
33
|
+
:file => file,
|
34
|
+
:description => (description || "Using #{name} sessions")
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_controller_mixins
|
39
|
+
types = @registered_session_types
|
40
|
+
Merb::Controller.class_eval do
|
41
|
+
lib = File.join(Merb.framework_root, 'merb')
|
42
|
+
session_store = Merb::Config[:session_store].to_s
|
43
|
+
if ["", "false"].include?(session_store)
|
44
|
+
puts "Not Using Sessions"
|
45
|
+
elsif reg = types[session_store]
|
46
|
+
if session_store == "cookie"
|
47
|
+
unless Merb::Config[:session_secret_key] && (Merb::Config[:session_secret_key].length >= 16)
|
48
|
+
puts("You must specify a session_secret_key in your merb.yml, and it must be at least 16 characters\nbailing out...")
|
49
|
+
exit!
|
50
|
+
end
|
51
|
+
Merb::Controller.session_secret_key = Merb::Config[:session_secret_key]
|
52
|
+
end
|
53
|
+
require reg[:file]
|
54
|
+
include ::Merb::SessionMixin
|
55
|
+
puts reg[:description]
|
56
|
+
else
|
57
|
+
puts "Session store not found, '#{Merb::Config[:session_store]}'."
|
58
|
+
puts "Defaulting to CookieStore Sessions"
|
59
|
+
unless Merb::Config[:session_secret_key] && (Merb::Config[:session_secret_key].length >= 16)
|
60
|
+
puts("You must specify a session_secret_key in your merb.yml, and it must be at least 16 characters\nbailing out...")
|
61
|
+
exit!
|
62
|
+
end
|
63
|
+
Merb::Controller.session_secret_key = Merb::Config[:session_secret_key]
|
64
|
+
require types['cookie'][:file]
|
65
|
+
include ::Merb::SessionMixin
|
66
|
+
puts "(plugin not installed?)"
|
67
|
+
end
|
68
|
+
|
69
|
+
if Merb::Config[:basic_auth]
|
70
|
+
require lib + "/mixins/basic_authentication"
|
71
|
+
include ::Merb::AuthenticationMixin
|
72
|
+
puts "Basic Authentication mixed in"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def after_app_loads(&block)
|
78
|
+
@after_app_blocks ||= []
|
79
|
+
@after_app_blocks << block
|
80
|
+
end
|
81
|
+
|
82
|
+
def app_loaded?
|
83
|
+
@app_loaded
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_action_arguments(klasses = Merb::Controller._subclasses)
|
87
|
+
begin
|
88
|
+
klasses.each do |controller|
|
89
|
+
controller = Object.full_const_get(controller)
|
90
|
+
controller.action_argument_list = {}
|
91
|
+
controller.callable_actions.each do |action, bool|
|
92
|
+
controller.action_argument_list[action.to_sym] = ParseTreeArray.translate(controller, action).get_args
|
93
|
+
end
|
94
|
+
end
|
95
|
+
rescue
|
96
|
+
klasses.each { |controller| Object.full_const_get(controller).action_argument_list = {} }
|
97
|
+
end if defined?(ParseTreeArray)
|
98
|
+
end
|
99
|
+
|
100
|
+
def template_paths(type = "*[a-zA-Z]")
|
101
|
+
# This gets all templates set in the controllers template roots
|
102
|
+
template_paths = Merb::AbstractController._abstract_subclasses.map do |klass|
|
103
|
+
Object.full_const_get(klass)._template_root
|
104
|
+
end.uniq.map do |path|
|
105
|
+
Dir["#{path}/**/#{type}"]
|
106
|
+
end
|
107
|
+
|
108
|
+
# This gets the templates that might be created outside controllers
|
109
|
+
# template roots. eg app/views/shared/*
|
110
|
+
template_paths << Dir["#{Merb.root}/app/views/**/*[a-zA-Z]"] if type == "*"
|
111
|
+
|
112
|
+
template_paths.flatten.compact.uniq || []
|
113
|
+
end
|
114
|
+
|
115
|
+
def load_controller_template_path_cache
|
116
|
+
Merb::AbstractController.reset_template_path_cache!
|
117
|
+
|
118
|
+
template_paths.each do |template|
|
119
|
+
Merb::AbstractController.add_path_to_template_cache(template)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def load_inline_helpers
|
124
|
+
partials = template_paths("_*.{erb,haml}")
|
125
|
+
|
126
|
+
partials.each do |partial|
|
127
|
+
case partial
|
128
|
+
when /erb$/
|
129
|
+
template = Erubis::Eruby.new(File.read(partial))
|
130
|
+
template.def_method(Merb::GlobalHelper, partial.gsub(/[^\.a-zA-Z0-9]/, "__").gsub(/\./, "_"), partial)
|
131
|
+
when /haml$/
|
132
|
+
if Object.const_defined?(:Haml) and Haml::Engine.instance_methods.include?('def_method')
|
133
|
+
template = Haml::Engine.new(File.read(partial), :filename => partial)
|
134
|
+
template.def_method(Merb::GlobalHelper, partial.gsub(/[^\.a-zA-Z0-9]/, "__").gsub(/\./, "_"))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def load_application
|
141
|
+
$LOAD_PATH.unshift( File.join(Merb.root , '/app/models') )
|
142
|
+
$LOAD_PATH.unshift( File.join(Merb.root , '/app/controllers') )
|
143
|
+
$LOAD_PATH.unshift( File.join(Merb.root , '/lib') )
|
144
|
+
Merb.load_paths.each do |glob|
|
145
|
+
Dir[Merb.root + glob].each { |m| require m }
|
146
|
+
end
|
147
|
+
load_action_arguments
|
148
|
+
load_controller_template_path_cache
|
149
|
+
load_inline_helpers
|
150
|
+
@app_loaded = true
|
151
|
+
load_libraries
|
152
|
+
(@after_app_blocks || []).each { |b| b.call }
|
153
|
+
end
|
154
|
+
|
155
|
+
def load_libraries
|
156
|
+
# Load the Sass plugin of /public/stylesheets/sass exists
|
157
|
+
begin
|
158
|
+
require "sass/plugin" if File.directory?(Merb.root / "public" / "stylesheets" / "sass")
|
159
|
+
rescue LoadError
|
160
|
+
end
|
161
|
+
# If you don't use the JSON gem, disable auto-parsing of json params too
|
162
|
+
if Merb::Config[:disable_json_gem]
|
163
|
+
Merb::Request::parse_json_params = false
|
164
|
+
else
|
165
|
+
begin
|
166
|
+
require 'json/ext'
|
167
|
+
rescue LoadError
|
168
|
+
require 'json/pure'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def remove_constant(const)
|
174
|
+
parts = const.to_s.split("::")
|
175
|
+
base = parts.size == 1 ? Object : Object.full_const_get(parts[0..-2].join("::"))
|
176
|
+
object = parts[-1].intern
|
177
|
+
Merb.logger.info("Removing constant #{object} from #{base}")
|
178
|
+
base.send(:remove_const, object) if object
|
179
|
+
Merb::Controller._subclasses.delete(const)
|
180
|
+
end
|
181
|
+
|
182
|
+
def reload
|
183
|
+
return if !Merb::Config[:reloader]
|
184
|
+
|
185
|
+
# First we collect all files in the project (this will also grab newly added files)
|
186
|
+
project_files = Merb.load_paths.map { |path| Dir[Merb.root + path] }.flatten.uniq
|
187
|
+
partials = template_paths("_*.*").map { |path| Dir[path] }.flatten.uniq
|
188
|
+
project_mtime = max_mtime(project_files + partials) # Latest changed time of all project files
|
189
|
+
|
190
|
+
return if @mtime.nil? || @mtime >= project_mtime # Only continue if a file has changed
|
191
|
+
|
192
|
+
project_files.each do |file|
|
193
|
+
if File.mtime(file) >= @mtime
|
194
|
+
# If the file has changed or been added since the last project reload time
|
195
|
+
# remove any cannonical constants, based on what type of project file it is
|
196
|
+
# and then reload the file
|
197
|
+
begin
|
198
|
+
constant = case file
|
199
|
+
when %r[/app/(models|controllers|parts|mailers)/(.+)\.rb$]
|
200
|
+
$2.to_const_string
|
201
|
+
when %r[/app/(helpers)/(.+)\.rb$]
|
202
|
+
"Merb::" + $2.to_const_string
|
203
|
+
end
|
204
|
+
remove_constant(constant)
|
205
|
+
rescue NameError => e
|
206
|
+
Merb.logger.warn "Couldn't remove constant #{constant}"
|
207
|
+
end
|
208
|
+
|
209
|
+
begin
|
210
|
+
Merb.logger.info("Reloading file #{file}")
|
211
|
+
old_subclasses = Merb::Controller._subclasses.dup
|
212
|
+
load(file)
|
213
|
+
loaded_classes = Merb::Controller._subclasses - old_subclasses
|
214
|
+
load_action_arguments(loaded_classes)
|
215
|
+
rescue Exception => e
|
216
|
+
puts "Error reloading file #{file}: #{e}"
|
217
|
+
Merb.logger.warn " Error: #{e}"
|
218
|
+
end
|
219
|
+
|
220
|
+
# constant = file =~ /\/(controllers|models|mailers|helpers|parts)\/(.*).rb/ ? $2.to_const_string : nil
|
221
|
+
# remove_constant($2.to_const_string, ($1 == "helpers") ? Merb : nil)
|
222
|
+
# load file and puts "loaded file: #{file}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Rebuild the glob cache and erubis inline helpers
|
227
|
+
load_controller_template_path_cache
|
228
|
+
load_inline_helpers
|
229
|
+
|
230
|
+
@mtime = project_mtime # As the last action, update the current @mtime
|
231
|
+
end
|
232
|
+
|
233
|
+
end # class << self
|
234
|
+
end # BootLoader
|
235
|
+
end # Merb
|
data/lib/merb/controller.rb
CHANGED
data/lib/merb/cookies.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
# Cookies are read and written through Merb::Controller#cookies. The cookies
|
4
|
+
# you read are those received in request along with those that have been set
|
5
|
+
# during the current request. The cookies you write will be sent out with the
|
6
|
+
# response. Cookies are read by value (so you won't get the cookie object
|
7
|
+
# itself back -- just the value it holds).
|
8
|
+
#
|
9
|
+
# == Writing
|
10
|
+
#
|
11
|
+
# cookies[:user] = "dave" # => Sets a simple session cookie
|
12
|
+
# cookies[:token] = { :value => user.token, :expires => Time.now + 2.weeks }
|
13
|
+
# # => Will set a cookie that expires in 2 weeks
|
14
|
+
#
|
15
|
+
# == Reading
|
16
|
+
#
|
17
|
+
# cookies[:user] # => "dave"
|
18
|
+
# cookies.size # => 2 (the number of cookies)
|
19
|
+
#
|
20
|
+
# == Deleting
|
21
|
+
#
|
22
|
+
# cookies.delete(:user)
|
23
|
+
#
|
24
|
+
# == Options
|
25
|
+
#
|
26
|
+
# * +value+ - the cookie's value
|
27
|
+
# * +path+ - the path for which this cookie applies. Defaults to the root
|
28
|
+
# of the application.
|
29
|
+
# * +expires+ - the time at which this cookie expires, as a +Time+ object.
|
30
|
+
class Cookies
|
31
|
+
|
32
|
+
def initialize(request_cookies, headers)
|
33
|
+
@_cookies = request_cookies
|
34
|
+
@_headers = headers
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the value of the cookie by +name+ or nil if no such cookie
|
38
|
+
# exists. You set new cookies using cookies[]=
|
39
|
+
def [](name)
|
40
|
+
@_cookies[name]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sets the value of a cookie. You can set the value directly or pass a hash
|
44
|
+
# with options.
|
45
|
+
#
|
46
|
+
# == Example
|
47
|
+
#
|
48
|
+
# cookies[:user] = "dave" # => Sets a simple session cookie
|
49
|
+
# cookies[:token] = { :value => user.token, :expires => Time.now + 2.weeks }
|
50
|
+
# # => Will set a cookie that expires in 2 weeks
|
51
|
+
#
|
52
|
+
# == Options
|
53
|
+
#
|
54
|
+
# * +value+ - the cookie's value or list of values (as an array).
|
55
|
+
# * +path+ - the path for which this cookie applies. Defaults to the root
|
56
|
+
# '/' of the application.
|
57
|
+
# * +expires+ - the time at which this cookie expires, as a +Time+ object.
|
58
|
+
def []=(name, options)
|
59
|
+
value = ''
|
60
|
+
if options.is_a?(Hash)
|
61
|
+
options = Mash.new(options)
|
62
|
+
value = Merb::Request.escape(options.delete(:value))
|
63
|
+
else
|
64
|
+
value = Merb::Request.escape(options)
|
65
|
+
options = Mash.new
|
66
|
+
end
|
67
|
+
@_cookies[name] = value
|
68
|
+
set_cookie(name, value, options)
|
69
|
+
Merb.logger.info("Cookie set: #{name} => #{value} -- #{options.inspect}")
|
70
|
+
return options
|
71
|
+
end
|
72
|
+
|
73
|
+
# Removes the cookie on the client machine by setting the value to an empty string
|
74
|
+
# and setting its expiration date into the past.
|
75
|
+
def delete(name, options = {})
|
76
|
+
cookie = @_cookies.delete(name)
|
77
|
+
options = Mash.new(options)
|
78
|
+
options[:expires] = Time.at(0)
|
79
|
+
set_cookie(name, "", options)
|
80
|
+
Merb.logger.info("Cookie deleted: #{name} => #{cookie.inspect}")
|
81
|
+
return cookie
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def set_cookie(name, value, options)
|
86
|
+
options[:path] = '/' unless options[:path]
|
87
|
+
if expiry = options[:expires]
|
88
|
+
options[:expires] = expiry.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
|
89
|
+
end
|
90
|
+
# options are sorted for testing purposes
|
91
|
+
(@_headers['Set-Cookie'] ||=[]) << "#{name}=#{value}; " +
|
92
|
+
options.map{|k, v| "#{k}=#{v};"}.sort.join(' ')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|