Class: Merb::Router::Route

Inherits:
Object show all
Defined in:
lib/merb-core/dispatch/router/route.rb

Overview

Route instances incapsulate information about particular route definition. Route definition ties number of conditions (URL match, HTTP request method) with resulting hash of route parameters: controller, action, format and named parameters from the URL.

The following routes definition:

Merb::Router.prepare do |r|

r.match("api/:action/:token.:format").to(:controller => "dev").fixatable

end

maps URL matching pattern to controller named “dev” and specifies fixation for that route. Path and request method are route conditions, controller name, action name, format and value of segment we decided to call :token are route parameters.

How route definitions are used.

When routes are compiled, each route produces a string with eval-able if/elsif condition statement. This statement together with others constructs body of Merb::Router.match method. Condition statements are Ruby code in form of string.

Segments.

Route definitions use conventional syntax for named parameters. This splits route path into segments. Static (not changing) segments represented internally as strings, named parameters are stored as symbols and called symbol segments. Symbol segments map to groups in regular expression in resulting condition statement.

Route conditions.

Because route conditions include path matching, regular expression is created from string that uses :segment format to fetch groups and assign them to named parameters. This regular expression is used to produce compiled statement mentioned above.

Route conditions may also include user agent. Symbol segments

Here is example of Route conditions:

:path => /^\/continents\/?(\.([^\/.,;?]+))?$/,
:method => /^get$/

Route parameters.

Route parameters is a Hash with controller name, action name and parameters key/value pairs. It is then merged with request.params hash.

Example of route parameters:

:action => "\"index\"",
:format => "path2",
:controller => "\"continents\""

Router takes first matching route and uses it’s parameters to dispatch request to certain controller and action.

Behavior

Each route has utility collaborator called behavior that incapsulates additional information about route (like namespace or if route is deferred) and also provides utility methods.

Route registration.

When route is added to set of routes, it is called route registration. Registred route knows it’s index in routes set.

Fixation

Fixatable routes allow setting of session key from GET params found in incoming request. This is very useful to allow certain URLs to be used by rich media applications and other kinds of clients that have no other way of passing session identifier.

Conditional block.

Conditional block is anonymous function that is evaluated when deferred routes are processed. Unless route is deferred, it has no condition block.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(conditions, params, behavior = nil, &conditional_block) ⇒ Route

Parameters

conditions<Hash>

Conditions for the route.

params<Hash>

Parameters for the route.

behavior<Merb::Router::Behavior>

The associated behavior. Defaults to nil.

&conditional_block

A block with the conditions to be met for the route to take effect.



107
108
109
110
111
112
113
114
# File 'lib/merb-core/dispatch/router/route.rb', line 107

def initialize(conditions, params, behavior = nil, &conditional_block)
  @conditions, @params, @behavior = conditions, params, behavior
  @conditional_block = conditional_block
  @fixation=false
  if @behavior && (path = @behavior.merged_original_conditions[:path])
    @segments = segments_from_path(path)
  end
end

Instance Attribute Details

#behaviorObject (readonly)

Returns the value of attribute behavior.



98
99
100
# File 'lib/merb-core/dispatch/router/route.rb', line 98

def behavior
  @behavior
end

#conditional_blockObject (readonly)

Returns the value of attribute conditional_block.



97
98
99
# File 'lib/merb-core/dispatch/router/route.rb', line 97

def conditional_block
  @conditional_block
end

#conditionsObject (readonly)

Returns the value of attribute conditions.



97
98
99
# File 'lib/merb-core/dispatch/router/route.rb', line 97

def conditions
  @conditions
end

#indexObject (readonly)

Returns the value of attribute index.



98
99
100
# File 'lib/merb-core/dispatch/router/route.rb', line 98

def index
  @index
end

#paramsObject (readonly)

Returns the value of attribute params.



98
99
100
# File 'lib/merb-core/dispatch/router/route.rb', line 98

def params
  @params
end

