merb-core 1.0.4 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/merb-core/autoload.rb +22 -23
- data/lib/merb-core/controller/abstract_controller.rb +578 -573
- data/lib/merb-core/controller/mixins/controller.rb +1 -1
- data/lib/merb-core/controller/template.rb +23 -21
- data/lib/merb-core/core_ext.rb +1 -0
- data/lib/merb-core/core_ext/class.rb +34 -0
- data/lib/merb-core/dispatch/request_parsers.rb +11 -0
- data/lib/merb-core/rack/middleware/profiler.rb +39 -5
- data/lib/merb-core/version.rb +1 -1
- metadata +4 -3
data/Rakefile
CHANGED
@@ -66,7 +66,7 @@ spec = Gem::Specification.new do |s|
|
|
66
66
|
s.add_dependency "rspec"
|
67
67
|
s.add_dependency "rack"
|
68
68
|
s.add_dependency "mime-types"
|
69
|
-
s.add_dependency "thor", ">= 0.9.
|
69
|
+
s.add_dependency "thor", ">= 0.9.9"
|
70
70
|
# this escalates to "regular" dependencies, comment it out
|
71
71
|
# for now. RubyGems need some love.
|
72
72
|
#s.add_development_dependency "libxml-ruby"
|
data/lib/merb-core/autoload.rb
CHANGED
@@ -1,31 +1,30 @@
|
|
1
|
+
require 'merb-core/core_ext'
|
2
|
+
require "merb-core/controller/exceptions"
|
3
|
+
require "merb-core/controller/mixins/responder"
|
4
|
+
require "merb-core/controller/mixins/render"
|
5
|
+
require "merb-core/controller/mixins/authentication"
|
6
|
+
require "merb-core/controller/mixins/conditional_get"
|
7
|
+
require "merb-core/controller/mixins/controller"
|
8
|
+
require "merb-core/controller/abstract_controller"
|
9
|
+
require "merb-core/controller/template"
|
10
|
+
require "merb-core/controller/merb_controller"
|
11
|
+
require "merb-core/bootloader"
|
12
|
+
require "merb-core/config"
|
13
|
+
require "merb-core/constants"
|
14
|
+
require "merb-core/dispatch/dispatcher"
|
15
|
+
require "merb-core/plugins"
|
16
|
+
require "merb-core/rack"
|
17
|
+
require "merb-core/dispatch/request"
|
18
|
+
require "merb-core/dispatch/request_parsers.rb"
|
19
|
+
require "merb-core/dispatch/router"
|
20
|
+
require "merb-core/dispatch/worker"
|
21
|
+
|
1
22
|
module Merb
|
2
|
-
autoload :
|
3
|
-
autoload :BootLoader, "merb-core/bootloader"
|
4
|
-
autoload :Config, "merb-core/config"
|
5
|
-
autoload :Const, "merb-core/constants"
|
6
|
-
autoload :ConditionalGetMixin, "merb-core/controller/mixins/conditional_get"
|
7
|
-
autoload :ControllerMixin, "merb-core/controller/mixins/controller"
|
8
|
-
autoload :ControllerExceptions, "merb-core/controller/exceptions"
|
9
|
-
autoload :Dispatcher, "merb-core/dispatch/dispatcher"
|
10
|
-
autoload :AuthenticationMixin, "merb-core/controller/mixins/authentication"
|
11
|
-
autoload :BasicAuthenticationMixin, "merb-core/controller/mixins/authentication/basic"
|
12
|
-
autoload :ErubisCaptureMixin, "merb-core/controller/mixins/erubis_capture"
|
13
|
-
autoload :Plugins, "merb-core/plugins"
|
14
|
-
autoload :Rack, "merb-core/rack"
|
15
|
-
autoload :RenderMixin, "merb-core/controller/mixins/render"
|
16
|
-
autoload :Request, "merb-core/dispatch/request"
|
17
|
-
autoload :Parse, "merb-core/dispatch/request_parsers.rb"
|
18
|
-
autoload :ResponderMixin, "merb-core/controller/mixins/responder"
|
19
|
-
autoload :Router, "merb-core/dispatch/router"
|
20
|
-
autoload :Test, "merb-core/test"
|
21
|
-
autoload :Worker, "merb-core/dispatch/worker"
|
23
|
+
autoload :Test, "merb-core/test"
|
22
24
|
end
|
23
25
|
|
24
26
|
# Require this rather than autoloading it so we can be sure the default template
|
25
27
|
# gets registered
|
26
|
-
require 'merb-core/core_ext'
|
27
|
-
require "merb-core/controller/template"
|
28
|
-
require "merb-core/controller/merb_controller"
|
29
28
|
|
30
29
|
module Merb
|
31
30
|
module InlineTemplates; end
|
@@ -92,622 +92,627 @@
|
|
92
92
|
# <code>params[:action]</code> and <code>params[:controller]</code> have been deprecated as of
|
93
93
|
# the 0.9.0 release. They are no longer set during dispatch, and
|
94
94
|
# have been replaced by <code>action_name</code> and <code>controller_name</code> respectively.
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
class_inheritable_accessor :_layout, :_template_root, :template_roots
|
100
|
-
class_inheritable_accessor :_before_filters, :_after_filters
|
101
|
-
class_inheritable_accessor :_before_dispatch_callbacks, :_after_dispatch_callbacks
|
102
|
-
|
103
|
-
cattr_accessor :_abstract_subclasses
|
104
|
-
|
105
|
-
# :api: plugin
|
106
|
-
attr_accessor :body, :action_name, :_benchmarks
|
107
|
-
# :api: private
|
108
|
-
attr_accessor :_thrown_content
|
109
|
-
|
110
|
-
# Stub so content-type support in RenderMixin doesn't throw errors
|
111
|
-
# :api: private
|
112
|
-
attr_accessor :content_type
|
113
|
-
|
114
|
-
FILTER_OPTIONS = [:only, :exclude, :if, :unless, :with]
|
115
|
-
|
116
|
-
self._before_filters, self._after_filters = [], []
|
117
|
-
self._before_dispatch_callbacks, self._after_dispatch_callbacks = [], []
|
118
|
-
|
119
|
-
#---
|
120
|
-
# We're using abstract_subclasses so that Merb::Controller can have its
|
121
|
-
# own subclasses. We're using a Set so we don't have to worry about
|
122
|
-
# uniqueness.
|
123
|
-
self._abstract_subclasses = Set.new
|
124
|
-
|
125
|
-
# ==== Returns
|
126
|
-
# String:: The controller name in path form, e.g. "admin/items".
|
127
|
-
# :api: public
|
128
|
-
def self.controller_name() @controller_name ||= self.name.to_const_path end
|
129
|
-
|
130
|
-
# ==== Returns
|
131
|
-
# String:: The controller name in path form, e.g. "admin/items".
|
132
|
-
#
|
133
|
-
# :api: public
|
134
|
-
def controller_name() self.class.controller_name end
|
95
|
+
|
96
|
+
module Merb
|
97
|
+
module InlineTemplates; end
|
135
98
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
#
|
140
|
-
# ==== Parameters
|
141
|
-
# context<~to_s>:: The controller context (the action or template name).
|
142
|
-
# type<~to_s>:: The content type. Could be nil.
|
143
|
-
# controller<~to_s>::
|
144
|
-
# The name of the controller. Defaults to being called with the controller_name. Set t
|
145
|
-
#
|
146
|
-
#
|
147
|
-
# ==== Returns
|
148
|
-
# String::
|
149
|
-
# Indicating where to look for the template for the current controller,
|
150
|
-
# context, and content-type.
|
151
|
-
#
|
152
|
-
# ==== Notes
|
153
|
-
# The type is irrelevant for controller-types that don't support
|
154
|
-
# content-type negotiation, so we default to not include it in the
|
155
|
-
# superclass.
|
156
|
-
#
|
157
|
-
# ==== Examples
|
158
|
-
# def _template_location
|
159
|
-
# "#{params[:controller]}.#{params[:action]}.#{content_type}"
|
160
|
-
# end
|
161
|
-
#
|
162
|
-
# This would look for templates at controller.action.mime.type instead
|
163
|
-
# of controller/action.mime.type
|
164
|
-
#
|
165
|
-
# :api: public
|
166
|
-
# @overridable
|
167
|
-
def _template_location(context, type, controller)
|
168
|
-
controller ? "#{controller}/#{context}" : context
|
169
|
-
end
|
99
|
+
class AbstractController
|
100
|
+
include Merb::RenderMixin
|
101
|
+
include Merb::InlineTemplates
|
170
102
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# template<String>:: The absolute path to a template - without template extension.
|
175
|
-
# type<~to_s>::
|
176
|
-
# The mime-type of the template that will be rendered. Defaults to being called with nil.
|
177
|
-
#
|
178
|
-
# :api: public
|
179
|
-
# @overridable
|
180
|
-
def _absolute_template_location(template, type)
|
181
|
-
template
|
182
|
-
end
|
103
|
+
class_inheritable_accessor :_layout, :_template_root, :template_roots
|
104
|
+
class_inheritable_accessor :_before_filters, :_after_filters
|
105
|
+
class_inheritable_accessor :_before_dispatch_callbacks, :_after_dispatch_callbacks
|
183
106
|
|
184
|
-
|
185
|
-
#
|
186
|
-
# ==== Parameters
|
187
|
-
# root<~to_s>::
|
188
|
-
# The new path to set the template root to.
|
189
|
-
#
|
190
|
-
# :api: public
|
191
|
-
def self._template_root=(root)
|
192
|
-
@_template_root = root
|
193
|
-
_reset_template_roots
|
194
|
-
end
|
107
|
+
cattr_accessor :_abstract_subclasses
|
195
108
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
self.template_roots = [[self._template_root, :_template_location]]
|
201
|
-
end
|
109
|
+
# :api: plugin
|
110
|
+
attr_accessor :body, :action_name, :_benchmarks
|
111
|
+
# :api: private
|
112
|
+
attr_accessor :_thrown_content
|
202
113
|
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
# method.
|
207
|
-
#
|
208
|
-
# :api: plugin
|
209
|
-
def self._template_roots
|
210
|
-
self.template_roots || _reset_template_roots
|
211
|
-
end
|
114
|
+
# Stub so content-type support in RenderMixin doesn't throw errors
|
115
|
+
# :api: private
|
116
|
+
attr_accessor :content_type
|
212
117
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
118
|
+
FILTER_OPTIONS = [:only, :exclude, :if, :unless, :with]
|
119
|
+
|
120
|
+
self._before_filters, self._after_filters = [], []
|
121
|
+
self._before_dispatch_callbacks, self._after_dispatch_callbacks = [], []
|
122
|
+
|
123
|
+
#---
|
124
|
+
# We're using abstract_subclasses so that Merb::Controller can have its
|
125
|
+
# own subclasses. We're using a Set so we don't have to worry about
|
126
|
+
# uniqueness.
|
127
|
+
self._abstract_subclasses = Set.new
|
128
|
+
|
129
|
+
# ==== Returns
|
130
|
+
# String:: The controller name in path form, e.g. "admin/items".
|
131
|
+
# :api: public
|
132
|
+
def self.controller_name() @controller_name ||= self.name.to_const_path end
|
133
|
+
|
134
|
+
# ==== Returns
|
135
|
+
# String:: The controller name in path form, e.g. "admin/items".
|
136
|
+
#
|
137
|
+
# :api: public
|
138
|
+
def controller_name() self.class.controller_name end
|
222
139
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
140
|
+
# This is called after the controller is instantiated to figure out where to
|
141
|
+
# look for templates under the _template_root. Override this to define a new
|
142
|
+
# structure for your app.
|
143
|
+
#
|
144
|
+
# ==== Parameters
|
145
|
+
# context<~to_s>:: The controller context (the action or template name).
|
146
|
+
# type<~to_s>:: The content type. Could be nil.
|
147
|
+
# controller<~to_s>::
|
148
|
+
# The name of the controller. Defaults to being called with the controller_name. Set t
|
149
|
+
#
|
150
|
+
#
|
151
|
+
# ==== Returns
|
152
|
+
# String::
|
153
|
+
# Indicating where to look for the template for the current controller,
|
154
|
+
# context, and content-type.
|
155
|
+
#
|
156
|
+
# ==== Notes
|
157
|
+
# The type is irrelevant for controller-types that don't support
|
158
|
+
# content-type negotiation, so we default to not include it in the
|
159
|
+
# superclass.
|
160
|
+
#
|
161
|
+
# ==== Examples
|
162
|
+
# def _template_location
|
163
|
+
# "#{params[:controller]}.#{params[:action]}.#{content_type}"
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# This would look for templates at controller.action.mime.type instead
|
167
|
+
# of controller/action.mime.type
|
168
|
+
#
|
169
|
+
# :api: public
|
170
|
+
# @overridable
|
171
|
+
def _template_location(context, type, controller)
|
172
|
+
controller ? "#{controller}/#{context}" : context
|
173
|
+
end
|
174
|
+
|
175
|
+
# The location to look for a template - override this method for particular behaviour.
|
176
|
+
#
|
177
|
+
# ==== Parameters
|
178
|
+
# template<String>:: The absolute path to a template - without template extension.
|
179
|
+
# type<~to_s>::
|
180
|
+
# The mime-type of the template that will be rendered. Defaults to being called with nil.
|
181
|
+
#
|
182
|
+
# :api: public
|
183
|
+
# @overridable
|
184
|
+
def _absolute_template_location(template, type)
|
185
|
+
template
|
186
|
+
end
|
187
|
+
|
188
|
+
# Resets the template roots to the template root passed in.
|
189
|
+
#
|
190
|
+
# ==== Parameters
|
191
|
+
# root<~to_s>::
|
192
|
+
# The new path to set the template root to.
|
193
|
+
#
|
194
|
+
# :api: public
|
195
|
+
def self._template_root=(root)
|
196
|
+
@_template_root = root
|
197
|
+
_reset_template_roots
|
198
|
+
end
|
199
|
+
|
200
|
+
# Reset the template root based on the @_template_root ivar.
|
201
|
+
#
|
202
|
+
# :api: private
|
203
|
+
def self._reset_template_roots
|
204
|
+
self.template_roots = [[self._template_root, :_template_location]]
|
205
|
+
end
|
206
|
+
|
207
|
+
# ==== Returns
|
208
|
+
# roots<Array[Array]>::
|
209
|
+
# Template roots as pairs of template root path and template location
|
210
|
+
# method.
|
211
|
+
#
|
212
|
+
# :api: plugin
|
213
|
+
def self._template_roots
|
214
|
+
self.template_roots || _reset_template_roots
|
215
|
+
end
|
216
|
+
|
217
|
+
# ==== Parameters
|
218
|
+
# roots<Array[Array]>::
|
219
|
+
# Template roots as pairs of template root path and template location
|
220
|
+
# method.
|
221
|
+
#
|
222
|
+
# :api: plugin
|
223
|
+
def self._template_roots=(roots)
|
224
|
+
self.template_roots = roots
|
225
|
+
end
|
231
226
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
Object.make_module helper_module_name
|
241
|
-
klass.class_eval <<-HERE
|
242
|
-
include Object.full_const_get("#{helper_module_name}") rescue nil
|
243
|
-
HERE
|
244
|
-
super
|
245
|
-
end
|
227
|
+
# Returns the list of classes that have specifically subclassed AbstractController.
|
228
|
+
# Does not include all decendents.
|
229
|
+
#
|
230
|
+
# ==== Returns
|
231
|
+
# Set:: The subclasses.
|
232
|
+
#
|
233
|
+
# :api: private
|
234
|
+
def self.subclasses_list() _abstract_subclasses end
|
246
235
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
236
|
+
# ==== Parameters
|
237
|
+
# klass<Merb::AbstractController>::
|
238
|
+
# The controller that is being inherited from Merb::AbstractController
|
239
|
+
#
|
240
|
+
# :api: private
|
241
|
+
def self.inherited(klass)
|
242
|
+
_abstract_subclasses << klass.to_s
|
243
|
+
helper_module_name = klass.to_s =~ /^(#|Merb::)/ ? "#{klass}Helper" : "Merb::#{klass}Helper"
|
244
|
+
Object.make_module helper_module_name
|
245
|
+
klass.class_eval <<-HERE
|
246
|
+
include Object.full_const_get("#{helper_module_name}") rescue nil
|
247
|
+
HERE
|
248
|
+
super
|
249
|
+
end
|
256
250
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
# ==== Returns
|
266
|
-
# <~to_s>::
|
267
|
-
# Returns the string that was returned from the action.
|
268
|
-
#
|
269
|
-
# ==== Raises
|
270
|
-
# ArgumentError:: Invalid result caught from before filters.
|
271
|
-
#
|
272
|
-
# :api: plugin
|
273
|
-
def _dispatch(action)
|
274
|
-
self.action_name = action
|
275
|
-
self._before_dispatch_callbacks.each { |cb| cb.call(self) }
|
276
|
-
|
277
|
-
caught = catch(:halt) do
|
278
|
-
start = Time.now
|
279
|
-
result = _call_filters(_before_filters)
|
280
|
-
@_benchmarks[:before_filters_time] = Time.now - start if _before_filters
|
281
|
-
result
|
251
|
+
# This will initialize the controller, it is designed to be overridden in subclasses (like MerbController)
|
252
|
+
# ==== Parameters
|
253
|
+
# *args:: The args are ignored in this class, but we need this so that subclassed initializes can have parameters
|
254
|
+
#
|
255
|
+
# :api: private
|
256
|
+
def initialize(*args)
|
257
|
+
@_benchmarks = {}
|
258
|
+
@_caught_content = {}
|
282
259
|
end
|
283
260
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
#
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
261
|
+
# This will dispatch the request, calling internal before/after dispatch callbacks.
|
262
|
+
# If the return value of _call_filters is not :filter_chain_completed the action is not called, and the return from the filters is used instead.
|
263
|
+
#
|
264
|
+
# ==== Parameters
|
265
|
+
# action<~to_s>::
|
266
|
+
# The action to dispatch to. This will be #send'ed in _call_action.
|
267
|
+
# Defaults to :to_s.
|
268
|
+
#
|
269
|
+
# ==== Returns
|
270
|
+
# <~to_s>::
|
271
|
+
# Returns the string that was returned from the action.
|
272
|
+
#
|
273
|
+
# ==== Raises
|
274
|
+
# ArgumentError:: Invalid result caught from before filters.
|
275
|
+
#
|
276
|
+
# :api: plugin
|
277
|
+
def _dispatch(action)
|
278
|
+
self.action_name = action
|
279
|
+
self._before_dispatch_callbacks.each { |cb| cb.call(self) }
|
280
|
+
|
281
|
+
caught = catch(:halt) do
|
282
|
+
start = Time.now
|
283
|
+
result = _call_filters(_before_filters)
|
284
|
+
@_benchmarks[:before_filters_time] = Time.now - start if _before_filters
|
285
|
+
result
|
286
|
+
end
|
287
|
+
|
288
|
+
@body = case caught
|
289
|
+
when :filter_chain_completed then _call_action(action_name)
|
290
|
+
when String then caught
|
291
|
+
# return *something* if you throw halt with nothing
|
292
|
+
when nil then "<html><body><h1>Filter Chain Halted!</h1></body></html>"
|
293
|
+
when Symbol then __send__(caught)
|
294
|
+
when Proc then self.instance_eval(&caught)
|
295
|
+
else
|
296
|
+
raise ArgumentError, "Threw :halt, #{caught}. Expected String, nil, Symbol, Proc."
|
297
|
+
end
|
298
|
+
start = Time.now
|
299
|
+
_call_filters(_after_filters)
|
300
|
+
@_benchmarks[:after_filters_time] = Time.now - start if _after_filters
|
297
301
|
|
298
|
-
|
302
|
+
self._after_dispatch_callbacks.each { |cb| cb.call(self) }
|
299
303
|
|
300
|
-
|
301
|
-
|
304
|
+
@body
|
305
|
+
end
|
302
306
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
307
|
+
# This method exists to provide an overridable hook for ActionArgs. It uses #send to call the action method.
|
308
|
+
#
|
309
|
+
# ==== Parameters
|
310
|
+
# action<~to_s>:: the action method to dispatch to
|
311
|
+
#
|
312
|
+
# :api: plugin
|
313
|
+
# @overridable
|
314
|
+
def _call_action(action)
|
315
|
+
send(action)
|
316
|
+
end
|
313
317
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
318
|
+
# Calls a filter chain.
|
319
|
+
#
|
320
|
+
# ==== Parameters
|
321
|
+
# filter_set<Array[Filter]>::
|
322
|
+
# A set of filters in the form [[:filter, rule], [:filter, rule]]
|
323
|
+
#
|
324
|
+
# ==== Returns
|
325
|
+
# Symbol:: :filter_chain_completed.
|
326
|
+
#
|
327
|
+
# ==== Notes
|
328
|
+
# Filter rules can be Symbols, Strings, or Procs.
|
329
|
+
#
|
330
|
+
# Symbols or Strings::
|
331
|
+
# Call the method represented by the +Symbol+ or +String+.
|
332
|
+
# Procs::
|
333
|
+
# Execute the +Proc+, in the context of the controller (self will be the
|
334
|
+
# controller)
|
335
|
+
#
|
336
|
+
# :api: private
|
337
|
+
def _call_filters(filter_set)
|
338
|
+
(filter_set || []).each do |filter, rule|
|
339
|
+
if _call_filter_for_action?(rule, action_name) && _filter_condition_met?(rule)
|
340
|
+
case filter
|
341
|
+
when Symbol, String
|
342
|
+
if rule.key?(:with)
|
343
|
+
args = rule[:with]
|
344
|
+
send(filter, *args)
|
345
|
+
else
|
346
|
+
send(filter)
|
347
|
+
end
|
348
|
+
when Proc then self.instance_eval(&filter)
|
343
349
|
end
|
344
|
-
when Proc then self.instance_eval(&filter)
|
345
350
|
end
|
346
351
|
end
|
352
|
+
return :filter_chain_completed
|
347
353
|
end
|
348
|
-
return :filter_chain_completed
|
349
|
-
end
|
350
354
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
355
|
+
# Determine whether the filter should be called for the current action using :only and :exclude.
|
356
|
+
#
|
357
|
+
# ==== Parameters
|
358
|
+
# rule<Hash>:: Rules for the filter (see below).
|
359
|
+
# action_name<~to_s>:: The name of the action to be called.
|
360
|
+
#
|
361
|
+
# ==== Options (rule)
|
362
|
+
# :only<Array>::
|
363
|
+
# Optional list of actions to fire. If given, action_name must be a part of
|
364
|
+
# it for this function to return true.
|
365
|
+
# :exclude<Array>::
|
366
|
+
# Optional list of actions not to fire. If given, action_name must not be a
|
367
|
+
# part of it for this function to return true.
|
368
|
+
#
|
369
|
+
# ==== Returns
|
370
|
+
# Boolean:: True if the action should be called.
|
371
|
+
#
|
372
|
+
# :api: private
|
373
|
+
def _call_filter_for_action?(rule, action_name)
|
374
|
+
# Both:
|
375
|
+
# * no :only or the current action is in the :only list
|
376
|
+
# * no :exclude or the current action is not in the :exclude list
|
377
|
+
(!rule.key?(:only) || rule[:only].include?(action_name)) &&
|
378
|
+
(!rule.key?(:exclude) || !rule[:exclude].include?(action_name))
|
379
|
+
end
|
376
380
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
381
|
+
# Determines whether the filter should be run based on the conditions passed (:if and :unless)
|
382
|
+
#
|
383
|
+
# ==== Parameters
|
384
|
+
# rule<Hash>:: Rules for the filter (see below).
|
385
|
+
#
|
386
|
+
# ==== Options (rule)
|
387
|
+
# :if<Array>:: Optional conditions that must be met for the filter to fire.
|
388
|
+
# :unless<Array>::
|
389
|
+
# Optional conditions that must not be met for the filter to fire.
|
390
|
+
#
|
391
|
+
# ==== Returns
|
392
|
+
# Boolean:: True if the conditions are met.
|
393
|
+
#
|
394
|
+
# :api: private
|
395
|
+
def _filter_condition_met?(rule)
|
396
|
+
# Both:
|
397
|
+
# * no :if or the if condition evaluates to true
|
398
|
+
# * no :unless or the unless condition evaluates to false
|
399
|
+
(!rule.key?(:if) || _evaluate_condition(rule[:if])) &&
|
400
|
+
(!rule.key?(:unless) || ! _evaluate_condition(rule[:unless]))
|
401
|
+
end
|
398
402
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
403
|
+
# Evaluates a filter condition (:if or :unless)
|
404
|
+
#
|
405
|
+
# ==== Parameters
|
406
|
+
# condition<Symbol, Proc>:: The condition to evaluate.
|
407
|
+
#
|
408
|
+
# ==== Raises
|
409
|
+
# ArgumentError:: condition not a Symbol or Proc.
|
410
|
+
#
|
411
|
+
# ==== Returns
|
412
|
+
# Boolean:: True if the condition is met.
|
413
|
+
#
|
414
|
+
# ==== Alternatives
|
415
|
+
# If condition is a symbol, it will be send'ed. If it is a Proc it will be
|
416
|
+
# called directly with self as an argument.
|
417
|
+
#
|
418
|
+
# :api: private
|
419
|
+
def _evaluate_condition(condition)
|
420
|
+
case condition
|
421
|
+
when Symbol then self.send(condition)
|
422
|
+
when Proc then self.instance_eval(&condition)
|
423
|
+
else
|
424
|
+
raise ArgumentError,
|
425
|
+
'Filter condtions need to be either a Symbol or a Proc'
|
426
|
+
end
|
422
427
|
end
|
423
|
-
end
|
424
428
|
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
429
|
+
# Adds a filter to the after filter chain
|
430
|
+
# ==== Parameters
|
431
|
+
# filter<Symbol, Proc>:: The filter to add. Defaults to nil.
|
432
|
+
# opts<Hash>::
|
433
|
+
# Filter options (see class documentation under <tt>Filter Options</tt>).
|
434
|
+
# &block:: A block to use as a filter if filter is nil.
|
435
|
+
#
|
436
|
+
# ==== Notes
|
437
|
+
# If the filter already exists, its options will be replaced with opts.;
|
438
|
+
#
|
439
|
+
# :api: public
|
440
|
+
def self.after(filter = nil, opts = {}, &block)
|
441
|
+
add_filter(self._after_filters, filter || block, opts)
|
442
|
+
end
|
439
443
|
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
444
|
+
# Adds a filter to the before filter chain.
|
445
|
+
#
|
446
|
+
# ==== Parameters
|
447
|
+
# filter<Symbol, Proc>:: The filter to add. Defaults to nil.
|
448
|
+
# opts<Hash>::
|
449
|
+
# Filter options (see class documentation under <tt>Filter Options</tt>).
|
450
|
+
# &block:: A block to use as a filter if filter is nil.
|
451
|
+
#
|
452
|
+
# ==== Notes
|
453
|
+
# If the filter already exists, its options will be replaced with opts.
|
454
|
+
#
|
455
|
+
# :api: public
|
456
|
+
def self.before(filter = nil, opts = {}, &block)
|
457
|
+
add_filter(self._before_filters, filter || block, opts)
|
458
|
+
end
|
455
459
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
460
|
+
# Removes a filter from the after filter chain. This removes the
|
461
|
+
# filter from the filter chain for the whole controller and does not
|
462
|
+
# take any options.
|
463
|
+
#
|
464
|
+
# ==== Parameters
|
465
|
+
# filter<Symbol, String>:: A filter name to skip.
|
466
|
+
#
|
467
|
+
# :api: public
|
468
|
+
def self.skip_after(filter)
|
469
|
+
skip_filter(self._after_filters, filter)
|
470
|
+
end
|
467
471
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
472
|
+
# Removes a filter from the before filter chain. This removes the
|
473
|
+
# filter from the filter chain for the whole controller and does not
|
474
|
+
# take any options.
|
475
|
+
#
|
476
|
+
# ==== Parameters
|
477
|
+
# filter<Symbol, String>:: A filter name to skip.
|
478
|
+
#
|
479
|
+
# :api: public
|
480
|
+
def self.skip_before(filter)
|
481
|
+
skip_filter(self._before_filters , filter)
|
482
|
+
end
|
483
|
+
|
484
|
+
# There are three possible ways to use this method. First, if you have a named route,
|
485
|
+
# you can specify the route as the first parameter as a symbol and any paramters in a
|
486
|
+
# hash. Second, you can generate the default route by just passing the params hash,
|
487
|
+
# just passing the params hash. Finally, you can use the anonymous parameters. This
|
488
|
+
# allows you to specify the parameters to a named route in the order they appear in the
|
489
|
+
# router.
|
490
|
+
#
|
491
|
+
# ==== Parameters(Named Route)
|
492
|
+
# name<Symbol>::
|
493
|
+
# The name of the route.
|
494
|
+
# args<Hash>::
|
495
|
+
# Parameters for the route generation.
|
496
|
+
#
|
497
|
+
# ==== Parameters(Default Route)
|
498
|
+
# args<Hash>::
|
499
|
+
# Parameters for the route generation. This route will use the default route.
|
500
|
+
#
|
501
|
+
# ==== Parameters(Anonymous Parameters)
|
502
|
+
# name<Symbol>::
|
503
|
+
# The name of the route.
|
504
|
+
# args<Array>::
|
505
|
+
# An array of anonymous parameters to generate the route
|
506
|
+
# with. These parameters are assigned to the route parameters
|
507
|
+
# in the order that they are passed.
|
508
|
+
#
|
509
|
+
# ==== Returns
|
510
|
+
# String:: The generated URL.
|
511
|
+
#
|
512
|
+
# ==== Examples
|
513
|
+
# Named Route
|
514
|
+
#
|
515
|
+
# Merb::Router.prepare do
|
516
|
+
# match("/articles/:title").to(:controller => :articles, :action => :show).name("articles")
|
517
|
+
# end
|
518
|
+
#
|
519
|
+
# url(:articles, :title => "new_article")
|
520
|
+
#
|
521
|
+
# Default Route
|
522
|
+
#
|
523
|
+
# Merb::Router.prepare do
|
524
|
+
# default_routes
|
525
|
+
# end
|
526
|
+
#
|
527
|
+
# url(:controller => "articles", :action => "new")
|
528
|
+
#
|
529
|
+
# Anonymous Paramters
|
530
|
+
#
|
531
|
+
# Merb::Router.prepare do
|
532
|
+
# match("/articles/:year/:month/:title").to(:controller => :articles, :action => :show).name("articles")
|
533
|
+
# end
|
534
|
+
#
|
535
|
+
# url(:articles, 2008, 10, "test_article")
|
536
|
+
#
|
537
|
+
# :api: public
|
538
|
+
def url(name, *args)
|
539
|
+
args << {}
|
540
|
+
Merb::Router.url(name, *args)
|
541
|
+
end
|
538
542
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
543
|
+
alias_method :relative_url, :url
|
544
|
+
|
545
|
+
# Returns the absolute url including the passed protocol and host.
|
546
|
+
#
|
547
|
+
# This uses the same arguments as the url method, with added requirements
|
548
|
+
# of protocol and host options.
|
549
|
+
#
|
550
|
+
# :api: public
|
551
|
+
def absolute_url(*args)
|
552
|
+
# FIXME: arrgh, why request.protocol returns http://?
|
553
|
+
# :// is not part of protocol name
|
554
|
+
options = extract_options_from_args!(args) || {}
|
555
|
+
protocol = options.delete(:protocol)
|
556
|
+
host = options.delete(:host)
|
553
557
|
|
554
|
-
|
555
|
-
|
558
|
+
raise ArgumentError, "The :protocol option must be specified" unless protocol
|
559
|
+
raise ArgumentError, "The :host option must be specified" unless host
|
556
560
|
|
557
|
-
|
561
|
+
args << options
|
558
562
|
|
559
|
-
|
560
|
-
|
563
|
+
protocol + "://" + host + url(*args)
|
564
|
+
end
|
561
565
|
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
end
|
595
|
-
|
596
|
-
# Calls the capture method for the selected template engine.
|
597
|
-
#
|
598
|
-
# ==== Parameters
|
599
|
-
# *args:: Arguments to pass to the block.
|
600
|
-
# &block:: The block to call.
|
601
|
-
#
|
602
|
-
# ==== Returns
|
603
|
-
# String:: The output of a template block or the return value of a non-template block converted to a string.
|
604
|
-
#
|
605
|
-
# :api: public
|
606
|
-
def capture(*args, &block)
|
607
|
-
ret = nil
|
608
|
-
|
609
|
-
captured = send("capture_#{@_engine}", *args) do |*args|
|
610
|
-
ret = yield *args
|
566
|
+
# Generates a URL for a single or nested resource.
|
567
|
+
#
|
568
|
+
# ==== Parameters
|
569
|
+
# resources<Symbol,Object>:: The resources for which the URL
|
570
|
+
# should be generated. These resources should be specified
|
571
|
+
# in the router.rb file using #resources and #resource.
|
572
|
+
#
|
573
|
+
# options<Hash>:: Any extra parameters that are needed to
|
574
|
+
# generate the URL.
|
575
|
+
#
|
576
|
+
# ==== Returns
|
577
|
+
# String:: The generated URL.
|
578
|
+
#
|
579
|
+
# ==== Examples
|
580
|
+
#
|
581
|
+
# Merb::Router.prepare do
|
582
|
+
# resources :users do
|
583
|
+
# resources :comments
|
584
|
+
# end
|
585
|
+
# end
|
586
|
+
#
|
587
|
+
# resource(:users) # => /users
|
588
|
+
# resource(@user) # => /users/10
|
589
|
+
# resource(@user, :comments) # => /users/10/comments
|
590
|
+
# resource(@user, @comment) # => /users/10/comments/15
|
591
|
+
# resource(:users, :new) # => /users/new
|
592
|
+
# resource(:@user, :edit) # => /users/10/edit
|
593
|
+
#
|
594
|
+
# :api: public
|
595
|
+
def resource(*args)
|
596
|
+
args << {}
|
597
|
+
Merb::Router.resource(*args)
|
611
598
|
end
|
612
599
|
|
613
|
-
#
|
614
|
-
|
615
|
-
|
600
|
+
# Calls the capture method for the selected template engine.
|
601
|
+
#
|
602
|
+
# ==== Parameters
|
603
|
+
# *args:: Arguments to pass to the block.
|
604
|
+
# &block:: The block to call.
|
605
|
+
#
|
606
|
+
# ==== Returns
|
607
|
+
# String:: The output of a template block or the return value of a non-template block converted to a string.
|
608
|
+
#
|
609
|
+
# :api: public
|
610
|
+
def capture(*args, &block)
|
611
|
+
ret = nil
|
616
612
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
613
|
+
captured = send("capture_#{@_engine}", *args) do |*args|
|
614
|
+
ret = yield *args
|
615
|
+
end
|
616
|
+
|
617
|
+
# return captured value only if it is not empty
|
618
|
+
captured.empty? ? ret.to_s : captured
|
619
|
+
end
|
620
|
+
|
621
|
+
# Calls the concatenate method for the selected template engine.
|
622
|
+
#
|
623
|
+
# ==== Parameters
|
624
|
+
# str<String>:: The string to concatenate to the buffer.
|
625
|
+
# binding<Binding>:: The binding to use for the buffer.
|
626
|
+
#
|
627
|
+
# :api: public
|
628
|
+
def concat(str, binding)
|
629
|
+
send("concat_#{@_engine}", str, binding)
|
630
|
+
end
|
627
631
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
632
|
+
private
|
633
|
+
# adds a filter to the specified filter chain
|
634
|
+
# ==== Parameters
|
635
|
+
# filters<Array[Filter]>:: The filter chain that this should be added to.
|
636
|
+
# filter<Filter>:: A filter that should be added.
|
637
|
+
# opts<Hash>::
|
638
|
+
# Filter options (see class documentation under <tt>Filter Options</tt>).
|
639
|
+
#
|
640
|
+
# ==== Raises
|
641
|
+
# ArgumentError::
|
642
|
+
# Both :only and :exclude, or :if and :unless given, if filter is not a
|
643
|
+
# Symbol, String or Proc, or if an unknown option is passed.
|
644
|
+
#
|
645
|
+
# :api: private
|
646
|
+
def self.add_filter(filters, filter, opts={})
|
647
|
+
raise(ArgumentError,
|
648
|
+
"You can specify either :only or :exclude but
|
649
|
+
not both at the same time for the same filter.") if opts.key?(:only) && opts.key?(:exclude)
|
646
650
|
|
647
|
-
|
648
|
-
|
649
|
-
|
651
|
+
raise(ArgumentError,
|
652
|
+
"You can specify either :if or :unless but
|
653
|
+
not both at the same time for the same filter.") if opts.key?(:if) && opts.key?(:unless)
|
650
654
|
|
651
|
-
|
652
|
-
|
653
|
-
|
655
|
+
opts.each_key do |key| raise(ArgumentError,
|
656
|
+
"You can only specify known filter options, #{key} is invalid.") unless FILTER_OPTIONS.include?(key)
|
657
|
+
end
|
654
658
|
|
655
|
-
|
659
|
+
opts = normalize_filters!(opts)
|
656
660
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
filters << [filter, opts]
|
663
|
-
when Symbol, String
|
664
|
-
if existing_filter = filters.find {|f| f.first.to_s == filter.to_s}
|
665
|
-
filters[ filters.index(existing_filter) ] = [filter, opts]
|
666
|
-
else
|
661
|
+
case filter
|
662
|
+
when Proc
|
663
|
+
# filters with procs created via class methods have identical signature
|
664
|
+
# regardless if they handle content differently or not. So procs just
|
665
|
+
# get appended
|
667
666
|
filters << [filter, opts]
|
667
|
+
when Symbol, String
|
668
|
+
if existing_filter = filters.find {|f| f.first.to_s == filter.to_s}
|
669
|
+
filters[ filters.index(existing_filter) ] = [filter, opts]
|
670
|
+
else
|
671
|
+
filters << [filter, opts]
|
672
|
+
end
|
673
|
+
else
|
674
|
+
raise(ArgumentError,
|
675
|
+
'Filters need to be either a Symbol, String or a Proc'
|
676
|
+
)
|
668
677
|
end
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
678
|
+
end
|
679
|
+
|
680
|
+
# Skip a filter that was previously added to the filter chain. Useful in
|
681
|
+
# inheritence hierarchies.
|
682
|
+
#
|
683
|
+
# ==== Parameters
|
684
|
+
# filters<Array[Filter]>:: The filter chain that this should be removed from.
|
685
|
+
# filter<Filter>:: A filter that should be removed.
|
686
|
+
#
|
687
|
+
# ==== Raises
|
688
|
+
# ArgumentError:: filter not Symbol or String.
|
689
|
+
#
|
690
|
+
# :api: private
|
691
|
+
def self.skip_filter(filters, filter)
|
692
|
+
raise(ArgumentError, 'You can only skip filters that have a String or Symbol name.') unless
|
693
|
+
[Symbol, String].include? filter.class
|
694
|
+
|
695
|
+
Merb.logger.warn("Filter #{filter} was not found in your filter chain.") unless
|
696
|
+
filters.reject! {|f| f.first.to_s[filter.to_s] }
|
673
697
|
end
|
674
|
-
end
|
675
|
-
|
676
|
-
# Skip a filter that was previously added to the filter chain. Useful in
|
677
|
-
# inheritence hierarchies.
|
678
|
-
#
|
679
|
-
# ==== Parameters
|
680
|
-
# filters<Array[Filter]>:: The filter chain that this should be removed from.
|
681
|
-
# filter<Filter>:: A filter that should be removed.
|
682
|
-
#
|
683
|
-
# ==== Raises
|
684
|
-
# ArgumentError:: filter not Symbol or String.
|
685
|
-
#
|
686
|
-
# :api: private
|
687
|
-
def self.skip_filter(filters, filter)
|
688
|
-
raise(ArgumentError, 'You can only skip filters that have a String or Symbol name.') unless
|
689
|
-
[Symbol, String].include? filter.class
|
690
|
-
|
691
|
-
Merb.logger.warn("Filter #{filter} was not found in your filter chain.") unless
|
692
|
-
filters.reject! {|f| f.first.to_s[filter.to_s] }
|
693
|
-
end
|
694
698
|
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
699
|
+
# Ensures that the passed in hash values are always arrays.
|
700
|
+
#
|
701
|
+
# ==== Parameters
|
702
|
+
# opts<Hash>:: Options for the filters (see below).
|
703
|
+
#
|
704
|
+
# ==== Options (opts)
|
705
|
+
# :only<Symbol, Array[Symbol]>:: A list of actions.
|
706
|
+
# :exclude<Symbol, Array[Symbol]>:: A list of actions.
|
707
|
+
#
|
708
|
+
# ==== Examples
|
709
|
+
# normalize_filters!(:only => :new) #=> {:only => [:new]}
|
710
|
+
#
|
711
|
+
# :api: public
|
712
|
+
def self.normalize_filters!(opts={})
|
713
|
+
opts[:only] = Array(opts[:only]).map {|x| x.to_s} if opts[:only]
|
714
|
+
opts[:exclude] = Array(opts[:exclude]).map {|x| x.to_s} if opts[:exclude]
|
715
|
+
return opts
|
716
|
+
end
|
712
717
|
end
|
713
|
-
end
|
718
|
+
end
|