Skip to content

Commit

Permalink
Fix handling SCRIPT_NAME from within mounted engine's
Browse files Browse the repository at this point in the history
When you mount your application at a sub-uri, for example /myapp, server
should set SCRIPT_NAME to /myapp. With such information, rails
application knows that it's mounted at /myapp path and it should generate
routes relative to such path.

Before this patch, rails handled SCRIPT_NAME correctly only for regular
apps, but it failed to do it for mounted engines. The solution was to
hardcode default_url_options[:script_name], which is not the best answer
- it will work only when application is mounted at fixed path.

This patch fixes the situation by respecting original value of
SCRIPT_NAME when generating application's routes from engine and the
other way round - when you generate engine's route from application.

This is done by using one of 2 pieces of information in env - current
SCRIPT_NAME or SCRIPT_NAME for a corresponding router. This is because
we have 2 cases to handle:

- generating engine's route from application: in this situation
  SCRIPT_NAME is basically SCRIPT_NAME set by the server and it
  indicates the place where application is mounted, so we can just pass
  it as :original_script_name in url_options. :original_script_name is
  used because if we use :script_name, router will ignore generating
  prefix for engine

- generating application's route from engine: in this situation we
  already lost information about the SCRIPT_NAME that server used. For
  example if application is mounted at /myapp and engine is mounted at
  /blog, at this point SCRIPT_NAME is equal /myapp/blog. Because of that
  we need to keep reference to /myapp SCRIPT_NAME by binding it to the
  current router. Later on we can extract it and use when generating url

Please not that starting from now you *should not* use
default_url_options[:script_name] explicitly if your server already
passes correct SCRIPT_NAME to rack env.

(closes #6933)
  • Loading branch information
drogus committed Aug 10, 2012
1 parent 5a0372f commit de55da0
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 41 deletions.
10 changes: 8 additions & 2 deletions actionpack/lib/action_controller/metal/url_for.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@ def url_options
:_recall => request.symbolized_path_parameters :_recall => request.symbolized_path_parameters
).freeze ).freeze


if _routes.equal?(env["action_dispatch.routes"]) if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
(original_script_name = env['SCRIPT_NAME'])
@_url_options.dup.tap do |options| @_url_options.dup.tap do |options|
options[:script_name] = request.script_name.dup if original_script_name
options[:original_script_name] = original_script_name
else
options[:script_name] = same_origin ? request.script_name.dup : script_name
end
options.freeze options.freeze
end end
else else
Expand Down
17 changes: 12 additions & 5 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -163,9 +163,9 @@ def length
private private


def define_named_route_methods(name, route) def define_named_route_methods(name, route)
define_url_helper route, :"#{name}_path", define_url_helper route, :"#{name}_path",
route.defaults.merge(:use_route => name, :only_path => true) route.defaults.merge(:use_route => name, :only_path => true)
define_url_helper route, :"#{name}_url", define_url_helper route, :"#{name}_url",
route.defaults.merge(:use_route => name, :only_path => false) route.defaults.merge(:use_route => name, :only_path => false)
end end


Expand Down Expand Up @@ -468,7 +468,7 @@ def current_controller
def use_recall_for(key) def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key]) if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if !named_route_exists? || segment_keys.include?(key) if !named_route_exists? || segment_keys.include?(key)
@options[key] = @recall.delete(key) @options[key] = @recall.delete(key)
end end
end end
end end
Expand Down Expand Up @@ -577,7 +577,8 @@ def generate(options, recall = {}, extras = false)
end end


RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length,
:trailing_slash, :anchor, :params, :only_path, :script_name] :trailing_slash, :anchor, :params, :only_path, :script_name,
:original_script_name]


def mounted? def mounted?
false false
Expand All @@ -597,7 +598,13 @@ def url_for(options)


user, password = extract_authentication(options) user, password = extract_authentication(options)
recall = options.delete(:_recall) recall = options.delete(:_recall)
script_name = options.delete(:script_name).presence || _generate_prefix(options)
original_script_name = options.delete(:original_script_name).presence
script_name = options.delete(:script_name).presence || _generate_prefix(options)

if script_name && original_script_name
script_name = original_script_name + script_name
end


path_options = options.except(*RESERVED_OPTIONS) path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given? path_options = yield(path_options) if block_given?
Expand Down
18 changes: 0 additions & 18 deletions actionpack/test/dispatch/prefix_generation_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -166,18 +166,6 @@ def setup
assert_equal "/generate", last_response.body assert_equal "/generate", last_response.body
end end


test "[ENGINE] generating application's url includes default_url_options[:script_name]" do
RailsApplication.routes.default_url_options = {:script_name => "/something"}
get "/pure-awesomeness/blog/url_to_application"
assert_equal "/something/generate", last_response.body
end

test "[ENGINE] generating application's url should give higher priority to default_url_options[:script_name]" do
RailsApplication.routes.default_url_options = {:script_name => "/something"}
get "/pure-awesomeness/blog/url_to_application", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/something/generate", last_response.body
end

test "[ENGINE] generating engine's url with polymorphic path" do test "[ENGINE] generating engine's url with polymorphic path" do
get "/pure-awesomeness/blog/polymorphic_path_for_engine" get "/pure-awesomeness/blog/polymorphic_path_for_engine"
assert_equal "/pure-awesomeness/blog/posts/1", last_response.body assert_equal "/pure-awesomeness/blog/posts/1", last_response.body
Expand All @@ -200,12 +188,6 @@ def setup
assert_equal "/something/awesome/blog/posts/1", last_response.body assert_equal "/something/awesome/blog/posts/1", last_response.body
end end


