Cameron
Cameron

Reputation: 28783

Rails routing redirect if slug not correct

In my Rails app I have posts and they have a route like so:

match '/posts/:id' => 'posts#show', :via => :get, :as => :post

In my model I have:

def to_param
  "#{id}/#{slug}"
end

So that I can have a pretty URL like:

/posts/1/This-is-a-post

However the first issue I have is that the / between the id and slug gets encoded! So I end up with: /posts/1%2FThis-is-a-post

The second issue I have, is that it doesn't matter what I put as the slug, it will ALWAYS show the post. While this isn't bad, it means the URL integrity is lost as the same post can have many variations.

What I'd like to do is make it work like on Stack Overflow, where if a user hits any of these URLs

/posts/1
/posts/1/This-is-a
/posts/1/this_is-A-PoSt
/posts/1/sifiusfheud

it will auto-redirect to

/posts/1/This-is-a-post

How can I do this in Rails? And can I do it using the to_param? Or will I have to do something custom?

Upvotes: 1

Views: 716

Answers (2)

Cameron
Cameron

Reputation: 28783

This is what I have come up with (inspired by Iceman's comments).

Two routes:

match '/posts/:id' => 'posts#show', :via => :get, :as => :post
match '/posts/:id/:slug' => 'posts#show', :via => :get, :as => :post_with_slug

And then use the post_with_slug for the links:

<%= link_to post.title, post_with_slug_path(id: post.id, slug: post.slug) %>

But then took it further with a helper:

def post_with_slug(post)
  "/posts/#{[post.id, post.slug].join('/')}"
end

So I could just pass the object:

<%= link_to post.title, post_with_slug(post) %>

Finally redirect if the slug param doesn't match.

def show
  @post = Post.find(params[:id])
  redirect_to post_with_slug_path(id: @post.id, slug: @post.slug), :status => 301 if @post.slug != params[:slug]
end

This looks to work really well, and even handles if the slash between the slug and ID is missing or different and always redirects the user to the correct url. I added a 301 status, just in case a user had used in an incorrect or old slug for SEO purposes.

Upvotes: 0

Eyeslandic
Eyeslandic

Reputation: 14890

How about having a route like this?

get '/posts/:id/:slug' => 'posts#show', as: :post_with_slug

and then use it in a view like

= link_to 'Post...', post_with_slug_path(:id @post.id, slug: @post.slug)

You could then do some checking of the spelling in the controller and route to the correct spelling of the post.

Then there is also the friendly_id gem that is quite useful.

Upvotes: 1

Related Questions