#segmentsObject (readonly)

Returns the value of attribute segments.



98
99
100
# File 'lib/merb-core/dispatch/router/route.rb', line 98

def segments
  @segments
end

#symbolObject (readonly)

Returns the value of attribute symbol.



98
99
100
# File 'lib/merb-core/dispatch/router/route.rb', line 98

def symbol
  @symbol
end

Instance Method Details

#allow_fixation?Boolean

Returns

Boolean

True if fixation is allowed.

Returns:

  • (Boolean)


118
119
120
# File 'lib/merb-core/dispatch/router/route.rb', line 118

def allow_fixation?
  @fixation
end

#behavior_traceObject

Prints a trace of the behavior for this route.



312
313
314
315
316
317
318
# File 'lib/merb-core/dispatch/router/route.rb', line 312

def behavior_trace
  if @behavior
    puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
  else
    puts "No behavior to trace #{self}"
  end
end

#compile(first = false) ⇒ Object

Compiles the route to a form used by Merb::Router. This form sometimes referred as condition statement of the route.

Parameters

first<Boolean>

True if this is the first route in set of routes. Defaults to false.

Returns

String

The code corresponding to the route in a form suited for eval.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/merb-core/dispatch/router/route.rb', line 289

def compile(first = false)
  code = ""
  default_params = { :action => "index" }
  get_value = proc do |key|
    if default_params.has_key?(key) && params[key][0] != ?"
      "#{params[key]} || \"#{default_params[key]}\""
    else
      "#{params[key]}"
    end
  end
  params_as_string = params.keys.map { |k| "#{k.inspect} => #{get_value[k]}" }.join(', ')
  code << "  els" unless first
  code << "if  # #{@behavior.merged_original_conditions.inspect}  \n"
  code << if_conditions(params_as_string).join(" && ") << "\n"
  code << "    # then\n"
  if @conditional_block
    code << "    [#{@index.inspect}, block_result]\n"
  else
    code << "    [#{@index.inspect}, {#{params_as_string}}]\n"
  end
end

#fixatable(enable = true) ⇒ Object

Parameters

enabled<Boolean>

True enables fixation on the route.



124
125
126
127
# File 'lib/merb-core/dispatch/router/route.rb', line 124

def fixatable(enable=true)
  @fixation = enable
  self
end

#generate(params = {}, fallback = {}) ⇒ Object

Generates URL using route segments and given parameters. If parameter value responds to :to_param, it is called.

Parameters

params<Hash>

Optional parameters for the route.

fallback<Hash>

Optional parameters for the fallback route.

Returns

String

The URL corresponding to the params, using the stored route segments for reconstruction of the URL.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/merb-core/dispatch/router/route.rb', line 206

def generate(params = {}, fallback = {})
  raise "Cannot generate regexp Routes" if regexp?
  query_params = params.dup if params.is_a? Hash
  url = @segments.map do |segment|
    value =
      if segment.is_a? Symbol
        if params.is_a? Hash
          if segment.to_s =~ /_id/ && params[:id].respond_to?(segment)
            params[segment] = params[:id].send(segment)
          end
          query_params.delete segment
          params[segment] || fallback[segment]
        else
          if segment == :id && params.respond_to?(:to_param)
            params.to_param
          elsif segment == :id && params.is_a?(Fixnum)
            params
          elsif params.respond_to?(segment)
            params.send(segment)
          else
            fallback[segment]
          end
        end
      elsif segment.respond_to? :to_s
        segment
      else
        raise "Segment type '#{segment.class}' can't be converted to a string"
      end
    (value.respond_to?(:to_param) ? value.to_param : value).to_s.unescape_regexp
  end.join
  if query_params && format = query_params.delete(:format)
    format = fallback[:format] if format == :current
    url += ".#{format}"
  end
  if query_params && !query_params.empty?
    url += "?" + Merb::Request.params_to_query_string(query_params)
  end
  url
end

#if_conditions(params_as_string) ⇒ Object

