Ruby on rails routing matching username - ruby-on-rails

I am sure there is a simple solution to this but I can't seem to find it.
I want to match the following dynamic username:
/users/dymanicUsername
How would this look in routes.rb
Thanks for any help.

allister,
To do that, just override to_param in your model. For instance :
#users.rb
def to_param
self.username
end
You will soon bump into some problems :
conflicts with existing routes. For instance, the username should not be 'new', got it ?
for fetching you user in the controller, you should create a method (that's what I did, mayybe not the best solution) in your model like this self.find_for_controller(username)
your username shall only contains url-enabled characters (forget accents, ponctuations etc...). A solution for this is to have a second attribute names username_urlized, that of course should be unique and not conflicting with other routes
And maybe more problems :)
Also, if you want something like twitter (yoursite.com/dynamicUserName), do the following in routes.rb :
resources :users, :path=>'' do

Here is a good explanation from 2006 which looks good for Rails 2.3.*:
http://garrickvanburen.com/archive/using-names-in-rails-routes-instead-of-ids
The main problems I see is that you would want to ensure not only all of the above that Marcel mentiones, but that the name is unique. ie put an index on name with :unique => true in the migration.
For rails3 to which I'm just now upgrading you would do the routing differently. For example map.connect is replaced by match.

Related

How do I set up a route to a url that ends with and number/id in ruby on rails?

