Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create app-wide slug routing for Rails app?

I have a number of different models in the Rails app I'm working on. I've seen a number of sites use an app-wide slug routing approach. What do I mean by this?

http://example.com/nick-oneill <-- This points to a User object
http://example.com/facebook    <-- This points to a Company object
http://example.com/developers  <-- This points to the users#index page

I'm aware of to_param and creating reader-friendly slugs within apps, however I'm not aware of an approach to have root-level slugs for a variety of objects. You can think of this being similar to Facebook's Graph API: there are different object types, but all of them exist at https://graph.facebook.com/object-id

Any insight would be much appreciated!

like image 276
Nick ONeill Avatar asked Mar 13 '13 01:03

Nick ONeill


2 Answers

There may be a way to do this with freindly_id but i think the problem with friendly id is things are scoped by model.

If I wanted truely sitewide slugging I would create a slugs table with a polymorphic relationship to all my models.

Sluggable_type and sluggable_id and then a slug field with the complete permalink/slug.

+---------------------------------------------+
| sluggable_type | sluggable_id |     slug    |
|      user      |       13     |  users/john |
+---------------------------------------------+

Now i could do do a wildcard catch all route or create the routes for all my slugs at runtime and force a route refresh when a model is updated that was under this sluggable control.

routes.rb

  get "/*segments",
               :controller => 'slugs',
               :action => 'dynamicroute'

Now in your SlugsController implement a method like

def dynamicroute
  segments = params[:segments]
  slugs.find_by_slug(segments)
  slug.sluggable_type.constantize.find(slug.sluggable_id) #retrive real record
  #some magic to handle the slugged item maybe redirect to the appropriate
  #controller or somehow call the show view for that controller
end

OR

routes.rb

begin  
  Slug.all.each do |s|
    begin
      get "#{s.slug}" => "#{s.sluggable_type.demodulize.pluralize.camelize}#show"
    rescue
    end
  end
rescue
end

If you use the 2nd approach to routing make sure you call

YOUR_APP_NAME::Application.reload_routes!

After editing any slugged record to refresh the routing table.

We've had similar issues and we may try our hand at gemifying this approach.

like image 64
j_mcnally Avatar answered Oct 31 '22 11:10

j_mcnally


I'd probably approach this as follows, at least as a first pass:

  • Use friendly_id or similar to generate slugs for each model involved
  • Hook up a catch-all route for /([-_a-zA-Z0-9]+) and point it to something like EntitiesController#show
  • Hook up a higher priority route for /developers pointing to Users#index
  • In EntitiesController#show:

    @entity = User.find(params[:id]) or Company.find(params[:id]) or raise ActionController::RoutingError.new('Not Found')

  • Then, based on the type of entity you've got:

    render "VIEW_PATH_BASED_ON_ENTITY_CLASS/show"

I would also order the finds from most to least frequently accessed (guess first, then use data later to tweak the order).

Finally, probably obvious but make sure you're indexing the slug column in each table since you'll often be doing multiple finds per request.

FWIW I'd love to know a better way to approach this as well; this is simply how I'd attack the problem initially.

like image 22
Kyle Avatar answered Oct 31 '22 13:10

Kyle