Skip to content

Commit

Permalink
Use env['action_dispatch.routes'] to determine if we should generate …
Browse files Browse the repository at this point in the history
…prefix or not.

This technique is here to allow using routes from Engine in Application
and vice versa. When using Engine routes inside Application it should
generate prefix based on mount point. When using Engine routes inside
Engine it should use env['SCRIPT_NAME']. In any other case it should
generate prefix as env should not be even available.
  • Loading branch information
drogus committed Sep 3, 2010
1 parent 32a5b49 commit 28016d3
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 8 deletions.
27 changes: 27 additions & 0 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -261,14 +261,41 @@ 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

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
Expand Down
20 changes: 14 additions & 6 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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!
Expand All @@ -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("://")
Expand All @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion actionpack/lib/action_dispatch/routing/url_for.rb
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion actionpack/test/controller/routing_test.rb
Expand Up @@ -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

Expand Down
102 changes: 102 additions & 0 deletions 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

0 comments on commit 28016d3

Please sign in to comment.