views:

1134

answers:

8

My application is in RoR

I have an action/view called showsummary where the ID has been passed into the URL, and the controller has used that to instantiate @vendor where @vendor.name is the name of a company.

I would like the URL to be, rather than showsummary/1/ to have /vendor-name in the URL instead.

How do I do that?

A: 

I'd have to experiment a bit to get it right, but there's two primary parts to the solution.

1) Add a route. in config/routes, add a line that sends requests of the form baseurl/controller/:vendor-name to the action showsummary, (or maybe a new action, show_summary_by_vendor_name) [also, if you planned on using baseurl/:vendorname, that's fine too] For convenience, make sure the parameter is something like :vendor-name, not the default :id

2) Write the controller action. In the controller file, either edit your showsummary action to differentiate based on whether it's called with an id or with a vendorname, or just write a show_summary_by_vendor_name. (depending on best practices, and what route you wrote in 1. I don't know off the top of my head which is preferable) You can then do @vendor = Vendors.find_by_name(params[:vendor_name]) or something like that, and then render it the way you would in regular showsummary.

3) Use that as the link. Once you confirm that baseurl[/controller?]/vendor-name works, and shows the summary, make sure all the links in your application, and elsewhere, use that link. Off the top of my head, I can't remember how difficult it is to integrate a custom route into link_to, but I think it's doable. Most search engines [google] rely heavily on links, so good SEO will have you using those named links, not the numbered ones. I think. I don't know much about SEO.

YenTheFirst
+3  A: 

Ryan Bates has a great screencast on this very subject.

Basically you overload the to_param method in the Vendor model.

   def to_param
     permalink
   end

Then when you look up the resource in your controller you do something like this:

   @vender = Vender.find_by_name(params[:id])

But the problem with this is that you'll have to make sure that the vendors' names are unique. If they can't be then do the other solution that Ryan suggests where he prepends the the id to the name and then parses the resulting uri to find the item id.

vrish88
+2  A: 

You do this by modifying the routes that are used to access those URL's and changing them to use :name, rather than :id. This will probably mean that you have to write the routes yourself rather than relying on resources.

For instance add this to the routes.rb file:

map.with_options :controller => "vendor" do |vendor|
  vendor.connect "/vendor/:name", :action => "show"
  # more routes here for update, delete, new, etc as required
end

The other change that will be required is that now you'll have to find the vendor object in the database by the name not the id, so:

@vendor = Vendor.find_by_name(params[:name])

Internally (at least to my knowledge/experimentation) whatever parameter name is not specified in the URL part of the route (i.e. not within the "/Controller/Action/:id" part of it) is tacked on to the end as a parameter.

Daemin
+7  A: 

All of these solutions use find_by_name, which would definitely require having an index on that column and require they are unique. A better solution that we have used, sacrificing a small amount of beauty, is to use prefix the vendor name with its ID. This means that you dont have to have an index on your name column and/or require uniqueness.

vendor.rb

def to_param
  normalized_name = name.gsub(' ', '-').gsub(/[^a-zA-Z0-9\_\-\.]/, '')
  "#{self.id}-#{normalized_name}"
end

So this would give you URLs like

/1-Acme

/19-Safeway

etc

Then in your show action you can still use

Vendor.find(params[:id])

as that method will implicitly call .to_i on its argument, and calling to_i on such a string will always return the numerical prefix and drop the remaining text- its all fluff at that point.

The above assumes you are using the default route of /:controller/:action/:id, which would make your URLs look like

/vendors/show/1-Acme

But if you want them to just look

/1-Acme

Then have a route like

map.show_vendor '/:id', :controller => 'vendors', :action => 'show'

This would imply that that it would pretty much swallow alot of URLs that you probably wouldnt want it too. Take warning.

Cody Caughlan
Hi, thanks for this -- what do you mean by "swallow alot of URLs that you wouldn't want it to?" oh, you mean the route would change pretty much anything calling that controller you mean?I am fine with vendors/show/1-Acme...how does it affect SEO?
AFG
Yes, '/:id' would match a lot of things, so you would have to have a lot of named routes for '/about', '/contact', etc. As for SEO, I think the number prefix is negligible.
Cody Caughlan
So how would I create a named route for vendors/list which would correspond to the action in the vendor_controller.rb called 'list'?Thanks. Otherwise it looks good.
AFG
To make the URL '/vendors/list' then the route would be:map.vendors_list '/vendors/list', :controller => 'vendors', :action => 'list'see rails routing docs: http://api.rubyonrails.org/classes/ActionController/Routing.html
Cody Caughlan
cool, thank you
AFG
Use String#parameterize (ActiveSupport) instead of #gsub
Bogdan Gusiev
+2  A: 

I thought I'd mention String#parameterize, as a supplement to the tagged answer.

def to_param
  "#{id}-#{name.parameterize}"
end

It'll filter out hyphenated characters, replace spaces with dashes etc.

August Lilleaas
that's interesting...any downsides?
AFG
Everything has downsides! One might be that the link will automatically change if the 'name' attribute changes, which can be controlled more precisely if you store the permalink in a separate attribute in the database.
August Lilleaas
A: 

DHH and the Rails team recommend the use of RESTful routes when possible. Above all else, this should form the basis of your quest for SEO friendly awesomeness! Here, a RESTful architecture is definitely possible, if not necessary. There are many brief tutorials on SEO Friendly URLs in Rails that can get you a quick win!

First, make sure you are using a resource route instead of the default routes. The default routes are bad because they make all actions in every controller accessible via GET requests

routes.rb

map.resources :vendors

Next, you want to override the to_param method for your Vendor model:

vendor.rb

def to_param
  "#{id}-#{name.downcase.gsub(/[^a-zA-Z 0-9]+/, '').gsub(/\s+/, '-')}"
end

When creating hyperlinks from your Rails Vendor model, the format you specify in your to_param will appear like so:

vendors/index.html.erb

<%= link_to @vendor.name, @vendor %>

Then when you need to find a specific instance of a Vendor object, then call:

vendor_controller.rb

def show
  @vendor = Vendor.find(params[:id])
end

Well, good luck!

Sean O'Malley
A: 

Finally I found site like minimalbugs.com

Great I be back again and again for search answer