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.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +458 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +319 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +340 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. 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