Class: ActionController::Routing::RouteSet

Inherits:
Object
  • Object
show all
Defined in:
lib/action_controller/routing.rb

Overview

:nodoc:

Defined Under Namespace

Classes: Mapper, NamedRouteCollection

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRouteSet

Returns a new instance of RouteSet.



1237
1238
1239
1240
# File 'lib/action_controller/routing.rb', line 1237

def initialize
  self.routes = []
  self.named_routes = NamedRouteCollection.new
end

Instance Attribute Details

#named_routesObject

Returns the value of attribute named_routes.



1235
1236
1237
# File 'lib/action_controller/routing.rb', line 1235

def named_routes
  @named_routes
end

#routesObject

Returns the value of attribute routes.



1235
1236
1237
# File 'lib/action_controller/routing.rb', line 1235

def routes
  @routes
end

Instance Method Details

#add_named_route(name, path, options = {}) ⇒ Object



1306
1307
1308
1309
1310
# File 'lib/action_controller/routing.rb', line 1306

def add_named_route(name, path, options = {})
  # TODO - is options EVER used?
  name = options[:name_prefix] + name.to_s if options[:name_prefix]
  named_routes[name.to_sym] = add_route(path, options)
end

#add_route(path, options = {}) ⇒ Object



1300
1301
1302
1303
1304
# File 'lib/action_controller/routing.rb', line 1300

def add_route(path, options = {})
  route = builder.build(path, options)
  routes << route
  route
end

#build_expiry(options, recall) ⇒ Object



1330
1331
1332
1333
1334
1335
# File 'lib/action_controller/routing.rb', line 1330

def build_expiry(options, recall)
  recall.inject({}) do |expiry, (key, recalled_value)|
    expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
    expiry
  end
end

#builderObject

Subclasses and plugins may override this method to specify a different RouteBuilder instance, so that other route DSL’s can be created.



1244
1245
1246
# File 'lib/action_controller/routing.rb', line 1244

def builder
  @builder ||= RouteBuilder.new
end

#clear!Object



1254
1255
1256
1257
1258
1259
# File 'lib/action_controller/routing.rb', line 1254

def clear!
  routes.clear
  named_routes.clear
  @combined_regexp = nil
  @routes_by_controller = nil
end

#draw {|Mapper.new(self)| ... } ⇒ Object

Yields:



1248
1249
1250
1251
1252
# File 'lib/action_controller/routing.rb', line 1248

def draw
  clear!
  yield Mapper.new(self)
  install_helpers
end

#empty?Boolean

Returns:

  • (Boolean)


1266
1267
1268
# File 'lib/action_controller/routing.rb', line 1266

def empty?
  routes.empty?
end

#extra_keys(options, recall = {}) ⇒ Object

Generate the path indicated by the arguments, and return an array of the keys that were not used to generate it.



1339
1340
1341
# File 'lib/action_controller/routing.rb', line 1339

def extra_keys(options, recall={})
  generate_extras(options, recall).last
end

#extract_request_environment(request) ⇒ Object

Subclasses and plugins may override this method to extract further attributes from the request, for use by route conditions and such.



1482
1483
1484
# File 'lib/action_controller/routing.rb', line 1482

def extract_request_environment(request)
  { :method => request.method }
end

#generate(options, recall = {}, method = :generate) ⇒ Object

Raises:



1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
# File 'lib/action_controller/routing.rb', line 1347

def generate(options, recall = {}, method=:generate)
  named_route_name = options.delete(:use_route)
  generate_all = options.delete(:generate_all)
  if named_route_name
    named_route = named_routes[named_route_name]
    options = named_route.parameter_shell.merge(options)
  end

  options = options_as_params(options)
  expire_on = build_expiry(options, recall)

  if options[:controller]
    options[:controller] = options[:controller].to_s
  end
  # if the controller has changed, make sure it changes relative to the
  # current controller module, if any. In other words, if we're currently
  # on admin/get, and the new controller is 'set', the new controller
  # should really be admin/set.
  if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
    old_parts = recall[:controller].split('/')
    new_parts = options[:controller].split('/')
    parts = old_parts[0..-(new_parts.length + 1)] + new_parts
    options[:controller] = parts.join('/')
  end

  # drop the leading '/' on the controller name
  options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
  merged = recall.merge(options)

  if named_route
    path = named_route.generate(options, merged, expire_on)
    if path.nil?
      raise_named_route_error(options, named_route, named_route_name)
    else
      return path
    end
  else
    merged[:action] ||= 'index'
    options[:action] ||= 'index'

    controller = merged[:controller]
    action = merged[:action]

    raise RoutingError, "Need controller and action!" unless controller && action

    if generate_all
      # Used by caching to expire all paths for a resource
      return routes.collect do |route|
        route.send!(method, options, merged, expire_on)
      end.compact
    end

    # don't use the recalled keys when determining which routes to check
    routes = routes_by_controller[controller][action][options.keys.sort_by { |x| x.object_id }]

    routes.each do |route|
      results = route.send!(method, options, merged, expire_on)
      return results if results && (!results.is_a?(Array) || results.first)
    end
  end

  raise RoutingError, "No route matches #{options.inspect}"