Generates and returns if statement used to construct final condition statement of the route.

Params

params_as_string<String>

The params hash as a string, e.g. “:foo => ‘bar’”.

Returns

Array

All the conditions as eval’able strings.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/merb-core/dispatch/router/route.rb', line 255

def if_conditions(params_as_string)
  cond = []
  condition_string = proc do |key, value, regexp_string|
    max = Behavior.count_parens_up_to(value.source, value.source.size)
    captures = max == 0 ? "" : (1..max).to_a.map{ |n| "#{key}#{n}" }.join(", ") + " = " +
                               (1..max).to_a.map{ |n| "$#{n}"}.join(", ")
    " (#{value.inspect} =~ #{regexp_string}) #{" && (" + captures + ")" unless captures.empty?}"
  end
  @conditions.each_pair do |key, value|

    # Note: =~ is slightly faster than .match
    cond << case key
    when :path then condition_string[key, value, "cached_path"]
    when :method then condition_string[key, value, "cached_method"]
    else condition_string[key, value, "request.#{key}.to_s"]
    end
  end
  if @conditional_block
    str = "  # #{@conditional_block.inspect.scan(/@([^>]+)/).flatten.first}\n"
    str << "    (block_result = #{CachedProc.new(@conditional_block)}.call(request, params.merge({#{params_as_string}})))" if @conditional_block
    cond << str
  end
  cond
end

#name(symbol = nil) ⇒ Object

Names this route in Router. Name must be a Symbol.

Parameters

symbol<Symbol>

The name of the route.

Raises

ArgumentError

symbol is not a Symbol.

Raises:

  • (ArgumentError)


182
183
184
185
# File 'lib/merb-core/dispatch/router/route.rb', line 182

def name(symbol = nil)
  raise ArgumentError unless (@symbol = symbol).is_a?(Symbol)
  Router.named_routes[@symbol] = self
end

#regexp?Boolean

Returns

Boolean

True if this route is a regexp, i.e. its behavior or one of the behavior’s ancestors is a regexp.

Returns:

  • (Boolean)


191
192
193
# File 'lib/merb-core/dispatch/router/route.rb', line 191

def regexp?
  @regexp ||= behavior.regexp? || behavior.ancestors.any? { |a| a.regexp? }
end

#registerObject

Registers the route in the Router.routes array. After registration route has index.



142
143
144
145
146
# File 'lib/merb-core/dispatch/router/route.rb', line 142

def register
  @index = Router.routes.size
  Router.routes << self
  self
end

#segments_from_path(path) ⇒ Object

Turn a path into string and symbol segments so it can be reconstructed, as in the case of a named route.

Parameters

path<String>

The path to split into segments.

Returns

Array

The Symbol and String segments for the path.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/merb-core/dispatch/router/route.rb', line 162

def segments_from_path(path)
  # Remove leading ^ and trailing $ from each segment (left-overs from regexp joining)
  strip = proc { |str| str.gsub(/^\^/, '').gsub(/\$$/, '') }
  segments = []
  while match = (path.match(SEGMENT_REGEXP))
    segments << strip[match.pre_match] unless match.pre_match.empty?
    segments << match[2].intern
    path = strip[match.post_match]
  end
  segments << strip[path] unless path.empty?
  segments
end

#symbol_segmentsObject

Returns

Array

All the symbols in the segments array.



150
151
152
# File 'lib/merb-core/dispatch/router/route.rb', line 150

def symbol_segments
  (segments || []).select{ |s| s.is_a?(Symbol) }
end

#to_sObject

Concatenates all route segments and returns result. Symbol segments have colon preserved.

Returns

String

The route as a string, e.g. “admin/:controller/:id”.



134
135
136
137
138
# File 'lib/merb-core/dispatch/router/route.rb', line 134

def to_s
  (segments || []).inject('') do |str,seg|
    str << (seg.is_a?(Symbol) ? ":#{seg}" : seg)
  end
end