GitHub Sale: sign up for any paid plan this week and pay nothing until January 1, 2009!  [ hide ]

public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
Lori Holden (author)
Mon Sep 01 22:29:33 -0700 2008
commit  02b3f1f228450beb944149b9650a123dbc5a0e06
tree    bd33c87743c458b4ce68bcd659792d4b9ea72412
parent  7b73934210b3ea614f6657787ac4ba61c83c338d
merb-core / lib / merb-core / dispatch / router.rb
100644 203 lines (188 sloc) 7.219 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
require 'merb-core/dispatch/router/cached_proc'
require 'merb-core/dispatch/router/behavior'
require 'merb-core/dispatch/router/route'
require 'merb-core/controller/mixins/responder'
module Merb
  # Router stores route definitions and finds the first
  # route that matches the incoming request URL.
  #
  # Then information from route is used by dispatcher to
  # call action on the controller.
  #
  # ==== Routes compilation.
  #
  # The most interesting method of Router (and heart of
  # route matching machinery) is match method generated
  # on the fly from routes definitions. It is called routes
  # compilation. Generated match method body contains
  # one if/elsif statement that picks the first matching route
  # definition and sets values to named parameters of the route.
  #
  # Compilation is synchronized by mutex.
  class Router
    SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
    SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
    JUST_BRACKETS = /\[(\d+)\]/
    PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
 
    @@named_routes = {}
    @@routes = []
    @@compiler_mutex = Mutex.new
    cattr_accessor :routes, :named_routes
 
    class << self
      # Finds route matching URI of the request and
      # returns a tuple of [route index, route params].
      #
      # ==== Parameters
      # request<Merb::Request>:: request to match.
      #
      # ==== Returns
      # <Array(Integer, Hash)::
      # Two-tuple: route index and route parameters. Route
      # parameters are :controller, :action and all the named
      # segments of the route.
      def route_for(request)
        index, params = match(request)
        route = routes[index] if index
        if !route
          raise ControllerExceptions::NotFound,
            "No routes match the request: #{request.uri}"
        end
        [route, params]
      end
      
      # Clear all routes.
      def reset!
        self.routes, self.named_routes = [], {}
      end
 
      # Appends the generated routes to the current routes.
      #
      # ==== Parameters
      # &block::
      # A block that generates new routes when yielded a new Behavior.
      def append(&block)
        prepare(@@routes, [], &block)
      end
 
      # Prepends the generated routes to the current routes.
      #
      # ==== Parameters
      # &block::
      # A block that generates new routes when yielded a new Behavior.
      def prepend(&block)
        prepare([], @@routes, &block)
      end
 
      # Prepares new routes and adds them to existing routes.
      #
      # ==== Parameters
      # first<Array>:: An array of routes to add before the generated routes.
      # last<Array>:: An array of routes to add after the generated routes.
      # &block:: A block that generates new routes.
      #
      # ==== Block parameters (&block)
      # new_behavior<Behavior>:: Behavior for child routes.
      def prepare(first = [], last = [], &block)
        @@routes = []
        yield Behavior.new({}, { :action => 'index' }) # defaults
        @@routes = first + @@routes + last
        compile
      end
 
      # Capture any new routes that have been added within the block.
      #
      # This utility method lets you track routes that have been added;
      # it doesn't affect how/which routes are added.
      #
      # &block:: A context in which routes are generated.
      def capture(&block)
        routes_before, named_route_keys_before = self.routes.dup, self.named_routes.keys
        yield
        [self.routes - routes_before, self.named_routes.except(*named_route_keys_before)]
      end
 
      # ==== Returns
      # String:: A routing lambda statement generated from the routes.
      def compiled_statement
        @@compiler_mutex.synchronize do
          @@compiled_statement = "def match(request)\n"
          @@compiled_statement << " params = request.params\n"
          @@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
          @@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) }
          @@compiled_statement << " else\n [nil, {}]\n"
          @@compiled_statement << " end\n"
          @@compiled_statement << "end"
        end
      end
 
      # Defines the match function for this class based on the
      # compiled_statement.
      def compile
        puts "compiled route: #{compiled_statement}" if $DEBUG
        eval(compiled_statement, binding, "Generated Code for Router#match(#{__FILE__}:#{__LINE__})", 1)
      end
 
      # Generates a URL based on passed options.
      #
      # ==== Parameters
      # name<~to_sym, Hash>:: The name of the route to generate.
      # params<Hash, Fixnum, Object>:: The params to use in the route generation.
      # fallback<Hash>:: Parameters for generating a fallback URL.
      #
      # ==== Returns
      # String:: The generated URL.
      #
      # ==== Alternatives
      # If name is a hash, it will be merged with params and passed on to
      # generate_for_default_route along with fallback.
      def generate(name, params = {}, fallback = {})
        params.reject! { |k,v| v.nil? } if params.is_a? Hash
        if name.is_a? Hash
          name.reject! { |k,v| v.nil? }
          return generate_for_default_route(name.merge(params), fallback)
        end
        name = name.to_sym
        unless @@named_routes.key? name
          raise "Named route not found: #{name}"
        else
          @@named_routes[name].generate(params, fallback)
        end
      end
 
      # Generates a URL based on the default route scheme of
      # "/:controller/:action/:id.:format".
      #
      # ==== Parameters
      # params<Hash>::
      # The primary parameters to create the route from (see below).
      # fallback<Hash>:: Fallback parameters. Same options as params.
      #
      # ==== Options (params)
      # :controller<~to_s>:: The controller name. Required.
      # :action<~to_s>:: The action name. Required.
      # :id<~to_s>:: The ID for use in the action.
      # :format<~to_s>:: The format of the preferred response.
      #
      # ==== Returns
      # String:: The generated URL.
      def generate_for_default_route(params, fallback)
        query_params = params.reject do |k,v|
          [:controller, :action, :id, :format, :fragment].include?(k.to_sym)
        end
 
        controller = params[:controller] || fallback[:controller]
        raise "Controller Not Specified" unless controller
        url = "/#{controller}"
 
        if params[:action] || params[:id] || params[:format] || !query_params.empty?
          action = params[:action] || fallback[:action]
          raise "Action Not Specified" unless action
          url += "/#{action}"
        end
        if params[:id]
          url += "/#{params[:id]}"
        end
        if format = params[:format]
          format = fallback[:format] if format == :current
          url += ".#{format}"
        end
        unless query_params.empty?
          url += "?" + Merb::Request.params_to_query_string(query_params)
        end
        if params[:fragment]
          url += "##{params[:fragment]}"
        end
        url
      end
    end # self
 
  end
end