end

#generate_extras(options, recall = {}) ⇒ Object



1343
1344
1345
# File 'lib/action_controller/routing.rb', line 1343

def generate_extras(options, recall={})
  generate(options, recall, :generate_extras)
end

#install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) ⇒ Object



1261
1262
1263
1264
# File 'lib/action_controller/routing.rb', line 1261

def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
  Array(destinations).each { |d| d.module_eval { include Helpers } }
  named_routes.install(destinations, regenerate_code)
end

#load!Object Also known as: reload!



1270
1271
1272
1273
1274
1275
# File 'lib/action_controller/routing.rb', line 1270

def load!
  Routing.use_controllers! nil # Clear the controller cache so we may discover new ones
  clear!
  load_routes!
  install_helpers
end

#load_routes!Object



1291
1292
1293
1294
1295
1296
1297
1298
# File 'lib/action_controller/routing.rb', line 1291

def load_routes!
  if defined?(RAILS_ROOT) && defined?(::ActionController::Routing::Routes) && self == ::ActionController::Routing::Routes
    load File.join("#{RAILS_ROOT}/config/routes.rb")
    @routes_last_modified = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
  else
    add_route ":controller/:action/:id"
  end
end

#options_as_params(options) ⇒ Object



1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
# File 'lib/action_controller/routing.rb', line 1312

def options_as_params(options)
  # If an explicit :controller was given, always make :action explicit
  # too, so that action expiry works as expected for things like
  #
  #   generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
  #
  # (the above is from the unit tests). In the above case, because the
  # controller was explicitly given, but no action, the action is implied to
  # be "index", not the recalled action of "show".
  #
  # great fun, eh?

  options_as_params = options.clone
  options_as_params[:action] ||= 'index' if options[:controller]
  options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
  options_as_params
end

#raise_named_route_error(options, named_route, named_route_name) ⇒ Object

try to give a helpful error message when named route generation fails



1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
# File 'lib/action_controller/routing.rb', line 1412

def raise_named_route_error(options, named_route, named_route_name)
  diff = named_route.requirements.diff(options)
  unless diff.empty?
    raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
  else
    required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
    required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
    raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route.  content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
  end
end

#recognize(request) ⇒ Object



1423
1424
1425
1426
1427
# File 'lib/action_controller/routing.rb', line 1423

def recognize(request)
  params = recognize_path(request.path, extract_request_environment(request))
  request.path_parameters = params.with_indifferent_access
  "#{params[:controller].camelize}Controller".constantize
end

#recognize_path(path, environment = {}) ⇒ Object



1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
# File 'lib/action_controller/routing.rb', line 1429

def recognize_path(path, environment={})
  routes.each do |route|
    result = route.recognize(path, environment) and return result
  end

  allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, :method => verb) } }

  if environment[:method] && !HTTP_METHODS.include?(environment[:method])
    raise NotImplemented.new(*allows)
  elsif !allows.empty?
    raise MethodNotAllowed.new(*allows)
  else
    raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
  end
end

#reloadObject



1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
# File 'lib/action_controller/routing.rb', line 1280

def reload
  if @routes_last_modified && defined?(RAILS_ROOT)
    mtime = File.stat("#{RAILS_ROOT}/config/routes.rb").mtime
    # if it hasn't been changed, then just return
    return if mtime == @routes_last_modified
    # if it has changed then record the new time and fall to the load! below
    @routes_last_modified = mtime
  end
  load!
end

#routes_by_controllerObject



1445
1446
1447
1448
1449
1450
1451
1452
1453
# File 'lib/action_controller/routing.rb', line 1445

def routes_by_controller
  @routes_by_controller ||= Hash.new do |controller_hash, controller|
    controller_hash[controller] = Hash.new do |action_hash, action|
      action_hash[action] = Hash.new do |key_hash, keys|
        key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys)
      end
    end
  end
end

#routes_for(options, merged, expire_on) ⇒ Object



1455
1456
1457
1458
1459
1460
1461
1462
# File 'lib/action_controller/routing.rb', line 1455

def routes_for(options, merged, expire_on)
  raise "Need controller and action!" unless controller && action
  controller = merged[:controller]
  merged = options if expire_on[:controller]
  action = merged[:action] || 'index'

  routes_by_controller[controller][action][merged.keys]
end

#routes_for_controller_and_action(controller, action) ⇒ Object



1464
1465
1466
1467
1468
1469
# File 'lib/action_controller/routing.rb', line 1464

def routes_for_controller_and_action(controller, action)
  selected = routes.select do |route|
    route.matches_controller_and_action? controller, action
  end
  (selected.length == routes.length) ? routes : selected
end

#routes_for_controller_and_action_and_keys(controller, action, keys) ⇒ Object



1471
1472
1473
1474
1475
1476
1477
1478
# File 'lib/action_controller/routing.rb', line 1471

def routes_for_controller_and_action_and_keys(controller, action, keys)
  selected = routes.select do |route|
    route.matches_controller_and_action? controller, action
  end
  selected.sort_by do |route|
    (keys - route.significant_keys).length
  end
end