diff options
Diffstat (limited to 'actionpack/lib/action_controller/routing/route_set.rb')
-rw-r--r-- | actionpack/lib/action_controller/routing/route_set.rb | 485 |
1 files changed, 0 insertions, 485 deletions
diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb deleted file mode 100644 index 25fdbf480e..0000000000 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ /dev/null @@ -1,485 +0,0 @@ -module ActionController - module Routing - class RouteSet #:nodoc: - # Mapper instances are used to build routes. The object passed to the draw - # block in config/routes.rb is a Mapper instance. - # - # Mapper instances have relatively few instance methods, in order to avoid - # clashes with named routes. - class Mapper #:doc: - include ActionController::Resources - - def initialize(set) #:nodoc: - @set = set - end - - # Create an unnamed route with the provided +path+ and +options+. See - # ActionController::Routing for an introduction to routes. - def connect(path, options = {}) - @set.add_route(path, options) - end - - # Creates a named route called "root" for matching the root level request. - def root(options = {}) - if options.is_a?(Symbol) - if source_route = @set.named_routes.routes[options] - options = source_route.defaults.merge({ :conditions => source_route.conditions }) - end - end - named_route("root", '', options) - end - - def named_route(name, path, options = {}) #:nodoc: - @set.add_named_route(name, path, options) - end - - # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. - # Example: - # - # map.namespace(:admin) do |admin| - # admin.resources :products, - # :has_many => [ :tags, :images, :variants ] - # end - # - # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. - # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for - # Admin::TagsController. - def namespace(name, options = {}, &block) - if options[:namespace] - with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) - else - with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) - end - end - - def method_missing(route_name, *args, &proc) #:nodoc: - super unless args.length >= 1 && proc.nil? - @set.add_named_route(route_name, *args) - end - end - - # A NamedRouteCollection instance is a collection of named routes, and also - # maintains an anonymous module that can be used to install helpers for the - # named routes. - class NamedRouteCollection #:nodoc: - include Enumerable - include ActionController::Routing::Optimisation - attr_reader :routes, :helpers - - def initialize - clear! - end - - def clear! - @routes = {} - @helpers = [] - - @module ||= Module.new - @module.instance_methods.each do |selector| - @module.class_eval { remove_method selector } - end - end - - def add(name, route) - routes[name.to_sym] = route - define_named_route_methods(name, route) - end - - def get(name) - routes[name.to_sym] - end - - alias []= add - alias [] get - alias clear clear! - - def each - routes.each { |name, route| yield name, route } - self - end - - def names - routes.keys - end - - def length - routes.length - end - - def reset! - old_routes = routes.dup - clear! - old_routes.each do |name, route| - add(name, route) - end - end - - def install(destinations = [ActionController::Base, ActionView::Base], regenerate = false) - reset! if regenerate - Array(destinations).each do |dest| - dest.__send__(:include, @module) - end - end - - private - def url_helper_name(name, kind = :url) - :"#{name}_#{kind}" - end - - def hash_access_name(name, kind = :url) - :"hash_for_#{name}_#{kind}" - end - - def define_named_route_methods(name, route) - {:url => {:only_path => false}, :path => {:only_path => true}}.each do |kind, opts| - hash = route.defaults.merge(:use_route => name).merge(opts) - define_hash_access route, name, kind, hash - define_url_helper route, name, kind, hash - end - end - - def named_helper_module_eval(code, *args) - @module.module_eval(code, *args) - end - - def define_hash_access(route, name, kind, options) - selector = hash_access_name(name, kind) - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(options = nil) # def hash_for_users_url(options = nil) - options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false} - end # end - protected :#{selector} # protected :hash_for_users_url - end_eval - helpers << selector - end - - def define_url_helper(route, name, kind, options) - selector = url_helper_name(name, kind) - # The segment keys used for positional parameters - - hash_access_method = hash_access_name(name, kind) - - # allow ordered parameters to be associated with corresponding - # dynamic segments, so you can do - # - # foo_url(bar, baz, bang) - # - # instead of - # - # foo_url(:bar => bar, :baz => baz, :bang => bang) - # - # Also allow options hash, so you can do - # - # foo_url(bar, baz, bang, :sort_by => 'baz') - # - named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(*args) # def users_url(*args) - # - #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)} - # - opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first - args.first || {} # args.first || {} - else # else - options = args.extract_options! # options = args.extract_options! - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)| - h[k] = v # h[k] = v - h # h - end # end - options.merge(args) # options.merge(args) - end # end - # - url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) - # - end # end - #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL. - def formatted_#{selector}(*args) # def formatted_users_url(*args) - ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn( - "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " + - "Please pass format to the standard " + # "Please pass format to the standard " + - "#{selector} method instead.", caller) # "users_url method instead.", caller) - #{selector}(*args) # users_url(*args) - end # end - protected :#{selector} # protected :users_url - end_eval - helpers << selector - end - end - - attr_accessor :routes, :named_routes, :configuration_files - - def initialize - self.configuration_files = [] - - self.routes = [] - self.named_routes = NamedRouteCollection.new - - clear! - end - - # Subclasses and plugins may override this method to specify a different - # RouteBuilder instance, so that other route DSL's can be created. - def builder - @builder ||= RouteBuilder.new - end - - def draw - clear! - yield Mapper.new(self) - install_helpers - end - - def clear! - routes.clear - named_routes.clear - - @combined_regexp = nil - @routes_by_controller = nil - - # This will force routing/recognition_optimization.rb - # to refresh optimisations. - clear_recognize_optimized! - end - - 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 - - def empty? - routes.empty? - end - - def add_configuration_file(path) - self.configuration_files << path - end - - # Deprecated accessor - def configuration_file=(path) - add_configuration_file(path) - end - - # Deprecated accessor - def configuration_file - configuration_files - end - - def load! - Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones - load_routes! - end - - # reload! will always force a reload whereas load checks the timestamp first - alias reload! load! - - def reload - if configuration_files.any? && @routes_last_modified - if routes_changed_at == @routes_last_modified - return # routes didn't change, don't reload - else - @routes_last_modified = routes_changed_at - end - end - - load! - end - - def load_routes! - if configuration_files.any? - configuration_files.each { |config| load(config) } - @routes_last_modified = routes_changed_at - else - draw do |map| - map.connect ":controller/:action/:id" - end - end - end - - def routes_changed_at - routes_changed_at = nil - - configuration_files.each do |config| - config_changed_at = File.stat(config).mtime - - if routes_changed_at.nil? || config_changed_at > routes_changed_at - routes_changed_at = config_changed_at - end - end - - routes_changed_at - end - - def add_route(path, options = {}) - options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) } - route = builder.build(path, options) - routes << route - route - end - - 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 - - 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 - - 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 - - # Generate the path indicated by the arguments, and return an array of - # the keys that were not used to generate it. - def extra_keys(options, recall={}) - generate_extras(options, recall).last - end - - def generate_extras(options, recall={}) - generate(options, recall, :generate_extras) - end - - 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.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] - - routes.each_with_index do |route, index| - results = route.__send__(method, options, merged, expire_on) - if results && (!results.is_a?(Array) || results.first) - return results - end - end - end - - raise RoutingError, "No route matches #{options.inspect}" - end - - # try to give a helpful error message when named route generation fails - 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 - - def call(env) - request = ActionDispatch::Request.new(env) - app = Routing::Routes.recognize(request) - app.action(request.parameters[:action] || 'index').call(env) - end - - def recognize(request) - params = recognize_path(request.path, extract_request_environment(request)) - request.path_parameters = params.with_indifferent_access - "#{params[:controller].to_s.camelize}Controller".constantize - end - - def recognize_path(path, environment={}) - raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path." - end - - 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 - - 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][1] - end - - def routes_for_controller_and_action_and_keys(controller, action, keys) - routes.select do |route| - route.matches_controller_and_action? controller, action - end - end - - # Subclasses and plugins may override this method to extract further attributes - # from the request, for use by route conditions and such. - def extract_request_environment(request) - { :method => request.method } - end - end - end -end |