I want my Rails 2.3.2 app to respond to and generate URLs like so:
/websites/asd.com
/websites/asd.com/dns_records/new
In my config/routes.rb, I have:
map.resources :websites, :has_many => :dns_records
map.resources :dns_records, :belongs_to => :website
I can then access resources such as:
/websites/1
/websites/1/dns_records
By modifying my Website model, I can generate better URLs like so:
class Website < ActiveRecord::Base
def to_param
domain_name
end
...
end
# app/views/websites/index.erb
<% @websites.each do |w| %>
<%= link_to "Show #{w}", website_path(w) %>
<% end %>
# Produces a link to:
/websites/example_without_periods_in_name
However, for domain names that contain '.' characters, Rails gets unhappy. I believe this is because the '.' character is defined in ActionController::Routing::SEPARATORS, which lists special characters to split the URL on. This allows you to do stuff like /websites/1.xml.
SO, is there a clean way to allow '.' characters in RESTful URLs?
I've tried redefining ActionController::Routing::SEPARATORS to not include '.', which is a totally bad way to solve the problem. This messes up generated URLs by appending ".:format" to them.
I also know I can add :requirements => { :id => regexp } to my config/routes.rb to match a domain name that includes '.' (without this, params[:id] is set to the part of the domain name before the first '.'), but this doesn't help in generating URLs/paths RESTfully.
Many thanks :)
Nick
Solved the problem, with a big thanks to http://poocs.net/2007/11/14/special-characters-and-nested-routes (and see http://dev.rubyonrails.org/ticket/6426 for additional reference)
I needed to add :requirements => { :website_id => regexp } for each nested route that was also going to include a domain name with periods in it.
Here's my working route:
map.resources :websites, :requirements => { :id => /[a-zA-Z0-9\-\.]+/ } do |websites|
websites.with_options :requirements => { :website_id => /[a-zA-Z0-9\-\.]+/ } do |websites_requirements|
websites_requirements.resources :dns_records
end
end
<%= link_to 'New DNS Record', new_website_dns_record_path(@website) %>
# Produces the URL
/websites/asd.com/dns_records/new
The call to
websites.with_options
is simply in keeping with DRY so that :requirements don't have to be specified for all nested routes for websites. So I could also have
websites_requirements.resources :accounts
websites_requirements.resources :monthly_bandwidth_records
etc.
This is an interesting issue. I don't think you can get rid of the bad '.:format'
appended to the end if you do a basic map.resources
. If you want periods in the names, you are not in accordance with the usual rails styles, and I think a custom route might be in order if you absolutely NEED the '.' in the URL.
However, maybe you should consider changing your definition of to_param
. What would you think about using the following?
def to_param
domain_name.sub('.','_dot_')
end
I think, if you're using this to manage customer websites, it's a fairly elegant way to generate nice (and SEO friendly) URLs like
/websites/asd_dot_com/dns_records/new
/websites/asd_dot_com/
I had a similar issue some time ago and came to a similar solution. Using /.+/ as the requirement for the param in question worked fine for me.
http://zargony.com/2009/05/05/routing-parameters-with-a-dot