Rails RESTful resources using to_param for a field

2019-04-06 09:16发布

问题:

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

回答1:

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.


回答2:

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/


回答3:

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