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:
namespace :api do
namespace :v1 do
resources :users
end
namespace :v2 do
resources :users
end
match 'v:api/*path', :to => redirect("/api/v2/%{path}")
match '*path', :to => redirect("/api/v2/%{path}")
end
If your mind is still intact after this point, let me explain.
First, we call namespace
which 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 our namespace
to be scoped to controllers within the Api
module and all requests to paths inside this route will be prefixed with api
. 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 V1
module inside the Api
module: Api::V1
. By defining resources :users
inside this route, the controller will be located at Api::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 at Api::V2::UsersController
. You get there by making requests like /api/v2/users
.
Next, a match
is 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 redirect
method and pass it a string with a special-interpolated %{path}
parameter. When a request comes in that matches this final match
, it will interpolate the path
parameter into the location of %{path}
inside the string and redirect the user to where they need to go.
Finally, we use another match
to route all remaining paths prefixed with /api
and redirect them to /api/v2/%{path}
. This means requests like /api/users
will go to /api/v2/users
.
I couldn't figure out how to get /api/asdf/users
to match, because how do you determine if that is supposed to be a request to /api/<resource>/<identifier>
or /api/<version>/<resource>
?
Anyway, this was fun to research and I hope it helps you!
Best Answer
The data services client and server do not do version negotation at connection time - they do it for every request. Each request or respond includes a version header that indicates what version of client or server is required to service that request. This means that a downlevel client can communicate with an up-level server so long as the server can respond to those requests without doing anything that requires it to up the version number of the response. Features that require the service to use higher version responses are all off by default.
What this means is that as new version of Data Services are published, the client and server will continue to be able to communicate with each other regardless of which version is installed on the client so long as new features have not been enabled on the server that require a higher version client to respond.