I’m trying to version my API like Stripe has. Below is given the latest API version is 2.
/api/users returns a 301 to /api/v2/users
/api/v1/users returns a 200 of users index at version 1
/api/v3/users returns a 301 to /api/v2/users
/api/asdf/users returns a 301 to /api/v2/users
So that basically anything that doesn’t specify the version links to the latest unless the specified version exists then redirect to it.
This is what I have so far:
scope 'api', :format => :json do
scope 'v:api_version', :api_version => /[12]/ do
resources :users
end
match '/*path', :to => redirect { |params| "/api/v2/#{params[:path]}" }
end
The original form of this answer is wildly different, and can be found here. Just proof that there’s more than one way to skin a cat.
I’ve updated the answer since to use namespaces and to use 301 redirects — rather than the default of 302. Thanks to pixeltrix and Bo Jeanes for the prompting on those things.
You might want to wear a really strong helmet because this is going to blow your mind.
The Rails 3 routing API is super wicked. To write the routes for your API, as per your requirements above, you need just this:
If your mind is still intact after this point, let me explain.
First, we call
namespacewhich is super handy for when you want a bunch of routes scoped to a specific path and module that are similarly named. In this case, we want all routes inside the block for ournamespaceto be scoped to controllers within theApimodule and all requests to paths inside this route will be prefixed withapi. Requests such as/api/v2/users, ya know?Inside the namespace, we define two more namespaces (woah!). This time we’re defining the "v1" namespace, so all routes for the controllers here will be inside the
V1module inside theApimodule:Api::V1. By definingresources :usersinside this route, the controller will be located atApi::V1::UsersController. This is version 1, and you get there by making requests like/api/v1/users.Version 2 is only a tiny bit different. Instead of the controller serving it being at
Api::V1::UsersController, it’s now atApi::V2::UsersController. You get there by making requests like/api/v2/users.Next, a
matchis used. This will match all API routes that go to things like/api/v3/users.This is the part I had to look up. The
:to =>option allows you to specify that a specific request should be redirected somewhere else — I knew that much — but I didn’t know how to get it to redirect to somewhere else and pass in a piece of the original request along with it.To do this, we call the
redirectmethod and pass it a string with a special-interpolated%{path}parameter. When a request comes in that matches this finalmatch, it will interpolate thepathparameter into the location of%{path}inside the string and redirect the user to where they need to go.Finally, we use another
matchto route all remaining paths prefixed with/apiand redirect them to/api/v2/%{path}. This means requests like/api/userswill go to/api/v2/users.I couldn’t figure out how to get
/api/asdf/usersto match, because how do you determine if that is supposed to be a request to/api/<resource>/<identifier>or/api/<version>/<resource>?