Skip to content

Commit

Permalink
Merge remote branch 'joshk/redirect_routing'
Browse files Browse the repository at this point in the history
Conflicts:
	actionpack/CHANGELOG
	actionpack/lib/action_controller/metal/mime_responds.rb

Signed-off-by: José Valim <jose.valim@gmail.com>
  • Loading branch information
josevalim committed Dec 3, 2010
2 parents 9a3e29e + 1e26bda commit 78afe68
Show file tree
Hide file tree
Showing 8 changed files with 573 additions and 363 deletions.
2 changes: 2 additions & 0 deletions actionpack/CHANGELOG
Expand Up @@ -2,6 +2,8 @@

* url_for and named url helpers now accept :subdomain and :domain as options [Josh Kalderimis]

* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused (check the documentation for examples). [Josh Kalderimis]

* Added config.action_controller.include_all_helpers. By default 'helper :all' is done in ActionController::Base, which includes all the helpers by default. Setting include_all_helpers to false will result in including only application_helper and helper corresponding to controller (like foo_helper for foo_controller). [Piotr Sarnacki]

* Added a convenience idiom to generate HTML5 data-* attributes in tag helpers from a :data hash of options:
Expand Down
52 changes: 52 additions & 0 deletions actionpack/lib/action_dispatch/http/url.rb
Expand Up @@ -24,6 +24,58 @@ def self.named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end

def self.url_for(options = {})
unless options[:host].present? || options[:only_path].present?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end

rewritten_url = ""

unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
rewritten_url << host_or_subdomain_and_domain(options)
rewritten_url << ":#{options.delete(:port)}" if options[:port]
end

path = options.delete(:path) || ''

params = options[:params] || {}
params.reject! {|k,v| !v }

rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "?#{params.to_query}" unless params.empty?
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end

class << self
private

def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
else
""
end
end

def host_or_subdomain_and_domain(options)
return options[:host] unless options[:subdomain] || options[:domain]

tld_length = options[:tld_length] || @@tld_length

host = ""
host << (options[:subdomain] || extract_subdomain(options[:host], tld_length))
host << "."
host << (options[:domain] || extract_domain(options[:host], tld_length))
host
end

end



# Returns the complete URL used for this request.
def url
Expand Down
39 changes: 4 additions & 35 deletions actionpack/lib/action_dispatch/routing/mapper.rb
Expand Up @@ -2,6 +2,7 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/object/blank'
require 'active_support/inflector'
require 'action_dispatch/routing/redirection'

module ActionDispatch
module Routing
Expand Down Expand Up @@ -383,39 +384,6 @@ def delete(*args, &block)
map_method(:delete, *args, &block)
end

# Redirect any path to another path:
#
# match "/stories" => redirect("/posts")
def redirect(*args)
options = args.last.is_a?(Hash) ? args.pop : {}

path = args.shift || Proc.new
path_proc = path.is_a?(Proc) ? path : proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
status = options[:status] || 301

lambda do |env|
req = Request.new(env)

params = [req.symbolized_path_parameters]
params << req if path_proc.arity > 1

uri = URI.parse(path_proc.call(*params))
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?

body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)

headers = {
'Location' => uri.to_s,
'Content-Type' => 'text/html',
'Content-Length' => body.length.to_s
}

[ status, headers, [body] ]
end
end

private
def map_method(method, *args, &block)
options = args.extract_options!
Expand Down Expand Up @@ -636,7 +604,7 @@ def namespace(path, options = {})
:shallow_path => path, :shallow_prefix => path }.merge!(options)
scope(options) { yield }
end

# === Parameter Restriction
# Allows you to constrain the nested routes based on a set of rules.
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
Expand All @@ -647,7 +615,7 @@ def namespace(path, options = {})
#
# Now routes such as +/posts/1+ will no longer be valid, but +/posts/1.1+ will be.
# The +id+ parameter must match the constraint passed in for this example.
#
#
# You may use this to also resrict other parameters:
#
# resources :posts do
Expand Down Expand Up @@ -1369,6 +1337,7 @@ def match(*args)

include Base
include HttpHelpers
include Redirection
include Scoping
include Resources
include Shorthand
Expand Down
110 changes: 110 additions & 0 deletions actionpack/lib/action_dispatch/routing/redirection.rb
@@ -0,0 +1,110 @@
require 'action_dispatch/http/request'

module ActionDispatch
module Routing
module Redirection

