diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f44c10f533ae0..b301fc8aae3aa 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -231,7 +231,11 @@ def mount(app, options = nil) raise "A rack application must be specified" unless path + name = (options[:as] ||= get_named_route_name(app)) + match(path, options.merge(:to => app, :anchor => false)) + + define_generate_prefix(app, name) self end @@ -239,6 +243,29 @@ def default_url_options=(options) @set.default_url_options = options end alias_method :default_url_options, :default_url_options= + + private + def get_named_route_name(app) + return unless app.respond_to?(:routes) + class_name = app.class.is_a?(Class) ? app.name : app.class.name + ActiveSupport::Inflector.underscore(class_name).gsub("/", "_") + end + + def define_generate_prefix(app, name) + return unless app.respond_to?(:routes) + + _route = @set.named_routes.routes[name.to_sym] + _router = @set + app.routes.class_eval do + define_method :_generate_prefix do |options| + keys = _route.segment_keys + ActionDispatch::Routing::RouteSet::RESERVED_OPTIONS + prefix_options = options.reject { |k, v| !(keys).include?(k) } + # we must actually delete prefix segment keys to avoid passing them to next url_for + _route.segment_keys.each { |k| options.delete(k) } + _router.url_helpers.send("#{name}_path", prefix_options) + end + end + end end module HttpHelpers diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 7c0e806421320..e9e166b9c10d0 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -198,6 +198,7 @@ def define_url_helper(route, name, kind, options) options[:_positional_keys] = route.segment_keys end + options[:routes] = _original_routes url_for(options) end end @@ -310,10 +311,9 @@ def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil end class Generator #:nodoc: - attr_reader :options, :recall, :set, :script_name, :named_route + attr_reader :options, :recall, :set, :named_route def initialize(options, recall, set, extras = false) - @script_name = options.delete(:script_name) @named_route = options.delete(:use_route) @options = options.dup @recall = recall.dup @@ -409,7 +409,7 @@ def generate return [path, params.keys] if @extras path << "?#{params.to_query}" if params.any? - "#{script_name}#{path}" + path rescue Rack::Mount::RoutingError raise error end @@ -471,7 +471,6 @@ def url_for(options) rewritten_url = "" path_segments = options.delete(:_path_segments) - unless options[:only_path] rewritten_url << (options[:protocol] || "http") rewritten_url << "://" unless rewritten_url.match("://") @@ -483,9 +482,15 @@ def url_for(options) rewritten_url << ":#{options.delete(:port)}" if options.key?(:port) end + path = [options.delete(:script_name)] + if !options.delete(:skip_prefix) + path << _generate_prefix(options) + end + path_options = options.except(*RESERVED_OPTIONS) path_options = yield(path_options) if block_given? - path = generate(path_options, path_segments || {}) + path << generate(path_options, path_segments || {}) + path = path.compact.join '' # ROUTES TODO: This can be called directly, so script_name should probably be set in the routes rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index 980abd44df095..73210d6dd63e6 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -128,7 +128,13 @@ def url_for(options = nil) when String options when nil, Hash - _routes.url_for(url_options.merge((options || {}).symbolize_keys)) + _original_routes = options ? options.delete(:routes) : nil + + if respond_to?(:env) && env && _original_routes.equal?(env["action_dispatch.routes"]) + options[:skip_prefix] = true + end + + (_original_routes || _routes).url_for(url_options.merge((options || {}).symbolize_keys)) else polymorphic_url(options) end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index fc85b01394a84..6d413b9a396b9 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -251,7 +251,7 @@ def test_optimised_named_route_with_host map.pages 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' end x = setup_for_named_route - x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages).once + x.expects(:url_for).with(:host => 'foo.com', :only_path => false, :controller => 'content', :action => 'show_page', :use_route => :pages, :router => rs).once x.send(:pages_url) end diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb new file mode 100644 index 0000000000000..d10f43bfd8ae3 --- /dev/null +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -0,0 +1,89 @@ +require 'abstract_unit' + +module TestGenerationPrefix + class WithMountedEngine < ActionDispatch::IntegrationTest + class BlogEngine + def self.routes + @routes ||= begin + routes = ActionDispatch::Routing::RouteSet.new + routes.draw do + match "/posts/:id", :to => "inside_engine_generating#index", :as => :post + end + + routes + end + end + + def self.call(env) + env['action_dispatch.routes'] = routes + routes.call(env) + end + end + + class RailsApplication + def self.routes + @routes ||= begin + routes = ActionDispatch::Routing::RouteSet.new + routes.draw do + scope "/:omg", :omg => "awesome" do + mount BlogEngine => "/blog" + end + match "/generate", :to => "outside_engine_generating#index" + end + + routes + end + end + + def self.call(env) + env['action_dispatch.routes'] = routes + routes.call(env) + end + end + + class ::InsideEngineGeneratingController < ActionController::Base + include BlogEngine.routes.url_helpers + def index + render :text => post_path(:id => params[:id]) + end + end + + class ::OutsideEngineGeneratingController < ActionController::Base + include RailsApplication.routes.url_helpers + include BlogEngine.routes.url_helpers + def index + render :text => post_path(:id => 1) + end + end + + RailsApplication.routes # force draw + include BlogEngine.routes.url_helpers + + test "generating URL with prefix" do + assert_equal "/awesome/blog/posts/1", post_path(:id => 1) + end + + test "use SCRIPT_NAME inside the engine" do + env = Rack::MockRequest.env_for("/posts/1") + env["SCRIPT_NAME"] = "/pure-awesomness/blog" + response = ActionDispatch::Response.new(*BlogEngine.call(env)) + assert_equal "/pure-awesomness/blog/posts/1", response.body + end + + test "prepend prefix outside the engine" do + env = Rack::MockRequest.env_for("/generate") + env["SCRIPT_NAME"] = "/something" # it could be set by passenger + response = ActionDispatch::Response.new(*RailsApplication.call(env)) + assert_equal "/something/awesome/blog/posts/1", response.body + end + + test "generating urls with options for both prefix and named_route" do + assert_equal "/pure-awesomness/blog/posts/3", post_path(:id => 3, :omg => "pure-awesomness") + end + + test "generating urls with url_for should prepend the prefix" do + path = BlogEngine.routes.url_for(:omg => 'omg', :controller => "inside_engine_generating", :action => "index", :id => 1, :only_path => true) + assert_equal "/omg/blog/posts/1", path + end + end +end