Question

StackOverflow seems to have this style of routes for questions:

/questions/:id/*slug

Which is easy enough to achieve, both in routes and to_param.

However, StackOverflow seems to also redirect to that path when just an ID is passed.

Example:

stackoverflow.com/questions/6841333 

redirects to:

stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/

Same goes for any variation of the slug

stackoverflow.com/questions/6841333/some-random-stuff

Will still redirect to the same URL.

My question is: Is this type of redirection typically handled in the controller (comparing the request to the route) or is there a way to do this in routes.rb?

The reason I wouldn't think this possible in the routes.rb file is that typically, you don't have access to the object (so you couldn't get the slug based off the ID, right?)

For anyone interested, Rails 3.2.13 and also using FriendlyID

Was it helpful?

Solution

Ok, so I think I've got this.

I was looking into doing something with middleware, but then decided that's probably not the place for this type of functionality (since we need to access ActiveRecord).

So I ended up building a service object, known as a PathCheck. The service looks like this:

class PathCheck
  def initialize(model, request)
    @model = model
    @request = request
  end 

  # Says if we are already where we need to be
  # /:id/*slug
  def at_proper_path?
    @request.fullpath == proper_path
  end

  # Returns what the proper path is
  def proper_path
    Rails.application.routes.url_helpers.send(path_name, @model) 
  end

private
  def path_name
    return "edit_#{model_lowercase_name}_path" if @request.filtered_parameters["action"] == "edit"
    "#{model_lowercase_name}_path"
  end

  def model_lowercase_name
    @model.class.name.underscore
  end
end

This is easy enough to implement into my controller:

def show
  @post = Post.find params[:post_id] || params[:id]
  check_path
end

private
  def check_path
    path_check = PathCheck.new @post, request
    redirect_to path_check.proper_path if !path_check.at_proper_path?
  end

My || in my find method is because in order to maintain resourceful routes, I did something like...

resources :posts do
  get '*id' => 'posts#show'
end

Which will make a routes like: /posts/:post_id/*id on top of /posts/:id

This way, the numeric id is primarily used to look up the record, if available. This allows us to loosely match /posts/12345/not-the-right-slug to be redirected to /posts/12345/the-right-slug

The service is written in a universal fashion, so I can use it in any resourceful controller. I have't found a way to break it yet, but I'm open to correction.

Resources

Railscast #398: Service Objects by Ryan Bates

This Helpful Tweet by Jared Fine

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top