You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询:更新用户名时如何删除旧的friendly_id gem slug

Got it, let's tackle this problem step by step. The issue here is that when a user updates their username (slug), the old URL still resolves to their profile instead of redirecting to the new one—and worse, that old slug could get taken by another user later. Here's how to fix this properly:

1. Track Previous Slugs in the Database

First, we need to add a field to your User table to store all past slugs the user has used. This lets us track old URLs and prevent reuse.

Run this migration to add the field (adjust for your database if needed—PostgreSQL works great with array types):

# Generate migration file
rails generate migration AddPreviousSlugsToUsers previous_slugs:text

# Edit the migration file to set up an array with index
class AddPreviousSlugsToUsers < ActiveRecord::Migration[6.1]
  def change
    add_column :users, :previous_slugs, :text, array: true, default: []
    add_index :users, :previous_slugs, using: 'gin' # For fast lookups
  end
end

# Run the migration
rails db:migrate
2. Update the Model (designer.rb) to Handle Slug History

Add callbacks and validations to your user model to save old slugs and prevent conflicts:

class Designer < ApplicationRecord
  # Save old username to previous_slugs when it changes
  before_update :archive_old_slug, if: :username_changed?

  # Validate new username is unique (including against past slugs)
  validates :username, uniqueness: true
  validate :username_not_used_in_history

  # Helper to find users by current OR past slugs
  def self.find_by_slug(slug)
    find_by(username: slug) || find_by("'#{slug}' = ANY(previous_slugs)")
  end

  private

  def archive_old_slug
    # Add old username to the array (avoid duplicates)
    self.previous_slugs = (previous_slugs + [username_was]).uniq
  end

  def username_not_used_in_history
    if Designer.exists?("'#{username}' = ANY(previous_slugs)")
      errors.add(:username, "This username has been used before—please choose another.")
    end
  end
end
3. Adjust Controllers to Trigger Slug Saving

Make sure your update logic in registration_controller.rb allows the username to be modified and triggers the model callbacks:

class RegistrationController < Devise::RegistrationsController
  def update
    self.resource = current_designer
    prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)

    resource_updated = update_resource(resource, account_update_params)
    yield resource if block_given?

    if resource_updated
      set_flash_message_for_update(resource, prev_unconfirmed_email)
      bypass_sign_in resource if sign_in_after_change_password?

      # Optional: Clear any cache tied to the old slug
      Rails.cache.delete("user_profile_#{resource.username_was}") if resource.username_changed?

      redirect_to after_update_path_for(resource)
    else
      clean_up_passwords resource
      set_minimum_password_length
      render :edit
    end
  end

  private

  # Allow username in update params
  def account_update_params
    params.require(:designer).permit(:username, :email, :password, :password_confirmation, :current_password)
  end
end

For omniauth_callbacks_controller.rb, ensure third-party logins don't bypass the slug history logic:

class OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def twitter # Adjust for your provider
    @user = Designer.from_omniauth(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication
      set_flash_message(:notice, :success, kind: "Twitter") if is_navigational_format?
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_designer_registration_url
    end
  end

  def self.from_omniauth(auth)
    user = Designer.where(provider: auth.provider, uid: auth.uid).first_or_initialize
    if user.persisted? && user.username != auth.info.nickname
      # Trigger update logic to archive old slug
      user.update!(username: auth.info.nickname)
    else
      user.username = auth.info.nickname
      user.email = auth.info.email
      user.save!
    end
    user
  end
end
4. Add Redirect Logic for Old Slugs

Update your routes and add a controller action to redirect old slug URLs to the new profile:

In config/routes.rb:

Rails.application.routes.draw do
  # Primary profile route (matches current usernames)
  get '/users/:username', to: 'users#show', as: :user

  # Catch old slugs and redirect (only if the slug isn't a current username)
  get '/users/:old_slug', to: 'users#redirect_old_slug', constraints: lambda { |req|
    !Designer.exists?(username: req.params[:old_slug])
  }
end

In users_controller.rb:

class UsersController < ApplicationController
  def show
    @user = Designer.find_by(username: params[:username])
    redirect_to_not_found unless @user
  end

  def redirect_old_slug
    @user = Designer.find_by_slug(params[:old_slug])
    if @user
      # Use 301 permanent redirect to tell search engines to update their index
      redirect_to user_path(@user.username), status: :moved_permanently
    else
      redirect_to_not_found
    end
  end

  private

  def redirect_to_not_found
    render file: "#{Rails.root}/public/404.html", status: :not_found, layout: false
  end
end
5. Final Checks
  • Test updating a username: The old URL should now redirect to the new one with a 301 status.
  • Try registering a username that's been archived by another user: The validation should block it.
  • Clear any cached profile pages tied to slugs to avoid stale content.

内容的提问来源于stack exchange,提问作者Designer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 04:07:23