Rails 3: Permalink public profile

2019-04-13 18:47发布

问题:

I have /users/1 as the public user profile (show method) but I'd like to have /user_name instead of /users/1.

Can I accomplish that if I use devise?

Thanks

Edit: My show controller:

# public profile page
  def show
    @user = User.find(params[:id])
  end

回答1:

Get username into URL

You can do it. Devise is just an authentication solution. Your other controllers can behave however you want them to. Here is an overview of the steps that you need to get this URL behavior.

  • Ensure that user_name is unique for each user
  • Override the User's to_param method to return user_name
  • Change the way that you get the user from the database

This would go in your User model

class User < ActiveRecord::Base
  devise :database_authenticatable, :confirmable, :validatable
  validates_uniqueness_of :user_name

  def to_param
    user_name
  end

end

Then, in your controller, do something like this:

@user = User.find_by_user_name(params[:id])

Helpers like user_path(@user) use the to_param method to generate the URL.

Remove controller from URL

As for changing the route from /user/:id to just /:id. Something like this would work:

match '/:id' => 'users#show', :as => :user

Here you can use user_path and user_url because of the :as => :user

But you may benefit from reading these:

How can I implement vanity URL's in a Rails application?

How to implement "short" nested vanity urls in rails?

There are a lot of things to think about when you do it that way.



回答2:

If you want to find by either the username or the id something like this should work:

@user = User.find_by_username(params[:id]) || User.find_by_id(params[:id])


回答3:

dontangg's answer is mostly right, but there are some very important things his answer is missing.

First, you absolutely should not use straight usernames for the slugs. You need to normalize them in some way and blacklist certain slugs to prevent them from colliding with your resource or action names, something that is especially important when you are using them without the resource prefix in the URL. So, as tee pointed out, the first step is using a slug generation library, and friendly_id is currently the best. Secondly, you need to add your resource names to the friendly_id slug blacklist and handle the exception thrown when an offending slug is generated. You'll generally want to have a cached_slug attribute on the sluggable model, and you'll need to expose that in #to_param (eg, def to_param; cached_slug end). Then you'll need to update your finders to check on the slug, eg ModelName.where(:cached_slug => params[:id]).first

Edit: also, WRT the comment on another answer that recommended using User.all, remember that .all should never be used on any table that has more than a handful of records. You load the whole table into memory as Ruby objects and can easily bring your server to a standstill.