44

Using Ruby on Rails 3's new routing system, is it possible to change the default :id parameter

resources :users, :key => :username

come out with the following routes

/users/new
/users/:username
/users/:username/edit
...etc

I'm asking because although the above example is simple, it would be really helpful to do in a current project I'm working on.

Is it possible to change this parameter, and if not, is there a particular reason as to why not?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
joeellis
  • 2,745
  • 7
  • 28
  • 45
  • 3
    I like this question from the perspective of "How do I change the default ID used to fetch a user/object/etc.?" However, I just want to put a plug in for obscuring that default ID as it tends to be a Primary Key directly from the database and there are all sorts of security reasons why you should not expose that value (easing SQL injection attacks, guessable IDs for other users, …). In particular, using the username enables direct attack on an account (pwd guessing). Using a large random and unique value makes all of these a lot harder. Cheers. – Andrew Philips Nov 19 '13 at 02:00
  • *Make sure to read the answers after the accepted one* – blnc Apr 22 '17 at 04:36

5 Answers5

73

In your route you can use

resources :user, param: :username
Ujjwal Thaakar
  • 839
  • 1
  • 6
  • 5
  • 4
    this should be the accepted answer, I'm much more comfortable adding an argument to a route than I am overwriting the `to_param` method – omnikron Feb 12 '14 at 13:53
  • 1
    @omnikron it depends on what you trying to achive in your app, sometimes you need to do both (e.g. when you want directly pass your resource to routing helpers `user_path @user` so that you don't have to do `user_path @user.username`) ...but yes you're right, this is elegant when you just want routing functionality. – equivalent8 Jun 04 '14 at 11:04
36

If I understand you correctly, what you want is to have the username instead of id in your url, right?

You can do that by overriding the to_param method in your model. You can get more information here.

Ju Nogueira
  • 8,435
  • 2
  • 29
  • 33
  • I guess this is the only way to go about it for now. Seems kind of roundabout though. – joeellis May 18 '10 at 14:22
  • 1
    Thanks for this pointer, completely solved my problem. I came here looking for how to change this b/c, by policy, we consider fetching by Primary Key ID from web as a security issue. Instead, we add a large random and unique value to the object (SecureRandom.urlsafe_base64(15)) and use that as the ID returned from to_param and when the RESTful APIs find_by_OID. Perhaps, when I understand ruby/rails better, this can be made into a gem. – Andrew Philips Nov 19 '13 at 01:47
30

For Ruby on Rails 4.1.4 (and possibly earlier versions) you need to do what both j.. and Ujjwal suggested:

1) In config/routes.rb, add:

resources :user, param: :username

2) In app/models/user.rb, add:

def to_param
  username
end

In you only do #1 then all your routes will be correct, as can be seen from rake routes:

$ rake routes
    Prefix Verb   URI Pattern                    Controller#Action
user_index GET    /user(.:format)                user#index
           POST   /user(.:format)                user#create
  new_user GET    /user/new(.:format)            user#new
 edit_user GET    /user/:username/edit(.:format) user#edit
      user GET    /user/:username(.:format)      user#show
           PATCH  /user/:username(.:format)      user#update
           PUT    /user/:username(.:format)      user#update
           DELETE /user/:username(.:format)      user#destroy

However the helper methods that construct a url based on a User instance will still include the id in the url, e.g. /user/1. To get the username in the constructed urls, you need to override to_param as in #2.

Community
  • 1
  • 1
Matt
  • 20,108
  • 1
  • 57
  • 70
  • 4
    the 2) is most of the time missed when when this question appears; this should be the accepted answer – Ben Jan 18 '15 at 19:14
1

Although the answer has been accepted by the asker, but there is a simple approach to do this. Write this code in your controller.

authorize_resource :find_by => :username

and in your view, where you want to show the link, write this code.

<%= link_to "Username", user_path(u.username) %>

You don't need any other changes in your routes or controller.

UPDATE: This will only work if you are using CanCan gem.

Kashif Umair Liaqat
  • 1,064
  • 1
  • 18
  • 27
  • That appears to be a CanCan method. Its not mentioned in the rails api at all. [apidock.com](http://apidock.com/rails/search?query=authorize_resource) [api.rubyonrails.org](http://api.rubyonrails.org/?q=authorize_resource) – iNulty May 20 '13 at 15:33
  • Yes, that is a CanCan method. I forgot to mention that in my answer. I have updated the answer. – Kashif Umair Liaqat May 21 '13 at 06:12
0

Pass the user name as the input to the path helpers.

In your view code:

user_path(u.username) #/users/john

In your controller treat the id received as username:

def show
  user = User.find_by_username!(params[:id])
  ...
end
Harish Shetty
  • 64,083
  • 21
  • 152
  • 198
  • That's kind of what I've been doing, but I just think it'd be more straightforward to do something like User.find_by_username(params[:username]) in the code. I really am just wondering why Rails doesn't allow this in general. Is it against some REST principle perhaps? – joeellis May 15 '10 at 14:57