# Redirect any path to another path:
#
# match "/stories" => redirect("/posts")
#
# You can also use interpolation in the supplied redirect argument:
#
# match 'docs/:article', :to => redirect('/wiki/%{article}')
#
# Alternatively you can use one of the other syntaxes:
#
# The block version of redirect allows for the easy encapsulation of any logic associated with
# the redirect in question. Either the params and request are supplied as arguments, or just
# params, depending of how many arguments your block accepts. A string is required as a
# return value.
#
# match 'jokes/:number', :to => redirect do |params, request|
# path = (params[:number].to_i.even? ? "/wheres-the-beef" : "/i-love-lamp")
# "http://#{request.host_with_port}/#{path}"
# end
#
# The options version of redirect allows you to supply only the parts of the url which need
# to change, it also supports interpolation of the path similar to the first example.
#
# match 'stores/:name', :to => redirect(:subdomain => 'stores', :path => '/%{name}')
# match 'stores/:name(*all)', :to => redirect(:subdomain => 'stores', :path => '/%{name}%{all}')
#
# Finally, an object which responds to call can be supplied to redirect, allowing you to reuse
# common redirect routes. The call method must accept two arguments, params and request, and return
# a string.
#
# match 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
#
def redirect(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
status = options.delete(:status) || 301

path = args.shift

path_proc = if path.is_a?(String)
proc { |params| (params.empty? || !path.match(/%\{\w*\}/)) ? path : (path % params) }
elsif options.any?
options_proc(options)
elsif path.respond_to?(:call)
proc { |params, request| path.call(params, request) }
elsif block
block
else
raise ArgumentError, "redirection argument not supported"
end

redirection_proc(status, path_proc)
end

private

def options_proc(options)
proc do |params, request|
path = if options[:path].nil?
request.path
elsif params.empty? || !options[:path].match(/%\{\w*\}/)
options.delete(:path)
else
(options.delete(:path) % params)
end

default_options = {
:protocol => request.protocol,
:host => request.host,
:port => request.optional_port,
:path => path,
:params => request.query_parameters
}

ActionDispatch::Http::URL.url_for(options.reverse_merge(default_options))
end
end

def redirection_proc(status, path_proc)
lambda do |env|
req = Request.new(env)

params = [req.symbolized_path_parameters]
params << req if path_proc.arity > 1

uri = URI.parse(path_proc.call(*params))
uri.scheme ||= req.scheme
uri.host ||= req.host
uri.port ||= req.port unless req.standard_port?

body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)

headers = {
'Location' => uri.to_s,
'Content-Type' => 'text/html',
'Content-Length' => body.length.to_s
}

[ status, headers, [body] ]
end
end

end
end
end
62 changes: 18 additions & 44 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Expand Up @@ -442,12 +442,9 @@ def generate

raise_routing_error unless path

params.reject! {|k,v| !v }

return [path, params.keys] if @extras

path << "?#{params.to_query}" unless params.empty?
path
[path, params]
rescue Rack::Mount::RoutingError
raise_routing_error
end
Expand Down Expand Up @@ -486,7 +483,7 @@ def generate(options, recall = {}, extras = false)
end

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

def _generate_prefix(options = {})
nil
Expand All @@ -498,29 +495,24 @@ def url_for(options)

handle_positional_args(options)

rewritten_url = ""

path_segments = options.delete(:_path_segments)
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
rewritten_url << host_from_options(options)
rewritten_url << ":#{options.delete(:port)}" if options[:port]
end
user, password = extract_authentication(options)
path_segments = options.delete(:_path_segments)
script_name = options.delete(:script_name)

script_name = options.delete(:script_name)
path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s

path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
path << generate(path_options, path_segments || {})

# 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)
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
path_addition, params = generate(path_options, path_segments || {})
path << path_addition

rewritten_url
ActionDispatch::Http::URL.url_for(options.merge({
:path => path,
:params => params,
:user => user,
:password => password
}))
end

def call(env)
Expand Down Expand Up @@ -561,23 +553,12 @@ def recognize_path(path, environment = {})

private

def host_from_options(options)
computed_host = subdomain_and_domain(options) || options[:host]
unless computed_host
raise ArgumentError, "Missing host to link to! Please provide :host parameter or set default_url_options[:host]"
def extract_authentication(options)
if options[:user] && options[:password]
[options.delete(:user), options.delete(:password)]
else
nil
end
computed_host
end

def subdomain_and_domain(options)
return nil unless options[:subdomain] || options[:domain]
tld_length = options[:tld_length] || ActionDispatch::Http::URL.tld_length

host = ""
host << (options[:subdomain] || ActionDispatch::Http::URL.extract_subdomain(options[:host], tld_length))
host << "."
host << (options[:domain] || ActionDispatch::Http::URL.extract_domain(options[:host], tld_length))
host
end

def handle_positional_args(options)
Expand All @@ -590,13 +571,6 @@ def handle_positional_args(options)
options.merge!(Hash[args.zip(keys).map { |v, k| [k, v] }])
end

def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
else
""
end
end
end
end
end

0 comments on commit 78afe68

Please sign in to comment.