---
title: Redis For A Flat URL Hierarchy
teaser:
tags: web,redis,rails
author: Dan Croak
published_on: 2011-05-23
---

In the <abbr title="Uniform Resource Locator">URL</abbr> structure of a new app,
I want the routes to have a flat hierarchy similar to Quora's:

* A user: http://quora.com/Jason-Morrison-1
* A topic: http://quora.com/Ruby-on-Rails
* A question: http://quora.com/How-do-you-install-vim-color-scheme-in-OS-X

I've heard this might be [good for
SEO](http://www.quora.com/Does-URL-syntax-impact-keyword-visibility-to-search-engines)
and a few objects in this new app should share the top-level namespace.

## Keys and values

<abbr title="Uniform Resource Locator">URL</abbr>s are to resources as keys are
to values. Maybe this is a job for a key-value store like
[Redis](http://redis.io).

## Set up

Getting Redis on OS X:

    brew install redis

In `Gemfile`:

    gem 'redis'

In `config/initializers/redis.rb`:

    REDIS = Redis.connect(url: ENV['REDISTOGO_URL'])

In `config/environments/development.rb`:

    ENV['REDISTOGO_URL'] ||= 'redis://localhost:6379'

That works with the "Redis To Go" Heroku add-on in staging/production:

    heroku addons:add redistogo

## Set the data

Each time any object I want to share this namespace is saved, I'll notify
Redis. For example, users:

    class User < ActiveRecord::Base
      validates :handle, presence: true, uniqueness: true

      before_save do
        REDIS.set handle, 'User'
      end
    end

I'm going to set their handle as the key and the value as the object type.

## Rack middleware for the request

When a new request comes in, let's have Redis find the key and if a record is
found, mutate the route for my Rack endpoint.

In `config.ru`:

    require ::File.expand_path('../config/environment',  __FILE__)
    use RedisRouter
    run MyRails::Application

In `app/middleware/redis_router.rb`:

    class RedisRouter
      def initialize(app)
        @app = app
      end

      def call(env)
        intended_resource = env['REQUEST_PATH'].gsub('/', '')

        if type = REDIS.get(intended_resource)
          new_route = "/#{type.underscore.pluralize}/#{intended_resource}"
          env["REQUEST_PATH"] = env["PATH_INFO"] = env["REQUEST_URI"] = new_route
        end

        @app.call(env)
      end
    end

If a record isn't found, Rack will pass through normally.

## Let Rails finish the request

In `config/routes.rb`:

    MyRails::Application.routes.draw do
      get '/users/:handle', to: 'users#show'
      get '/places/:handle', to: 'places#show'
      get '/lists/:handle', to: 'lists#show'
    end

In `app/controllers/users_controller.rb`:

    class UsersController < ApplicationController
      def show
        @user = User.find_by_handle!(params[:handle])
      end
    end

## Would you do it this way

This approach introduces a dependency on Redis and makes two requests to two
separate databases. We could have had one <abbr title="Structured Query
Language">SQL</abbr> query that queried multiple tables.

I like the idea of separating the concern of determining which type of object we
want. That query should be fast and we can avoid messing with database indexes
on our <abbr title="Structured Query Language">SQL</abbr> database for this one
case. We can separate this concern and if necessary, scale this component of the
application independently.