test "[APP] generating engine's route should give higher priority to default_url_options[:script_name]" do
RailsApplication.routes.default_url_options = {:script_name => "/something"}
get "/generate", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/something/awesome/blog/posts/1", last_response.body
end

test "[APP] generating engine's url with polymorphic path" do test "[APP] generating engine's url with polymorphic path" do
get "/polymorphic_path_for_engine" get "/polymorphic_path_for_engine"
assert_equal "/awesome/blog/posts/1", last_response.body assert_equal "/awesome/blog/posts/1", last_response.body
Expand Down
5 changes: 5 additions & 0 deletions railties/CHANGELOG.md
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ## ## Rails 4.0.0 (unreleased) ##


* Correctly handle SCRIPT_NAME when generating routes to engine in application
that's mounted at a sub-uri. With this behavior, you *should not* use
default_url_options[:script_name] to set proper application's mount point by
yourself. *Piotr Sarnacki*

* The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance * The migration generator will now produce AddXXXToYYY/RemoveXXXFromYYY migrations with references statements, for instance


rails g migration AddReferencesToProducts user:references supplier:references{polymorphic} rails g migration AddReferencesToProducts user:references supplier:references{polymorphic}
Expand Down
6 changes: 5 additions & 1 deletion railties/lib/rails/engine.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -494,7 +494,11 @@ def endpoint


# Define the Rack API for this engine. # Define the Rack API for this engine.
def call(env) def call(env)
app.call(env.merge!(env_config)) env.merge!(env_config)
if env['SCRIPT_NAME']
env.merge! "ROUTES_#{routes.object_id}_SCRIPT_NAME" => env['SCRIPT_NAME'].dup
end
app.call(env)
end end


# Defines additional Rack env configuration that is added on each call. # Defines additional Rack env configuration that is added on each call.
Expand Down
48 changes: 48 additions & 0 deletions railties/test/railties/engine_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1193,6 +1193,54 @@ def index
last_response.body.split("\n").map(&:strip) last_response.body.split("\n").map(&:strip)
end end


test "paths are properly generated when application is mounted at sub-path" do
@plugin.write "lib/bukkits.rb", <<-RUBY
module Bukkits
class Engine < ::Rails::Engine
isolate_namespace Bukkits
end
end
RUBY

app_file "app/controllers/bar_controller.rb", <<-RUBY
class BarController < ApplicationController
def index
render :text => bukkits.bukkit_path
end
end
RUBY

app_file "config/routes.rb", <<-RUBY
AppTemplate::Application.routes.draw do
get '/bar' => 'bar#index', :as => 'bar'
mount Bukkits::Engine => "/bukkits", :as => "bukkits"
end
RUBY

@plugin.write "config/routes.rb", <<-RUBY
Bukkits::Engine.routes.draw do
get '/bukkit' => 'bukkit#index'
end
RUBY


@plugin.write "app/controllers/bukkits/bukkit_controller.rb", <<-RUBY
class Bukkits::BukkitController < ActionController::Base
def index
render :text => main_app.bar_path
end
end
RUBY

boot_rails

get("/bukkits/bukkit", {}, {'SCRIPT_NAME' => '/foo'})
assert_equal '/foo/bar', last_response.body

get("/bar", {}, {'SCRIPT_NAME' => '/foo'})
assert_equal '/foo/bukkits/bukkit', last_response.body
end

private private
def app def app
Rails.application Rails.application
Expand Down
15 changes: 0 additions & 15 deletions railties/test/railties/mounted_engine_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -163,24 +163,14 @@ def app
end end
end end


def reset_script_name!
Rails.application.routes.default_url_options = {}
end

def script_name(script_name)
Rails.application.routes.default_url_options = {:script_name => script_name}
end

test "routes generation in engine and application" do test "routes generation in engine and application" do
# test generating engine's route from engine # test generating engine's route from engine
get "/john/blog/posts" get "/john/blog/posts"
assert_equal "/john/blog/posts/1", last_response.body assert_equal "/john/blog/posts/1", last_response.body


# test generating engine's route from engine with default_url_options # test generating engine's route from engine with default_url_options
script_name "/foo"
get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo" get "/john/blog/posts", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts/1", last_response.body assert_equal "/foo/john/blog/posts/1", last_response.body
reset_script_name!


# test generating engine's route from application # test generating engine's route from application
get "/engine_route" get "/engine_route"
Expand All @@ -193,14 +183,11 @@ def script_name(script_name)
assert_equal "/john/blog/posts", last_response.body assert_equal "/john/blog/posts", last_response.body


# test generating engine's route from application with default_url_options # test generating engine's route from application with default_url_options
script_name "/foo"
get "/engine_route", {}, 'SCRIPT_NAME' => "/foo" get "/engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/anonymous/blog/posts", last_response.body assert_equal "/foo/anonymous/blog/posts", last_response.body


script_name "/foo"
get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo" get "/url_for_engine_route", {}, 'SCRIPT_NAME' => "/foo"
assert_equal "/foo/john/blog/posts", last_response.body assert_equal "/foo/john/blog/posts", last_response.body
reset_script_name!


# test generating application's route from engine # test generating application's route from engine
get "/someone/blog/generate_application_route" get "/someone/blog/generate_application_route"
Expand All @@ -210,10 +197,8 @@ def script_name(script_name)
assert_equal "/", last_response.body assert_equal "/", last_response.body


# test generating application's route from engine with default_url_options # test generating application's route from engine with default_url_options
script_name "/foo"
get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo'
assert_equal "/foo/", last_response.body assert_equal "/foo/", last_response.body
reset_script_name!


# test polymorphic routes # test polymorphic routes
get "/polymorphic_route" get "/polymorphic_route"
Expand Down

0 comments on commit de55da0

Please sign in to comment.