How do I route to a page that ends with an id?
E.G.
before: site.com/messages/8
after: site.com/messages/terrytibbs
I've tried:
match "/messages/:username" => "messages#id"
No luck so far. Just trying to make the url have a little more meaning by replacing the number with the username of the user the current user is talking to.
Kind regards
If you want something simple without having to change your routes etc, why not do this:
class Message
def to_param
"#{id}-{username}"
end
...
end
Assuming you have a username attribute on your message. That will make your url look like:
site.com/messages/8-terrytibbs
this works because of the following (say in irb):
"8-terrytibbs".to_i
=> 8
and when rails looks up your message in your controller it will do the same thing to the id parameter.
EDIT: there is an excellent railscast on this here: http://railscasts.com/episodes/63-model-name-in-url and an updated version here: http://railscasts.com/episodes/63-model-name-in-url-revised
Take a look at friendly_id gem. I think it's what you need.
FriendlyId is the "Swiss Army bulldozer" of slugging and permalink plugins for Ruby on Rails. It allows you to create pretty URLs and work with human-friendly strings as if they were numeric ids for Active Record models.
Using FriendlyId, it's easy to make your application use URLs like:
http://example.com/states/washington
instead of:
http://example.com/states/4323454
Your route is set up correctly you have to change the Controller to use the correct parameters.
Assuming your MessagesController does:
def id
User.find(params[:id])
end
change to:
def id
User.find_by_username(params[:username])
end
I would also recommend adding indexing on user name.
You're on the right track, you just need to make sure the route is pointing at a proper action on the controller, like so:
Say the action you want this to point to is named show, here is how you would define the route:
match 'messages/:username' => 'messages#show'
Then if you navigate to messages/8, params[:username] will be set to '8' (parameters always come in as String's.
Likewise if you navigate to messages/terrytibbs, params[:username] will be set to 'terrytibbs'.
Try reading Chapter 3-3.5 of the Rails Routing Guide, it provides a good overview of how to bind parameters to a route like you are attempting to do.

Things to change when using username slug/short URL in rails

I'm pretty new at rails, so forgive me if I'm overlooking simple things or the rails way. My objective is to completely replace URLs of the form
/users/1
with
/username
for all purposes. (I think exposing IDs scaffolding publicly is like walking around with a bone sticking out of your arm.) But implementing seems a little more complicated than I expected. This seems to change the entire way rails indexes and looks up users, rather than just substituting a lookup method.
Although I've kind of gotten this to function using the to_param override in my user.rb file, I've read this means I'll have indexing problems down the road when using params([:username]), and I'm not sure how it will impact my
(a) session model at new user creation, and
(b) #User usage in the user/show.html.erb file.
So, I've either consulted the following pages (or asked the questions):
Ruby on rails routing matching username
customize rails url with username
Routing in Rails making the Username an URL:
routing error with :username in url
Correct routing for short url by username in Rails
rails3, clean url, friendly_id, sessions
The major issues I'd like to understand from this question:
What functionality do I lose by transitioning to this? That is, what things currently "just work" in rails that I'll have to address and rewrite if I pursue this replacement?
As a practice, is this something better to replace with friendly_id? My concern here is that creating a slug column in my DB identical to the username seems a little non-DRY and makes me uncomfortable, and I'd rather avoid dependencies on external gems where possible.
What does my users#show need to look like?
You should check out Friendly ID. Makes doing what you're trying to do incredibly easy.
https://github.com/norman/friendly_id
There's a Railscast for it, too.
http://railscasts.com/episodes/314-pretty-urls-with-friendlyid?view=asciicast
If your username contains a special characters like #, -, . and got an error that says "No route matches" then you need to filter its route. See below:
match "/user/:username" => 'users#show', :as => :profile, :username => /[\.a-zA-Z0-9_#-]+/
After working around this for a couple weeks, I'd say the best answer as of Aug 2, 2012 is that if you do this, you violate many rails conventions and rip apart the very fabric of time and space itself.
Ugly scaffolding in the URLs is a necessary part of rails' approach to RESTfulness.

Drupal-like routing system in Rails

I am trying to find a best-practice to allow users to define the route to their pages in Rails, by writing them in a text field when submitting posts, like with the Path module in Drupal (yes, we are porting a Drupal site to Rails)
So, I need to
define a new, named route on article submission (eg http://www.domain.com/a-day-in-annas-life)
change the existing route on article edit, if they define a new one, by doing a 301 redirect from the old route to the new one
How can I best achieve this?
Okay, I found a way, but if it's best practice or not, I cant say.
I am using custom restrictor's like this:
class CharitiesRestrictor
def self.matches?(request)
slug = request.path_parameters[:path]
!Charity.find_by_name(slug).nil?
end
end
constraints CharitiesRestrictor do
match '*path' => 'charities#show_by_slug', :constraints => CharitiesRestrictor.new
end
When I create a block like this for each of my model/controller pairs that should be able to respond to permalinks, I can have them all have a chance to act on a permalink. However, this also means that they all get called in series, which is not necessarily ideal.

Rails routes creating additional info in URL

Say if I have a model called 'deliver' and I am using the default URL route of:
# Install the default routes as the lowest priority.
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
So the deliver URL would be:
http://localhost:3000/deliver/123
What I am trying to work out, is how to use another field from the database alongside or instead of the ID.
For example. If I have a field in the create view called 'deliveraddress', how do I put that into the routes?
So I can have something link this:
http://localhost:3000/deliver/deliveraddress
Thanks,
Danny
Since it sounds from your comments like you're trying to obfuscate the ID in your URL, I would suggest that you look at this question, which was asked a few days ago.
Obfuscating ids in Rails app
First of all, the url "http://localhost:3000/deliver/123" matches either default routing rule. However, only after you declared an "resource" it will generate such a RESTful URL.
In your case, just implement the "to_param" method of Deliver model:
class Deliver < ActiveRecord::Base
def to_param
return self.deliveraddress
end
end
it will generate the url you want by calling url_for method, like link_to #deliver
Do not forget to make sure you have unique deliver addresses in your database so you will never find duplicated records with one address.
After that, you need to update the finder methods in actions:
def show
#deliver = Deliver.find_by_deliver_address!(params[:id])
end
Hope this answer will be useful.

how do I make the URL's in Ruby on Rails SEO friendly knowing a #vendor.name?

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?
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.
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.
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.
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.
Friendly ID
http://github.com/norman/friendly_id/blob/26b373414eba639a773e61ac595bb9c1424f6c0b/README.rdoc
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.
Take a look also at this quck start to SEO for Rails

Resources