wycats-merb-core 0.9.8
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/CHANGELOG +992 -0
- data/CONTRIBUTORS +94 -0
- data/LICENSE +20 -0
- data/PUBLIC_CHANGELOG +142 -0
- data/README +21 -0
- data/Rakefile +458 -0
- data/TODO +0 -0
- data/bin/merb +11 -0
- data/bin/merb-specs +5 -0
- data/lib/merb-core.rb +598 -0
- data/lib/merb-core/autoload.rb +31 -0
- data/lib/merb-core/bootloader.rb +717 -0
- data/lib/merb-core/config.rb +305 -0
- data/lib/merb-core/constants.rb +45 -0
- data/lib/merb-core/controller/abstract_controller.rb +568 -0
- data/lib/merb-core/controller/exceptions.rb +315 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +107 -0
- data/lib/merb-core/controller/mixins/authentication.rb +123 -0
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/controller.rb +319 -0
- data/lib/merb-core/controller/mixins/render.rb +513 -0
- data/lib/merb-core/controller/mixins/responder.rb +469 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +9 -0
- data/lib/merb-core/core_ext/hash.rb +7 -0
- data/lib/merb-core/core_ext/kernel.rb +340 -0
- data/lib/merb-core/dispatch/cookies.rb +130 -0
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
- data/lib/merb-core/dispatch/dispatcher.rb +176 -0
- data/lib/merb-core/dispatch/request.rb +729 -0
- data/lib/merb-core/dispatch/router.rb +151 -0
- data/lib/merb-core/dispatch/router/behavior.rb +566 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/resources.rb +191 -0
- data/lib/merb-core/dispatch/router/route.rb +511 -0
- data/lib/merb-core/dispatch/session.rb +222 -0
- data/lib/merb-core/dispatch/session/container.rb +74 -0
- data/lib/merb-core/dispatch/session/cookie.rb +173 -0
- data/lib/merb-core/dispatch/session/memcached.rb +68 -0
- data/lib/merb-core/dispatch/session/memory.rb +99 -0
- data/lib/merb-core/dispatch/session/store_container.rb +150 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +203 -0
- data/lib/merb-core/plugins.rb +67 -0
- data/lib/merb-core/rack.rb +25 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +32 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +20 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
- data/lib/merb-core/rack/middleware/content_length.rb +18 -0
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/rack/middleware/tracer.rb +20 -0
- data/lib/merb-core/server.rb +284 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/gem_management.rb +229 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
- data/lib/merb-core/tasks/stats.rake +71 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/route_helper.rb +39 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
- data/lib/merb-core/test/run_specs.rb +49 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +342 -0
- data/lib/merb-core/version.rb +3 -0
- metadata +253 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'merb-core/dispatch/router/cached_proc'
|
2
|
+
require 'merb-core/dispatch/router/behavior'
|
3
|
+
require 'merb-core/dispatch/router/resources'
|
4
|
+
require 'merb-core/dispatch/router/route'
|
5
|
+
|
6
|
+
module Merb
|
7
|
+
# Router stores route definitions and finds the first
|
8
|
+
# route that matches the incoming request URL.
|
9
|
+
#
|
10
|
+
# Then information from route is used by dispatcher to
|
11
|
+
# call action on the controller.
|
12
|
+
#
|
13
|
+
# ==== Routes compilation.
|
14
|
+
#
|
15
|
+
# The most interesting method of Router (and heart of
|
16
|
+
# route matching machinery) is match method generated
|
17
|
+
# on the fly from routes definitions. It is called routes
|
18
|
+
# compilation. Generated match method body contains
|
19
|
+
# one if/elsif statement that picks the first matching route
|
20
|
+
# definition and sets values to named parameters of the route.
|
21
|
+
#
|
22
|
+
# Compilation is synchronized by mutex.
|
23
|
+
class Router
|
24
|
+
@routes = []
|
25
|
+
@named_routes = {}
|
26
|
+
@compiler_mutex = Mutex.new
|
27
|
+
@root_behavior = Behavior.new.defaults(:action => "index")
|
28
|
+
|
29
|
+
# Raised when route lookup fails.
|
30
|
+
class RouteNotFound < StandardError; end;
|
31
|
+
# Raised when parameters given to generation
|
32
|
+
# method do not match route parameters.
|
33
|
+
class GenerationError < StandardError; end;
|
34
|
+
class NotCompiledError < StandardError; end;
|
35
|
+
|
36
|
+
class << self
|
37
|
+
# @private
|
38
|
+
attr_accessor :routes, :named_routes, :root_behavior
|
39
|
+
|
40
|
+
# Creates a route building context and evaluates the block in it. A
|
41
|
+
# copy of +root_behavior+ (and instance of Behavior) is copied as
|
42
|
+
# the context.
|
43
|
+
#
|
44
|
+
# ==== Parameters
|
45
|
+
# first<Array>::
|
46
|
+
# An array containing routes that should be prepended to the routes
|
47
|
+
# defined in the block.
|
48
|
+
#
|
49
|
+
# last<Array>::
|
50
|
+
# An array containing routes that should be appended to the routes
|
51
|
+
# defined in the block.
|
52
|
+
#
|
53
|
+
# ==== Returns
|
54
|
+
# Merb::Router::
|
55
|
+
# Returns self to allow chaining of methods.
|
56
|
+
def prepare(first = [], last = [], &block)
|
57
|
+
@routes = []
|
58
|
+
root_behavior.with_proxy(&block)
|
59
|
+
@routes = first + @routes + last
|
60
|
+
compile
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
# Appends route in the block to routing table.
|
65
|
+
def append(&block)
|
66
|
+
prepare(routes, [], &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Prepends routes in the block to routing table.
|
70
|
+
def prepend(&block)
|
71
|
+
prepare([], routes, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Clears the routing table. Route generation and request matching
|
75
|
+
# won't work anymore until a new routing table is built.
|
76
|
+
def reset!
|
77
|
+
class << self
|
78
|
+
alias_method :match, :match_before_compilation
|
79
|
+
end
|
80
|
+
self.routes, self.named_routes = [], {}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Finds route matching URI of the request and returns a tuple of
|
84
|
+
# [route index, route params]. This method is called by the
|
85
|
+
# dispatcher and isn't as useful in applications.
|
86
|
+
#
|
87
|
+
# ==== Parameters
|
88
|
+
# request<Merb::Request>:: request to match.
|
89
|
+
#
|
90
|
+
# ==== Returns
|
91
|
+
# <Array(Integer, Hash)::
|
92
|
+
# Two-tuple: route index and route parameters. Route
|
93
|
+
# parameters are :controller, :action and all the named
|
94
|
+
# segments of the route.
|
95
|
+
#
|
96
|
+
# ---
|
97
|
+
# @private
|
98
|
+
def route_for(request) #:nodoc:
|
99
|
+
index, params = match(request)
|
100
|
+
route = routes[index] if index
|
101
|
+
if !route
|
102
|
+
raise ControllerExceptions::NotFound,
|
103
|
+
"No routes match the request: #{request.uri}"
|
104
|
+
end
|
105
|
+
[route, params]
|
106
|
+
end
|
107
|
+
|
108
|
+
# Just a placeholder for the compiled match method
|
109
|
+
def match_before_compilation(request) #:nodoc:
|
110
|
+
raise NotCompiledError, "The routes have not been compiled yet"
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :match, :match_before_compilation
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
# Defines method with a switch statement that does routes recognition.
|
118
|
+
def compile
|
119
|
+
if routes.any?
|
120
|
+
eval(compiled_statement, binding, "Generated Code for Router", 1)
|
121
|
+
else
|
122
|
+
reset!
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Generates method that does route recognition with a switch statement.
|
127
|
+
def compiled_statement
|
128
|
+
@compiler_mutex.synchronize do
|
129
|
+
condition_keys, if_statements = Set.new, ""
|
130
|
+
|
131
|
+
routes.each_with_index do |route, i|
|
132
|
+
route.freeze
|
133
|
+
route.conditions.keys.each { |key| condition_keys << key }
|
134
|
+
if_statements << route.compiled_statement(i == 0)
|
135
|
+
end
|
136
|
+
|
137
|
+
statement = "def match(request)\n"
|
138
|
+
statement << condition_keys.inject("") do |cached, key|
|
139
|
+
cached << " cached_#{key} = request.#{key}.to_s\n"
|
140
|
+
end
|
141
|
+
statement << if_statements
|
142
|
+
statement << " else\n"
|
143
|
+
statement << " [nil, {}]\n"
|
144
|
+
statement << " end\n"
|
145
|
+
statement << "end"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end # class << self
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,566 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class Router
|
4
|
+
|
5
|
+
class Behavior
|
6
|
+
|
7
|
+
class Error < StandardError; end;
|
8
|
+
|
9
|
+
# Proxy catches any methods and proxies them to the current behavior.
|
10
|
+
# This allows building routes without constantly having to catching the
|
11
|
+
# yielded behavior object
|
12
|
+
# ---
|
13
|
+
# @private
|
14
|
+
class Proxy #:nodoc:
|
15
|
+
# Undefine as many methods as possible so that everything can be proxied
|
16
|
+
# along to the behavior
|
17
|
+
instance_methods.each { |m| undef_method m unless %w[ __id__ __send__ class kind_of? respond_to? assert_kind_of should should_not instance_variable_set instance_variable_get instance_eval].include?(m) }
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@behaviors = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def push(behavior)
|
24
|
+
@behaviors.push(behavior)
|
25
|
+
end
|
26
|
+
|
27
|
+
def pop
|
28
|
+
@behaviors.pop
|
29
|
+
end
|
30
|
+
|
31
|
+
# Rake does some stuff with methods in the global namespace, so if I don't
|
32
|
+
# explicitly define the Behavior methods to proxy here (specifically namespace)
|
33
|
+
# Rake's methods take precedence.
|
34
|
+
%w(
|
35
|
+
match to with register default defaults options option namespace identify
|
36
|
+
default_routes defer_to name full_name fixatable redirect capture
|
37
|
+
).each do |method|
|
38
|
+
class_eval %{
|
39
|
+
def #{method}(*args, &block)
|
40
|
+
@behaviors.last.#{method}(*args, &block)
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to?(*args)
|
46
|
+
super || @behaviors.last.respond_to?(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def method_missing(method, *args, &block)
|
52
|
+
behavior = @behaviors.last
|
53
|
+
|
54
|
+
if behavior.respond_to?(method)
|
55
|
+
behavior.send(method, *args, &block)
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Behavior objects are used for the Route building DSL. Each object keeps
|
63
|
+
# track of the current definitions for the level at which it is defined.
|
64
|
+
# Each time a method is called on a Behavior object that accepts a block,
|
65
|
+
# a new instance of the Behavior class is created.
|
66
|
+
#
|
67
|
+
# ==== Parameters
|
68
|
+
#
|
69
|
+
# proxy<Proxy>::
|
70
|
+
# This is the object initialized by Merb::Router.prepare that tracks the
|
71
|
+
# current Behavior object stack so that Behavior methods can be called
|
72
|
+
# without explicitly calling them on an instance of Behavior.
|
73
|
+
# conditions<Hash>::
|
74
|
+
# The initial route conditions. See #match.
|
75
|
+
# params<Hash>::
|
76
|
+
# The initial route parameters. See #to.
|
77
|
+
# defaults<Hash>::
|
78
|
+
# The initial route default parameters. See #defaults.
|
79
|
+
# options<Hash>::
|
80
|
+
# The initial route options. See #options.
|
81
|
+
#
|
82
|
+
# ==== Returns
|
83
|
+
# Behavior:: The initialized Behavior object
|
84
|
+
#---
|
85
|
+
# @private
|
86
|
+
def initialize(proxy = nil, conditions = {}, params = {}, defaults = {}, identifiers = {}, options = {}) #:nodoc:
|
87
|
+
@proxy = proxy
|
88
|
+
@conditions = conditions
|
89
|
+
@params = params
|
90
|
+
@defaults = defaults
|
91
|
+
@identifiers = identifiers
|
92
|
+
@options = options
|
93
|
+
|
94
|
+
stringify_condition_values
|
95
|
+
end
|
96
|
+
|
97
|
+
# Defines the +conditions+ that are required to match a Request. Each
|
98
|
+
# +condition+ is applied to a method of the Request object. Conditions
|
99
|
+
# can also be applied to segments of the +path+.
|
100
|
+
#
|
101
|
+
# If #match is passed a block, it will create a new route scope with
|
102
|
+
# the conditions passed to it and yield to the block such that all
|
103
|
+
# routes that are defined in the block have the conditions applied
|
104
|
+
# to them.
|
105
|
+
#
|
106
|
+
# ==== Parameters
|
107
|
+
#
|
108
|
+
# path<String, Regexp>::
|
109
|
+
# The pattern against which Merb::Request path is matched.
|
110
|
+
#
|
111
|
+
# When +path+ is a String, any substring that is wrapped in parenthesis
|
112
|
+
# is considered optional and any segment that begins with a colon, ex.:
|
113
|
+
# ":login", defines both a capture and a named param. Extra conditions
|
114
|
+
# can then be applied each named param individually.
|
115
|
+
#
|
116
|
+
# When +path+ is a Regexp, the pattern is left untouched and the
|
117
|
+
# Merb::Request path is matched against it as is.
|
118
|
+
#
|
119
|
+
# +path+ is optional.
|
120
|
+
#
|
121
|
+
# conditions<Hash>::
|
122
|
+
# Additional conditions that the request must meet in order to match.
|
123
|
+
# The keys must be the names of previously defined path segments or
|
124
|
+
# be methods that the Merb::Request instance will respond to. The
|
125
|
+
# value is the string or regexp that matched the returned value.
|
126
|
+
# Conditions are inherited by child routes.
|
127
|
+
#
|
128
|
+
# &block::
|
129
|
+
# All routes defined in the block will be scoped to the conditions
|
130
|
+
# defined by the #match method.
|
131
|
+
#
|
132
|
+
# ==== Block parameters
|
133
|
+
# r<Behavior>:: +optional+ - The match behavior object.
|
134
|
+
#
|
135
|
+
# ==== Returns
|
136
|
+
# Behavior::
|
137
|
+
# A new instance of Behavior with the specified path and conditions.
|
138
|
+
#
|
139
|
+
# +Tip+: When nesting always make sure the most inner sub-match registers
|
140
|
+
# a Route and doesn't just returns new Behaviors.
|
141
|
+
#
|
142
|
+
# ==== Examples
|
143
|
+
#
|
144
|
+
# # registers /foo/bar to controller => "foo", :action => "bar"
|
145
|
+
# # and /foo/baz to controller => "foo", :action => "baz"
|
146
|
+
# match("/foo") do
|
147
|
+
# match("/bar").to(:controller => "foo", :action => "bar")
|
148
|
+
# match("/baz").to(:controller => "foo", :action => "caz")
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# # Checks the format of the segments against the specified Regexp
|
152
|
+
# match("/:string/:number", :string => /[a-z]+/, :number => /\d+/).
|
153
|
+
# to(:controller => "string_or_numbers")
|
154
|
+
#
|
155
|
+
# # Equivalent to the default_route
|
156
|
+
# match("/:controller(/:action(:id))(.:format)").register
|
157
|
+
#
|
158
|
+
# #match only if the browser string contains MSIE or Gecko
|
159
|
+
# match("/foo", :user_agent => /(MSIE|Gecko)/ )
|
160
|
+
# .to(:controller => 'foo', :action => 'popular')
|
161
|
+
#
|
162
|
+
# # Route GET and POST requests to different actions (see also #resources)
|
163
|
+
# r.match('/foo', :method => :get).to(:action => 'show')
|
164
|
+
# r.match('/foo', :method => :post).to(:action => 'create')
|
165
|
+
#
|
166
|
+
# # match also takes regular expressions
|
167
|
+
#
|
168
|
+
# r.match(%r[/account/([a-z]{4,6})]).to(:controller => "account",
|
169
|
+
# :action => "show", :id => "[1]")
|
170
|
+
#
|
171
|
+
# r.match(%r{/?(en|es|fr|be|nl)?}).to(:language => "[1]") do
|
172
|
+
# match("/guides/:action/:id").to(:controller => "tour_guides")
|
173
|
+
# end
|
174
|
+
#---
|
175
|
+
# @public
|
176
|
+
def match(path = {}, conditions = {}, &block)
|
177
|
+
path, conditions = path[:path], path if Hash === path
|
178
|
+
conditions[:path] = merge_paths(path)
|
179
|
+
|
180
|
+
raise Error, "The route has already been committed. Further conditions cannot be specified" if @route
|
181
|
+
|
182
|
+
behavior = Behavior.new(@proxy, @conditions.merge(conditions), @params, @defaults, @identifiers, @options)
|
183
|
+
with_behavior_context(behavior, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Creates a Route from one or more Behavior objects, unless a +block+ is
|
187
|
+
# passed in.
|
188
|
+
#
|
189
|
+
# ==== Parameters
|
190
|
+
# params<Hash>:: The parameters the route maps to.
|
191
|
+
#
|
192
|
+
# &block::
|
193
|
+
# All routes defined in the block will be scoped to the params
|
194
|
+
# defined by the #to method.
|
195
|
+
#
|
196
|
+
# ==== Block parameters
|
197
|
+
# r<Behavior>:: +optional+ - The to behavior object.
|
198
|
+
#
|
199
|
+
# ==== Returns
|
200
|
+
# Route:: It registers a new route and returns it.
|
201
|
+
#
|
202
|
+
# ==== Examples
|
203
|
+
# match('/:controller/:id).to(:action => 'show')
|
204
|
+
#
|
205
|
+
# to(:controller => 'simple') do
|
206
|
+
# match('/test').to(:action => 'index')
|
207
|
+
# match('/other').to(:action => 'other')
|
208
|
+
# end
|
209
|
+
#---
|
210
|
+
# @public
|
211
|
+
def to(params = {}, &block)
|
212
|
+
raise Error, "The route has already been committed. Further params cannot be specified" if @route
|
213
|
+
|
214
|
+
behavior = Behavior.new(@proxy, @conditions, @params.merge(params), @defaults, @identifiers, @options)
|
215
|
+
|
216
|
+
if block_given?
|
217
|
+
with_behavior_context(behavior, &block)
|
218
|
+
else
|
219
|
+
behavior.to_route
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Equivalent of #to. Allows for some nicer syntax when scoping blocks
|
224
|
+
# --- Ex:
|
225
|
+
# Merb::Router.prepare do
|
226
|
+
# with(:controller => "users") do
|
227
|
+
# match("/signup").to(:action => "signup")
|
228
|
+
# match("/login").to(:action => "login")
|
229
|
+
# match("/logout").to(:action => "logout")
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
alias_method :with, :to
|
233
|
+
|
234
|
+
# Equivalent of #to. Allows for nicer syntax when registering routes with no params
|
235
|
+
# --- Ex:
|
236
|
+
# Merb::Router.prepare do
|
237
|
+
# match("/:controller(/:action(/:id))(.:format)").register
|
238
|
+
# end
|
239
|
+
#
|
240
|
+
alias_method :register, :to
|
241
|
+
|
242
|
+
# Sets default values for route parameters. If no value for the key
|
243
|
+
# can be extracted from the request, then the value provided here
|
244
|
+
# will be used.
|
245
|
+
#
|
246
|
+
# ==== Parameters
|
247
|
+
# defaults<Hash>::
|
248
|
+
# The default values for named segments.
|
249
|
+
#
|
250
|
+
# &block::
|
251
|
+
# All routes defined in the block will be scoped to the defaults defined
|
252
|
+
# by the #default method.
|
253
|
+
#
|
254
|
+
# ==== Block parameters
|
255
|
+
# r<Behavior>:: +optional+ - The defaults behavior object.
|
256
|
+
# ---
|
257
|
+
# @public
|
258
|
+
def default(defaults = {}, &block)
|
259
|
+
behavior = Behavior.new(@proxy, @conditions, @params, @defaults.merge(defaults), @identifiers, @options)
|
260
|
+
with_behavior_context(behavior, &block)
|
261
|
+
end
|
262
|
+
|
263
|
+
alias_method :defaults, :default
|
264
|
+
|
265
|
+
# Allows the fine tuning of certain router options.
|
266
|
+
#
|
267
|
+
# ==== Parameters
|
268
|
+
# options<Hash>::
|
269
|
+
# The options to set for all routes defined in the scope. The currently
|
270
|
+
# supported options are:
|
271
|
+
# * :controller_prefix - The module that the controller is included in.
|
272
|
+
# * :name_prefix - The prefix added to all routes named with #name
|
273
|
+
#
|
274
|
+
# &block::
|
275
|
+
# All routes defined in the block will be scoped to the options defined
|
276
|
+
# by the #options method.
|
277
|
+
#
|
278
|
+
# ==== Block parameters
|
279
|
+
# r<Behavior>:: The options behavior object. This is optional
|
280
|
+
#
|
281
|
+
# ==== Examples
|
282
|
+
# # If :group is not matched in the path, it will be "registered" instead
|
283
|
+
# # of nil.
|
284
|
+
# match("/users(/:group)").default(:group => "registered")
|
285
|
+
# ---
|
286
|
+
# @public
|
287
|
+
def options(opts = {}, &block)
|
288
|
+
options = @options.dup
|
289
|
+
|
290
|
+
opts.each_pair do |key, value|
|
291
|
+
options[key] = (options[key] || []) + [value.freeze] if value
|
292
|
+
end
|
293
|
+
|
294
|
+
behavior = Behavior.new(@proxy, @conditions, @params, @defaults, @identifiers, options)
|
295
|
+
with_behavior_context(behavior, &block)
|
296
|
+
end
|
297
|
+
|
298
|
+
alias_method :options, :options
|
299
|
+
|
300
|
+
# Creates a namespace for a route. This way you can have logical
|
301
|
+
# separation to your routes.
|
302
|
+
#
|
303
|
+
# ==== Parameters
|
304
|
+
# name_or_path<String, Symbol>::
|
305
|
+
# The name or path of the namespace.
|
306
|
+
#
|
307
|
+
# options<Hash>::
|
308
|
+
# Optional hash, set :path if you want to override what appears on the url
|
309
|
+
#
|
310
|
+
# &block::
|
311
|
+
# All routes defined in the block will be scoped to the namespace defined
|
312
|
+
# by the #namespace method.
|
313
|
+
#
|
314
|
+
# ==== Block parameters
|
315
|
+
# r<Behavior>:: The namespace behavior object. This is optional
|
316
|
+
#
|
317
|
+
# ==== Examples
|
318
|
+
# namespace :admin do
|
319
|
+
# resources :accounts
|
320
|
+
# resource :email
|
321
|
+
# end
|
322
|
+
#
|
323
|
+
# # /super_admin/accounts
|
324
|
+
# namespace(:admin, :path=>"super_admin") do
|
325
|
+
# resources :accounts
|
326
|
+
# end
|
327
|
+
# ---
|
328
|
+
# @public
|
329
|
+
def namespace(name_or_path, opts = {}, &block)
|
330
|
+
name = name_or_path.to_s # We don't want this modified ever
|
331
|
+
path = opts.has_key?(:path) ? opts[:path] : name
|
332
|
+
|
333
|
+
raise Error, "The route has already been committed. Further options cannot be specified" if @route
|
334
|
+
|
335
|
+
# option keys could be nil
|
336
|
+
opts[:controller_prefix] = name unless opts.has_key?(:controller_prefix)
|
337
|
+
opts[:name_prefix] = name unless opts.has_key?(:name_prefix)
|
338
|
+
|
339
|
+
behavior = self
|
340
|
+
behavior = behavior.match("/#{path}") unless path.nil? || path.empty?
|
341
|
+
behavior.options(opts, &block)
|
342
|
+
end
|
343
|
+
|
344
|
+
# Sets a method for instances of specified Classes to be called before
|
345
|
+
# insertion into a route. This is useful when using models and want a
|
346
|
+
# specific method to be called on it (For example, for ActiveRecord::Base
|
347
|
+
# it would be #to_param).
|
348
|
+
#
|
349
|
+
# The default method called on objects is #to_s.
|
350
|
+
#
|
351
|
+
# ==== Paramters
|
352
|
+
# identifiers<Hash>::
|
353
|
+
# The keys are Classes and the values are the method that instances of the specified
|
354
|
+
# class should have called on.
|
355
|
+
#
|
356
|
+
# &block::
|
357
|
+
# All routes defined in the block will be call the specified methods during
|
358
|
+
# generation.
|
359
|
+
#
|
360
|
+
# ==== Block parameters
|
361
|
+
# r<Behavior>:: The identify behavior object. This is optional
|
362
|
+
# ---
|
363
|
+
# @public
|
364
|
+
def identify(identifiers = {}, &block)
|
365
|
+
identifiers = if Hash === identifiers
|
366
|
+
@identifiers.merge(identifiers)
|
367
|
+
else
|
368
|
+
{ Object => identifiers }
|
369
|
+
end
|
370
|
+
|
371
|
+
behavior = Behavior.new(@proxy, @conditions, @params, @defaults, identifiers.freeze, @options)
|
372
|
+
with_behavior_context(behavior, &block)
|
373
|
+
end
|
374
|
+
|
375
|
+
# Creates the most common routes /:controller/:action/:id.format when
|
376
|
+
# called with no arguments. You can pass a hash or a block to add parameters
|
377
|
+
# or override the default behavior.
|
378
|
+
#
|
379
|
+
# ==== Parameters
|
380
|
+
# params<Hash>::
|
381
|
+
# This optional hash can be used to augment the default settings
|
382
|
+
#
|
383
|
+
# &block::
|
384
|
+
# When passing a block a new behavior is yielded and more refinement is
|
385
|
+
# possible.
|
386
|
+
#
|
387
|
+
# ==== Returns
|
388
|
+
# Route:: the default route
|
389
|
+
#
|
390
|
+
# ==== Examples
|
391
|
+
#
|
392
|
+
# # Passing an extra parameter "mode" to all matches
|
393
|
+
# r.default_routes :mode => "default"
|
394
|
+
#
|
395
|
+
# # specifying exceptions within a block
|
396
|
+
# r.default_routes do |nr|
|
397
|
+
# nr.defer_to do |request, params|
|
398
|
+
# nr.match(:protocol => "http://").to(:controller => "login",
|
399
|
+
# :action => "new") if request.env["REQUEST_URI"] =~ /\/private\//
|
400
|
+
# end
|
401
|
+
# end
|
402
|
+
#---
|
403
|
+
# @public
|
404
|
+
def default_routes(params = {}, &block)
|
405
|
+
match("/:controller(/:action(/:id))(.:format)").to(params, &block).name(:default)
|
406
|
+
end
|
407
|
+
|
408
|
+
# Takes a block and stores it for deferred conditional routes. The block
|
409
|
+
# takes the +request+ object and the +params+ hash as parameters.
|
410
|
+
#
|
411
|
+
# ==== Parameters
|
412
|
+
# params<Hash>:: Parameters and conditions associated with this behavior.
|
413
|
+
# &conditional_block::
|
414
|
+
# A block with the conditions to be met for the behavior to take
|
415
|
+
# effect.
|
416
|
+
#
|
417
|
+
# ==== Returns
|
418
|
+
# Route :: The default route.
|
419
|
+
#
|
420
|
+
# ==== Examples
|
421
|
+
# r.defer_to do |request, params|
|
422
|
+
# params.merge :controller => 'here',
|
423
|
+
# :action => 'there' if request.xhr?
|
424
|
+
# end
|
425
|
+
#---
|
426
|
+
# @public
|
427
|
+
def defer_to(params = {}, &conditional_block)
|
428
|
+
to_route(params, &conditional_block)
|
429
|
+
end
|
430
|
+
|
431
|
+
# Names this route in Router. Name must be a Symbol.
|
432
|
+
#
|
433
|
+
# ==== Parameters
|
434
|
+
# symbol<Symbol>:: The name of the route.
|
435
|
+
#
|
436
|
+
# ==== Raises
|
437
|
+
# ArgumentError:: symbol is not a Symbol.
|
438
|
+
def name(prefix, name = nil)
|
439
|
+
unless name
|
440
|
+
name, prefix = prefix, nil
|
441
|
+
end
|
442
|
+
|
443
|
+
full_name([prefix, @options[:name_prefix], name].flatten.compact.join('_'))
|
444
|
+
end
|
445
|
+
|
446
|
+
# Names this route in Router. Name must be a Symbol. The current
|
447
|
+
# name_prefix is ignored.
|
448
|
+
#
|
449
|
+
# ==== Parameters
|
450
|
+
# symbol<Symbol>:: The name of the route.
|
451
|
+
#
|
452
|
+
# ==== Raises
|
453
|
+
# ArgumentError:: symbol is not a Symbol.
|
454
|
+
def full_name(name)
|
455
|
+
if @route
|
456
|
+
@route.name = name
|
457
|
+
self
|
458
|
+
else
|
459
|
+
register.full_name(name)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
# ==== Parameters
|
464
|
+
# enabled<Boolean>:: True enables fixation on the route.
|
465
|
+
def fixatable(enable = true)
|
466
|
+
@route.fixation = enable
|
467
|
+
self
|
468
|
+
end
|
469
|
+
|
470
|
+
def redirect(url, permanent = true)
|
471
|
+
raise Error, "The route has already been committed." if @route
|
472
|
+
|
473
|
+
status = permanent ? 301 : 302
|
474
|
+
@route = Route.new(@conditions, {:url => url.freeze, :status => status.freeze}, :redirects => true)
|
475
|
+
@route.register
|
476
|
+
self
|
477
|
+
end
|
478
|
+
|
479
|
+
# Capture any new routes that have been added within the block.
|
480
|
+
#
|
481
|
+
# This utility method lets you track routes that have been added;
|
482
|
+
# it doesn't affect how/which routes are added.
|
483
|
+
#
|
484
|
+
# &block:: A context in which routes are generated.
|
485
|
+
def capture(&block)
|
486
|
+
captured_routes = {}
|
487
|
+
name_prefix = [@options[:name_prefix]].flatten.compact.map { |p| "#{p}_"}
|
488
|
+
current_names = Merb::Router.named_routes.keys
|
489
|
+
|
490
|
+
behavior = Behavior.new(@proxy, @conditions, @params, @defaults, @identifiers, @options)
|
491
|
+
with_behavior_context(behavior, &block)
|
492
|
+
|
493
|
+
Merb::Router.named_routes.reject { |k,v| current_names.include?(k) }.each do |name, route|
|
494
|
+
name = route.name.to_s.sub("#{name_prefix}", '').to_sym unless name_prefix.empty?
|
495
|
+
captured_routes[name] = route
|
496
|
+
end
|
497
|
+
|
498
|
+
captured_routes
|
499
|
+
end
|
500
|
+
|
501
|
+
# So that Router can have a default route
|
502
|
+
# ---
|
503
|
+
# @private
|
504
|
+
def with_proxy(&block) #:nodoc:
|
505
|
+
proxy = Proxy.new
|
506
|
+
proxy.push Behavior.new(proxy, @conditions, @params, @defaults, @identifiers, @options)
|
507
|
+
proxy.instance_eval(&block)
|
508
|
+
proxy
|
509
|
+
end
|
510
|
+
|
511
|
+
protected
|
512
|
+
|
513
|
+
def to_route(params = {}, &conditional_block) # :nodoc:
|
514
|
+
|
515
|
+
raise Error, "The route has already been committed." if @route
|
516
|
+
|
517
|
+
params = @params.merge(params)
|
518
|
+
controller = params[:controller]
|
519
|
+
|
520
|
+
if prefixes = @options[:controller_prefix]
|
521
|
+
controller ||= ":controller"
|
522
|
+
|
523
|
+
prefixes.reverse_each do |prefix|
|
524
|
+
break if controller =~ %r{^/(.*)} && controller = $1
|
525
|
+
controller = "#{prefix}/#{controller}"
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
params.merge!(:controller => controller.to_s.gsub(%r{^/}, '')) if controller
|
530
|
+
|
531
|
+
# Sorts the identifiers so that modules that are at the bottom of the
|
532
|
+
# inheritance chain come first (more specific modules first). Object
|
533
|
+
# should always be last.
|
534
|
+
identifiers = @identifiers.sort { |(first,_),(sec,_)| first <=> sec || 1 }
|
535
|
+
|
536
|
+
@route = Route.new(@conditions.dup, params, :defaults => @defaults.dup, :identifiers => identifiers, &conditional_block)
|
537
|
+
@route.register
|
538
|
+
self
|
539
|
+
end
|
540
|
+
|
541
|
+
private
|
542
|
+
|
543
|
+
def stringify_condition_values # :nodoc:
|
544
|
+
@conditions.each do |key, value|
|
545
|
+
unless value.nil? || Regexp === value || Array === value
|
546
|
+
@conditions[key] = value.to_s
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
def with_behavior_context(behavior, &block) # :nodoc:
|
552
|
+
if block_given?
|
553
|
+
@proxy.push(behavior)
|
554
|
+
retval = yield(behavior)
|
555
|
+
@proxy.pop
|
556
|
+
end
|
557
|
+
behavior
|
558
|
+
end
|
559
|
+
|
560
|
+
def merge_paths(path) # :nodoc:
|
561
|
+
[@conditions[:path], path.freeze].flatten.compact
|
562
|
+
end
|
563
|
+
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|