diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index a2570cb877f319af97ff80d3d069732a2491020a..f3aa09f4a7e8247f657a8ed36edad6551631def1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -261,7 +261,11 @@ def mount(app, options = nil) raise "A rack application must be specified" unless path + options[:as] ||= app_name(app) + match(path, options.merge(:to => app, :anchor => false, :format => false)) + + define_generate_prefix(app, options[:as]) self end @@ -269,6 +273,29 @@ def default_url_options=(options) @set.default_url_options = options end alias_method :default_url_options, :default_url_options= + + private + def app_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 b531cc1a8e3d39c932bc0bcf5123cd93dc68aced..cb0373951f2e7af21f92e065c87221f656454cf7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -303,10 +303,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 @@ -401,7 +400,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_routing_error end @@ -453,7 +452,11 @@ def generate(options, recall = {}, extras = false) Generator.new(options, recall, self, extras).generate end - RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash] + RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash, :script_name, :skip_prefix, :routes] + + def _generate_prefix(options = {}) + nil + end def url_for(options) finalize! @@ -464,7 +467,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("://") @@ -476,9 +478,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 28ec830fe8a1b0e955c0fa979d97ebacdb7950b1..f73067688c16e7f0a48ed17407eced37041c04c7 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((options || {}).reverse_merge!(url_options).symbolize_keys) + routes = (options ? options.delete(:routes) : nil) || _routes + + if respond_to?(:env) && env && routes.equal?(env["action_dispatch.routes"]) + options[:skip_prefix] = true + end + + routes.url_for((options || {}).reverse_merge!(url_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 a8c74a60643c995551bd8352ea817cafbc6e680a..1f14607c3149ce7df1295ef62eda79d0c309142a 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 0000000000000000000000000000000000000000..95d5c1c3662cb94f70142191d48ed8c81c7c0572 --- /dev/null +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -0,0 +1,102 @@ +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 BlogEngine.routes.url_helpers + def index + render :text => post_path(:id => 1) + end + end + + class Foo + include ActionDispatch::Routing::UrlFor + include BlogEngine.routes.url_helpers + + def foo + post_path(42) + 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 + + test "generating urls from a regular class" do + assert_equal "/awesome/blog/posts/42", Foo.new.foo + end + end +end