717 lines
24 KiB
Ruby
717 lines
24 KiB
Ruby
|
module ActionController
|
||
|
module Routing #:nodoc:
|
||
|
class << self
|
||
|
def expiry_hash(options, recall)
|
||
|
k = v = nil
|
||
|
expire_on = {}
|
||
|
options.each {|k, v| expire_on[k] = ((rcv = recall[k]) && (rcv != v))}
|
||
|
expire_on
|
||
|
end
|
||
|
|
||
|
def extract_parameter_value(parameter) #:nodoc:
|
||
|
CGI.escape((parameter.respond_to?(:to_param) ? parameter.to_param : parameter).to_s)
|
||
|
end
|
||
|
def controller_relative_to(controller, previous)
|
||
|
if controller.nil? then previous
|
||
|
elsif controller[0] == ?/ then controller[1..-1]
|
||
|
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
|
||
|
else controller
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def treat_hash(hash, keys_to_delete = [])
|
||
|
k = v = nil
|
||
|
hash.each do |k, v|
|
||
|
if v then hash[k] = (v.respond_to? :to_param) ? v.to_param.to_s : v.to_s
|
||
|
else
|
||
|
hash.delete k
|
||
|
keys_to_delete << k
|
||
|
end
|
||
|
end
|
||
|
hash
|
||
|
end
|
||
|
|
||
|
def test_condition(expression, condition)
|
||
|
case condition
|
||
|
when String then "(#{expression} == #{condition.inspect})"
|
||
|
when Regexp then
|
||
|
condition = Regexp.new("^#{condition.source}$") unless /^\^.*\$$/ =~ condition.source
|
||
|
"(#{condition.inspect} =~ #{expression})"
|
||
|
when Array then
|
||
|
conds = condition.collect do |condition|
|
||
|
cond = test_condition(expression, condition)
|
||
|
(cond[0, 1] == '(' && cond[-1, 1] == ')') ? cond : "(#{cond})"
|
||
|
end
|
||
|
"(#{conds.join(' || ')})"
|
||
|
when true then expression
|
||
|
when nil then "! #{expression}"
|
||
|
else
|
||
|
raise ArgumentError, "Valid criteria are strings, regular expressions, true, or nil"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Component #:nodoc:
|
||
|
def dynamic?() false end
|
||
|
def optional?() false end
|
||
|
|
||
|
def key() nil end
|
||
|
|
||
|
def self.new(string, *args)
|
||
|
return super(string, *args) unless self == Component
|
||
|
case string
|
||
|
when ':controller' then ControllerComponent.new(:controller, *args)
|
||
|
when /^:(\w+)$/ then DynamicComponent.new($1, *args)
|
||
|
when /^\*(\w+)$/ then PathComponent.new($1, *args)
|
||
|
else StaticComponent.new(string, *args)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class StaticComponent < Component #:nodoc:
|
||
|
attr_reader :value
|
||
|
|
||
|
def initialize(value)
|
||
|
@value = value
|
||
|
end
|
||
|
|
||
|
def write_recognition(g)
|
||
|
g.if_next_matches(value) do |gp|
|
||
|
gp.move_forward {|gpp| gpp.continue}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def write_generation(g)
|
||
|
g.add_segment(value) {|gp| gp.continue }
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class DynamicComponent < Component #:nodoc:
|
||
|
attr_reader :key, :default
|
||
|
attr_accessor :condition
|
||
|
|
||
|
def dynamic?() true end
|
||
|
def optional?() @optional end
|
||
|
|
||
|
def default=(default)
|
||
|
@optional = true
|
||
|
@default = default
|
||
|
end
|
||
|
|
||
|
def initialize(key, options = {})
|
||
|
@key = key.to_sym
|
||
|
@optional = false
|
||
|
default, @condition = options[:default], options[:condition]
|
||
|
self.default = default if options.key?(:default)
|
||
|
end
|
||
|
|
||
|
def default_check(g)
|
||
|
presence = "#{g.hash_value(key, !! default)}"
|
||
|
if default
|
||
|
"!(#{presence} && #{g.hash_value(key, false)} != #{default.to_s.inspect})"
|
||
|
else
|
||
|
"! #{presence}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def write_generation(g)
|
||
|
wrote_dropout = write_dropout_generation(g)
|
||
|
write_continue_generation(g, wrote_dropout)
|
||
|
end
|
||
|
|
||
|
def write_dropout_generation(g)
|
||
|
return false unless optional? && g.after.all? {|c| c.optional?}
|
||
|
|
||
|
check = [default_check(g)]
|
||
|
gp = g.dup # Use another generator to write the conditions after the first &&
|
||
|
# We do this to ensure that the generator will not assume x_value is set. It will
|
||
|
# not be set if it follows a false condition -- for example, false && (x = 2)
|
||
|
|
||
|
check += gp.after.map {|c| c.default_check gp}
|
||
|
gp.if(check.join(' && ')) { gp.finish } # If this condition is met, we stop here
|
||
|
true
|
||
|
end
|
||
|
|
||
|
def write_continue_generation(g, use_else)
|
||
|
test = Routing.test_condition(g.hash_value(key, true, default), condition || true)
|
||
|
check = (use_else && condition.nil? && default) ? [:else] : [use_else ? :elsif : :if, test]
|
||
|
|
||
|
g.send(*check) do |gp|
|
||
|
gp.expire_for_keys(key) unless gp.after.empty?
|
||
|
add_segments_to(gp) {|gpp| gpp.continue}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def add_segments_to(g)
|
||
|
g.add_segment(%(\#{CGI.escape(#{g.hash_value(key, true, default)})})) {|gp| yield gp}
|
||
|
end
|
||
|
|
||
|
def recognition_check(g)
|
||
|
test_type = [true, nil].include?(condition) ? :presence : :constraint
|
||
|
|
||
|
prefix = condition.is_a?(Regexp) ? "#{g.next_segment(true)} && " : ''
|
||
|
check = prefix + Routing.test_condition(g.next_segment(true), condition || true)
|
||
|
|
||
|
g.if(check) {|gp| yield gp, test_type}
|
||
|
end
|
||
|
|
||
|
def write_recognition(g)
|
||
|
test_type = nil
|
||
|
recognition_check(g) do |gp, test_type|
|
||
|
assign_result(gp) {|gpp| gpp.continue}
|
||
|
end
|
||
|
|
||
|
if optional? && g.after.all? {|c| c.optional?}
|
||
|
call = (test_type == :presence) ? [:else] : [:elsif, "! #{g.next_segment(true)}"]
|
||
|
|
||
|
g.send(*call) do |gp|
|
||
|
assign_default(gp)
|
||
|
gp.after.each {|c| c.assign_default(gp)}
|
||
|
gp.finish(false)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def assign_result(g, with_default = false)
|
||
|
g.result key, "CGI.unescape(#{g.next_segment(true, with_default ? default : nil)})"
|
||
|
g.move_forward {|gp| yield gp}
|
||
|
end
|
||
|
|
||
|
def assign_default(g)
|
||
|
g.constant_result key, default unless default.nil?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class ControllerComponent < DynamicComponent #:nodoc:
|
||
|
def key() :controller end
|
||
|
|
||
|
def add_segments_to(g)
|
||
|
g.add_segment(%(\#{#{g.hash_value(key, true, default)}})) {|gp| yield gp}
|
||
|
end
|
||
|
|
||
|
def recognition_check(g)
|
||
|
g << "controller_result = ::ActionController::Routing::ControllerComponent.traverse_to_controller(#{g.path_name}, #{g.index_name})"
|
||
|
g.if('controller_result') do |gp|
|
||
|
gp << 'controller_value, segments_to_controller = controller_result'
|
||
|
if condition
|
||
|
gp << "controller_path = #{gp.path_name}[#{gp.index_name},segments_to_controller].join('/')"
|
||
|
gp.if(Routing.test_condition("controller_path", condition)) do |gpp|
|
||
|
gpp.move_forward('segments_to_controller') {|gppp| yield gppp, :constraint}
|
||
|
end
|
||
|
else
|
||
|
gp.move_forward('segments_to_controller') {|gpp| yield gpp, :constraint}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def assign_result(g)
|
||
|
g.result key, 'controller_value'
|
||
|
yield g
|
||
|
end
|
||
|
|
||
|
def assign_default(g)
|
||
|
ControllerComponent.assign_controller(g, default)
|
||
|
end
|
||
|
|
||
|
class << self
|
||
|
def assign_controller(g, controller)
|
||
|
expr = "::#{controller.split('/').collect {|c| c.camelize}.join('::')}Controller"
|
||
|
g.result :controller, expr, true
|
||
|
end
|
||
|
|
||
|
def traverse_to_controller(segments, start_at = 0)
|
||
|
mod = ::Object
|
||
|
length = segments.length
|
||
|
index = start_at
|
||
|
mod_name = controller_name = segment = nil
|
||
|
while index < length
|
||
|
return nil unless /\A[A-Za-z][A-Za-z\d_]*\Z/ =~ (segment = segments[index])
|
||
|
index += 1
|
||
|
|
||
|
mod_name = segment.camelize
|
||
|
controller_name = "#{mod_name}Controller"
|
||
|
path_suffix = File.join(segments[start_at..(index - 1)])
|
||
|
next_mod = nil
|
||
|
|
||
|
# If the controller is already present, or if we load it, return it.
|
||
|
if mod.const_defined?(controller_name) || attempt_load(mod, controller_name, path_suffix + "_controller") == :defined
|
||
|
controller = mod.const_get(controller_name)
|
||
|
return nil unless controller.is_a?(Class) && controller.ancestors.include?(ActionController::Base) # it's not really a controller?
|
||
|
return [controller, (index - start_at)]
|
||
|
end
|
||
|
|
||
|
# No controller? Look for the module
|
||
|
if mod.const_defined? mod_name
|
||
|
next_mod = mod.send(:const_get, mod_name)
|
||
|
next_mod = nil unless next_mod.is_a?(Module)
|
||
|
else
|
||
|
# Try to load a file that defines the module we want.
|
||
|
case attempt_load(mod, mod_name, path_suffix)
|
||
|
when :defined then next_mod = mod.const_get mod_name
|
||
|
when :dir then # We didn't find a file, but there's a dir.
|
||
|
next_mod = Module.new # So create a module for the directory
|
||
|
mod.send :const_set, mod_name, next_mod
|
||
|
else
|
||
|
return nil
|
||
|
end
|
||
|
end
|
||
|
mod = next_mod
|
||
|
|
||
|
return nil unless mod && mod.is_a?(Module)
|
||
|
end
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
protected
|
||
|
def safe_load_paths #:nodoc:
|
||
|
if defined?(RAILS_ROOT)
|
||
|
$LOAD_PATH.select do |base|
|
||
|
base = File.expand_path(base)
|
||
|
extended_root = File.expand_path(RAILS_ROOT)
|
||
|
# Exclude all paths that are not nested within app, lib, or components.
|
||
|
base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
|
||
|
end
|
||
|
else
|
||
|
$LOAD_PATH
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def attempt_load(mod, const_name, path)
|
||
|
has_dir = false
|
||
|
safe_load_paths.each do |load_path|
|
||
|
full_path = File.join(load_path, path)
|
||
|
file_path = full_path + '.rb'
|
||
|
if File.file?(file_path) # Found a .rb file? Load it up
|
||
|
require_dependency(file_path)
|
||
|
return :defined if mod.const_defined? const_name
|
||
|
else
|
||
|
has_dir ||= File.directory?(full_path)
|
||
|
end
|
||
|
end
|
||
|
return (has_dir ? :dir : nil)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class PathComponent < DynamicComponent #:nodoc:
|
||
|
def optional?() true end
|
||
|
def default() [] end
|
||
|
def condition() nil end
|
||
|
|
||
|
def default=(value)
|
||
|
raise RoutingError, "All path components have an implicit default of []" unless value == []
|
||
|
end
|
||
|
|
||
|
def write_generation(g)
|
||
|
raise RoutingError, 'Path components must occur last' unless g.after.empty?
|
||
|
g.if("#{g.hash_value(key, true)} && ! #{g.hash_value(key, true)}.empty?") do
|
||
|
g << "#{g.hash_value(key, true)} = #{g.hash_value(key, true)}.join('/') unless #{g.hash_value(key, true)}.is_a?(String)"
|
||
|
g.add_segment("\#{CGI.escape_skipping_slashes(#{g.hash_value(key, true)})}") {|gp| gp.finish }
|
||
|
end
|
||
|
g.else { g.finish }
|
||
|
end
|
||
|
|
||
|
def write_recognition(g)
|
||
|
raise RoutingError, "Path components must occur last" unless g.after.empty?
|
||
|
|
||
|
start = g.index_name
|
||
|
start = "(#{start})" unless /^\w+$/ =~ start
|
||
|
|
||
|
value_expr = "#{g.path_name}[#{start}..-1] || []"
|
||
|
g.result key, "ActionController::Routing::PathComponent::Result.new_escaped(#{value_expr})"
|
||
|
g.finish(false)
|
||
|
end
|
||
|
|
||
|
class Result < ::Array #:nodoc:
|
||
|
def to_s() join '/' end
|
||
|
def self.new_escaped(strings)
|
||
|
new strings.collect {|str| CGI.unescape str}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Route #:nodoc:
|
||
|
attr_accessor :components, :known
|
||
|
attr_reader :path, :options, :keys, :defaults
|
||
|
|
||
|
def initialize(path, options = {})
|
||
|
@path, @options = path, options
|
||
|
|
||
|
initialize_components path
|
||
|
defaults, conditions = initialize_hashes options.dup
|
||
|
@defaults = defaults.dup
|
||
|
configure_components(defaults, conditions)
|
||
|
add_default_requirements
|
||
|
initialize_keys
|
||
|
end
|
||
|
|
||
|
def inspect
|
||
|
"<#{self.class} #{path.inspect}, #{options.inspect[1..-1]}>"
|
||
|
end
|
||
|
|
||
|
def write_generation(generator = CodeGeneration::GenerationGenerator.new)
|
||
|
generator.before, generator.current, generator.after = [], components.first, (components[1..-1] || [])
|
||
|
|
||
|
if known.empty? then generator.go
|
||
|
else
|
||
|
# Alter the conditions to allow :action => 'index' to also catch :action => nil
|
||
|
altered_known = known.collect do |k, v|
|
||
|
if k == :action && v== 'index' then [k, [nil, 'index']]
|
||
|
else [k, v]
|
||
|
end
|
||
|
end
|
||
|
generator.if(generator.check_conditions(altered_known)) {|gp| gp.go }
|
||
|
end
|
||
|
|
||
|
generator
|
||
|
end
|
||
|
|
||
|
def write_recognition(generator = CodeGeneration::RecognitionGenerator.new)
|
||
|
g = generator.dup
|
||
|
g.share_locals_with generator
|
||
|
g.before, g.current, g.after = [], components.first, (components[1..-1] || [])
|
||
|
|
||
|
known.each do |key, value|
|
||
|
if key == :controller then ControllerComponent.assign_controller(g, value)
|
||
|
else g.constant_result(key, value)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
g.go
|
||
|
|
||
|
generator
|
||
|
end
|
||
|
|
||
|
def initialize_keys
|
||
|
@keys = (components.collect {|c| c.key} + known.keys).compact
|
||
|
@keys.freeze
|
||
|
end
|
||
|
|
||
|
def extra_keys(options)
|
||
|
options.keys - @keys
|
||
|
end
|
||
|
|
||
|
def matches_controller?(controller)
|
||
|
if known[:controller] then known[:controller] == controller
|
||
|
else
|
||
|
c = components.find {|c| c.key == :controller}
|
||
|
return false unless c
|
||
|
return c.condition.nil? || eval(Routing.test_condition('controller', c.condition))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
protected
|
||
|
def initialize_components(path)
|
||
|
path = path.split('/') if path.is_a? String
|
||
|
path.shift if path.first.blank?
|
||
|
self.components = path.collect {|str| Component.new str}
|
||
|
end
|
||
|
|
||
|
def initialize_hashes(options)
|
||
|
path_keys = components.collect {|c| c.key }.compact
|
||
|
self.known = {}
|
||
|
defaults = options.delete(:defaults) || {}
|
||
|
conditions = options.delete(:require) || {}
|
||
|
conditions.update(options.delete(:requirements) || {})
|
||
|
|
||
|
options.each do |k, v|
|
||
|
if path_keys.include?(k) then (v.is_a?(Regexp) ? conditions : defaults)[k] = v
|
||
|
else known[k] = v
|
||
|
end
|
||
|
end
|
||
|
[defaults, conditions]
|
||
|
end
|
||
|
|
||
|
def configure_components(defaults, conditions)
|
||
|
components.each do |component|
|
||
|
if defaults.key?(component.key) then component.default = defaults[component.key]
|
||
|
elsif component.key == :action then component.default = 'index'
|
||
|
elsif component.key == :id then component.default = nil
|
||
|
end
|
||
|
|
||
|
component.condition = conditions[component.key] if conditions.key?(component.key)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def add_default_requirements
|
||
|
component_keys = components.collect {|c| c.key}
|
||
|
known[:action] ||= 'index' unless component_keys.include? :action
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class RouteSet #:nodoc:
|
||
|
attr_reader :routes, :categories, :controller_to_selector
|
||
|
def initialize
|
||
|
@routes = []
|
||
|
@generation_methods = Hash.new(:generate_default_path)
|
||
|
end
|
||
|
|
||
|
def generate(options, request_or_recall_hash = {})
|
||
|
recall = request_or_recall_hash.is_a?(Hash) ? request_or_recall_hash : request_or_recall_hash.symbolized_path_parameters
|
||
|
use_recall = true
|
||
|
|
||
|
controller = options[:controller]
|
||
|
options[:action] ||= 'index' if controller
|
||
|
recall_controller = recall[:controller]
|
||
|
if (recall_controller && recall_controller.include?(?/)) || (controller && controller.include?(?/))
|
||
|
recall = {} if controller && controller[0] == ?/
|
||
|
options[:controller] = Routing.controller_relative_to(controller, recall_controller)
|
||
|
end
|
||
|
options = recall.dup if options.empty? # XXX move to url_rewriter?
|
||
|
|
||
|
keys_to_delete = []
|
||
|
Routing.treat_hash(options, keys_to_delete)
|
||
|
|
||
|
merged = recall.merge(options)
|
||
|
keys_to_delete.each {|key| merged.delete key}
|
||
|
expire_on = Routing.expiry_hash(options, recall)
|
||
|
|
||
|
generate_path(merged, options, expire_on)
|
||
|
end
|
||
|
|
||
|
def generate_path(merged, options, expire_on)
|
||
|
send @generation_methods[merged[:controller]], merged, options, expire_on
|
||
|
end
|
||
|
def generate_default_path(*args)
|
||
|
write_generation
|
||
|
generate_default_path(*args)
|
||
|
end
|
||
|
|
||
|
def write_generation
|
||
|
method_sources = []
|
||
|
@generation_methods = Hash.new(:generate_default_path)
|
||
|
categorize_routes.each do |controller, routes|
|
||
|
next unless routes.length < @routes.length
|
||
|
|
||
|
ivar = controller.gsub('/', '__')
|
||
|
method_name = "generate_path_for_#{ivar}".to_sym
|
||
|
instance_variable_set "@#{ivar}", routes
|
||
|
code = generation_code_for(ivar, method_name).to_s
|
||
|
method_sources << code
|
||
|
|
||
|
filename = "generated_code/routing/generation_for_controller_#{controller}.rb"
|
||
|
eval(code, nil, filename)
|
||
|
|
||
|
@generation_methods[controller.to_s] = method_name
|
||
|
@generation_methods[controller.to_sym] = method_name
|
||
|
end
|
||
|
|
||
|
code = generation_code_for('routes', 'generate_default_path').to_s
|
||
|
eval(code, nil, 'generated_code/routing/generation.rb')
|
||
|
|
||
|
return (method_sources << code)
|
||
|
end
|
||
|
|
||
|
def recognize(request)
|
||
|
string_path = request.path
|
||
|
string_path.chomp! if string_path[0] == ?/
|
||
|
path = string_path.split '/'
|
||
|
path.shift
|
||
|
|
||
|
hash = recognize_path(path)
|
||
|
return recognition_failed(request) unless hash && hash['controller']
|
||
|
|
||
|
controller = hash['controller']
|
||
|
hash['controller'] = controller.controller_path
|
||
|
request.path_parameters = hash
|
||
|
controller.new
|
||
|
end
|
||
|
alias :recognize! :recognize
|
||
|
|
||
|
def recognition_failed(request)
|
||
|
raise ActionController::RoutingError, "Recognition failed for #{request.path.inspect}"
|
||
|
end
|
||
|
|
||
|
def write_recognition
|
||
|
g = generator = CodeGeneration::RecognitionGenerator.new
|
||
|
g.finish_statement = Proc.new {|hash_expr| "return #{hash_expr}"}
|
||
|
|
||
|
g.def "self.recognize_path(path)" do
|
||
|
each do |route|
|
||
|
g << 'index = 0'
|
||
|
route.write_recognition(g)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
eval g.to_s, nil, 'generated/routing/recognition.rb'
|
||
|
return g.to_s
|
||
|
end
|
||
|
|
||
|
def generation_code_for(ivar = 'routes', method_name = nil)
|
||
|
routes = instance_variable_get('@' + ivar)
|
||
|
key_ivar = "@keys_for_#{ivar}"
|
||
|
instance_variable_set(key_ivar, routes.collect {|route| route.keys})
|
||
|
|
||
|
g = generator = CodeGeneration::GenerationGenerator.new
|
||
|
g.def "self.#{method_name}(merged, options, expire_on)" do
|
||
|
g << 'unused_count = options.length + 1'
|
||
|
g << "unused_keys = keys = options.keys"
|
||
|
g << 'path = nil'
|
||
|
|
||
|
routes.each_with_index do |route, index|
|
||
|
g << "new_unused_keys = keys - #{key_ivar}[#{index}]"
|
||
|
g << 'new_path = ('
|
||
|
g.source.indent do
|
||
|
if index.zero?
|
||
|
g << "new_unused_count = new_unused_keys.length"
|
||
|
g << "hash = merged; not_expired = true"
|
||
|
route.write_generation(g.dup)
|
||
|
else
|
||
|
g.if "(new_unused_count = new_unused_keys.length) < unused_count" do |gp|
|
||
|
gp << "hash = merged; not_expired = true"
|
||
|
route.write_generation(gp)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
g.source.lines.last << ' )' # Add the closing brace to the end line
|
||
|
g.if 'new_path' do
|
||
|
g << 'return new_path, [] if new_unused_count.zero?'
|
||
|
g << 'path = new_path; unused_keys = new_unused_keys; unused_count = new_unused_count'
|
||
|
end
|
||
|
end
|
||
|
|
||
|
g << "raise RoutingError, \"No url can be generated for the hash \#{options.inspect}\" unless path"
|
||
|
g << "return path, unused_keys"
|
||
|
end
|
||
|
|
||
|
return g
|
||
|
end
|
||
|
|
||
|
def categorize_routes
|
||
|
@categorized_routes = by_controller = Hash.new(self)
|
||
|
|
||
|
known_controllers.each do |name|
|
||
|
set = by_controller[name] = []
|
||
|
each do |route|
|
||
|
set << route if route.matches_controller? name
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@categorized_routes
|
||
|
end
|
||
|
|
||
|
def known_controllers
|
||
|
@routes.inject([]) do |known, route|
|
||
|
if (controller = route.known[:controller])
|
||
|
if controller.is_a?(Regexp)
|
||
|
known << controller.source.scan(%r{[\w\d/]+}).select {|word| controller =~ word}
|
||
|
else known << controller
|
||
|
end
|
||
|
end
|
||
|
known
|
||
|
end.uniq
|
||
|
end
|
||
|
|
||
|
def reload
|
||
|
NamedRoutes.clear
|
||
|
|
||
|
if defined?(RAILS_ROOT) then load(File.join(RAILS_ROOT, 'config', 'routes.rb'))
|
||
|
else connect(':controller/:action/:id', :action => 'index', :id => nil)
|
||
|
end
|
||
|
|
||
|
NamedRoutes.install
|
||
|
end
|
||
|
|
||
|
def connect(*args)
|
||
|
new_route = Route.new(*args)
|
||
|
@routes << new_route
|
||
|
return new_route
|
||
|
end
|
||
|
|
||
|
def draw
|
||
|
old_routes = @routes
|
||
|
@routes = []
|
||
|
|
||
|
begin yield self
|
||
|
rescue
|
||
|
@routes = old_routes
|
||
|
raise
|
||
|
end
|
||
|
write_generation
|
||
|
write_recognition
|
||
|
end
|
||
|
|
||
|
def empty?() @routes.empty? end
|
||
|
|
||
|
def each(&block) @routes.each(&block) end
|
||
|
|
||
|
# Defines a new named route with the provided name and arguments.
|
||
|
# This method need only be used when you wish to use a name that a RouteSet instance
|
||
|
# method exists for, such as categories.
|
||
|
#
|
||
|
# For example, map.categories '/categories', :controller => 'categories' will not work
|
||
|
# due to RouteSet#categories.
|
||
|
def named_route(name, path, hash = {})
|
||
|
route = connect(path, hash)
|
||
|
NamedRoutes.name_route(route, name)
|
||
|
route
|
||
|
end
|
||
|
|
||
|
def method_missing(name, *args)
|
||
|
(1..2).include?(args.length) ? named_route(name, *args) : super(name, *args)
|
||
|
end
|
||
|
|
||
|
def extra_keys(options, recall = {})
|
||
|
generate(options.dup, recall).last
|
||
|
end
|
||
|
end
|
||
|
|
||
|
module NamedRoutes #:nodoc:
|
||
|
Helpers = []
|
||
|
class << self
|
||
|
def clear() Helpers.clear end
|
||
|
|
||
|
def hash_access_name(name)
|
||
|
"hash_for_#{name}_url"
|
||
|
end
|
||
|
|
||
|
def url_helper_name(name)
|
||
|
"#{name}_url"
|
||
|
end
|
||
|
|
||
|
def known_hash_for_route(route)
|
||
|
hash = route.known.symbolize_keys
|
||
|
route.defaults.each do |key, value|
|
||
|
hash[key.to_sym] ||= value if value
|
||
|
end
|
||
|
hash[:controller] = "/#{hash[:controller]}"
|
||
|
|
||
|
hash
|
||
|
end
|
||
|
|
||
|
def define_hash_access_method(route, name)
|
||
|
hash = known_hash_for_route(route)
|
||
|
define_method(hash_access_name(name)) do |*args|
|
||
|
args.first ? hash.merge(args.first) : hash
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def name_route(route, name)
|
||
|
define_hash_access_method(route, name)
|
||
|
|
||
|
module_eval(%{def #{url_helper_name name}(options = {})
|
||
|
url_for(#{hash_access_name(name)}.merge(options))
|
||
|
end}, "generated/routing/named_routes/#{name}.rb")
|
||
|
|
||
|
protected url_helper_name(name), hash_access_name(name)
|
||
|
|
||
|
Helpers << url_helper_name(name).to_sym
|
||
|
Helpers << hash_access_name(name).to_sym
|
||
|
Helpers.uniq!
|
||
|
end
|
||
|
|
||
|
def install(cls = ActionController::Base)
|
||
|
cls.send :include, self
|
||
|
if cls.respond_to? :helper_method
|
||
|
Helpers.each do |helper_name|
|
||
|
cls.send :helper_method, helper_name
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
Routes = RouteSet.new
|
||
|
end
|
||
|
end
|