How to serve the latest API through application mime - Rails - ruby-on-rails

Consider the following
namespace :api, defaults: {format: 'json'} do
namespace :v1 do
resources :users
resources :profiles
end
namespace :v2 do
resources :users
resources :profiles
end
end
instead of routing to api/v1 and api/v2, what i would like is set the accept header to use which ever i want it to serve and use the path api neglecting the version.
So if i set it to serve api/v1 the path should just be localhost:3000/api
i was going through this railcasts video
http://railscasts.com/episodes/350-rest-api-versioning?autoplay=true
but looking in comments there are variable issues with the code and the solution at time.
What is the best solution at the moment?
Replies from that link
Each time you create a new MIME type name (application/vnd.example.v1, application/vnd.example.v2, ...),
a kitten is hurt! don't do that! that's bad!
Use MIME parameters instead: application/vnd.example; version=1, application/vnd.example; version=2, ...
(version=, or level=, or v= or whatever you want, this is part of your design).
The semantic of a MIME type is that every document "flagged" with that type share the same "lineage",
and that two different names are not related, even if they share a same prefix.
By incorporating a version identifier into the MIME type name,
you are making the names distinct and are in the same case as if your V1 API was using text/html documents
whereas your V2 now uses image/png pictures instead.
The problem with the default is it will accept any mime type, as it will only check the boolean and not the string:
#default || req.headers['Accept'].include?("application/vnd.example.v#{#version}")
I mean, if we're expecting "application/vnd.github.v1", it will also accept "application/vnd.twitter.v1"

I've used the approach described in that Railscast in several projects, and haven't had any problems with it. I think it is a really good and up to date solution.
Place this in your /lib directory:
class ApiConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.yourdomain.v#{#version}")
end
end
Then define your routes as follows:
namespace :api, defaults: {format: 'json'} do
scope module: :v1, constraints: ApiConstraints.new(version: 1) do
resources :users
resources :profiles
end
scope module: :v2, constraints: ApiConstraints.new(version: 2, default: true) do
resources :users
resources :profiles
end
end
You can now access the API with end points like:
https://www.yourdomain.com/api/users
https://www.yourdomain.com/api/profiles
Which will serve up version 2 by default.
Or you can set an Accepts header like:
Accept: application/vnd.yourdomain.v1
and you'll get version 1 instead.
What's wrong with that?

Related

generate route to the namespace itself

I have multiple different namespaces like this:
namespace :some_category do
resources :orders
resources :some_foo
get 'aaa', to: 'aa#aa'
get 'bbb', to: 'bb#bb'
# etc
end
namespace :other_namespace do
resources :foo
resources :bar
get 'ccc', to: 'cc#cc'
get 'ddd', to: 'dd#dd'
# etc
end
namespace :etc do
end
Now, the routes are created correctly. Unfortunately, there aren't any routes generated for the namespaces itself. I would like to also have site.com/some_category and site.com/other_namespace.
should I just add outside the namespaces
get 'some_category', to: 'foo#bar'
get 'other_namespace', to: 'foo#baz'
Or is that bad practice?
I would like to make them as some kind of dashboards. Is it possible to generate links to all available subroutes?
I don't like to build a custom dashboard for each namespace. It's not very DRY, because all the dashboards are the same: it is just a list of links, that belongs in that namespace. Is there some code for:
Get the namespace of which I am the dashboard --> get all routes that are inside that namespace --> generate a link for each of those routes

Rails resources route with dynamic prefix instead of controller name

I have a TextsController, each Text can be of a different (fixed) type.
Let's say I have a "book" type. I want to create a resource route to show a text, and I want the route to look like this:
/book/my-book
Another type, "manual" for instance, should lead to using the following URL:
/manual/rtfm
Well, I have RTFM and I can't get it to work the way I thought it should work.
Here's what I've tried:
scope '/:text_type' do
resources :texts, only: :show
end
rake routes shows me the following route spec:
text GET /:text_type/texts/:id(.:format) texts#show
I don't get why the static 'texts' segment should be there?
So I tried including an empty path option:
scope '/:text_type', path: '' do
resources :texts, only: :show
end
Which doesn't change anything, I guess because (from source) my first argument to scope actually overrides any value given to path.
The only route setup that got me what I'm looking for is this:
scope '/:text_type' do
resources :texts, only: :show, path: ''
end
It seems to completely defeat the purpose of scope which is to "[scope] a set of routes to the given default options".
Why wouldn't any of the previous forms actually override path for my resources call?
Looks like a bug to me?
So should I file a bug report, or will you hit me hard on the head with the f* manual? ^^
First of all the scoping thing. Routes with scope are for namespacing routes, as you would do for admin areas. So the mentioned routes are generated correctly and there is no bug (and no bug report needed). You can read more details about namespacing at Controller Namespaces and Routing.
You could slug the parameters yourself by following 'Creating Vanity URLs in Rails'
or use the friendly_id gem like the Railscast advises.
Though I would stick to ids as long as I could for several reasons.

API Versioning for Rails Routes

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:
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>?
A couple of things to add:
Your redirect match isn't going to work for certain routes - the *api param is greedy and will swallow up everything, e.g. /api/asdf/users/1 will redirect to /api/v2/1. You'd be better off using a regular param like :api. Admittedly it won't match cases like /api/asdf/asdf/users/1 but if you have nested resources in your api it's a better solution.
Ryan WHY U NO LIKE namespace? :-), e.g:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v2, &current_api_routes
namespace :v2, &current_api_routes
namespace :v1, &current_api_routes
match ":api/*path", :to => redirect("/api/v2/%{path}")
end
Which has the added benefit of versioned and generic named routes. One additional note - the convention when using :module is to use underscore notation, e.g: api/v1 not 'Api::V1'. At one point the latter didn't work but I believe it was fixed in Rails 3.1.
Also, when you release v3 of your API the routes would be updated like this:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v3, &current_api_routes
namespace :v3, &current_api_routes
namespace :v2, &current_api_routes
namespace :v1, &current_api_routes
match ":api/*path", :to => redirect("/api/v3/%{path}")
end
Of course it's likely that your API has different routes between versions in which case you can do this:
current_api_routes = lambda do
# Define latest API
end
namespace :api do
scope :module => :v3, &current_api_routes
namespace :v3, &current_api_routes
namespace :v2 do
# Define API v2 routes
end
namespace :v1 do
# Define API v1 routes
end
match ":api/*path", :to => redirect("/api/v3/%{path}")
end
If at all possible, I would suggest rethinking your urls so that the version isn't in the url, but is put into the accepts header. This stack overflow answer goes into it well:
Best practices for API versioning?
and this link shows exactly how to do that with rails routing:
http://freelancing-gods.com/posts/versioning_your_ap_is
I'm not a big fan of versioning by routes. We built VersionCake to support an easier form of API versioning.
By including the API version number in the filename of each of our respective views (jbuilder, RABL, etc), we keep the versioning unobtrusive and allow for easy degradation to support backwards compatibility (e.g. if v5 of the view doesn't exist, we render v4 of the view).
I'm not sure why you want to redirect to a specific version if a version isn't explicitly requested. Seems like you simply want to define a default version that gets served up if no version is explicitly requested. I also agree with David Bock that keeping versions out of the URL structure is a cleaner way to support versioning.
Shameless plug: Versionist supports these use cases (and more).
https://github.com/bploetz/versionist
Implemented this today and found what I believe to be the 'right way' on RailsCasts - REST API Versioning. So simple. So maintainable. So effective.
Add lib/api_constraints.rb (don't even have to change vnd.example.)
class ApiConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.example.v#{#version}")
end
end
Setup config/routes.rb like so
require 'api_constraints'
Rails.application.routes.draw do
# Squads API
namespace :api do
# ApiConstaints is a lib file to allow default API versions,
# this will help prevent having to change link names from /api/v1/squads to /api/squads, better maintainability
scope module: :v1, constraints: ApiConstraints.new(version:1, default: true) do
resources :squads do
# my stuff was here
end
end
end
resources :squads
root to: 'site#index'
Edit your controller (ie /controllers/api/v1/squads_controller.rb)
module Api
module V1
class SquadsController < BaseController
# my stuff was here
end
end
end
Then you can change all links in your app from /api/v1/squads to /api/squads and you can EASILY implement new api versions without even having to change links
Ryan Bigg answer worked for me.
If you also want to keep query parameters through the redirect, you can do it like this:
match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }

How to get alternative info from a routing namespace in rails?

Imagine you are working on a f,acebook(to skip the g,f,w) like site, and you need some routes like:
www.mydomain.com/ihome/jim/posts
www.mydomain.com/ihome/jim/post/3
www.mydomain.com/ihome/jim/posts/3/edit.
Then how to set the routes to get the 'jim' part? I know I can use the following if there is no account part:
namespace :ihome do
resources :posts
end
A quick (untested) answer is : use the scope, it will give you a params[:user]
namespace :ihome do
scope ":user" do
resources :posts
end
end
Have a look at the docs here : http://guides.rubyonrails.org/routing.html#defining-defaults

about rails route

if i use
namespace :helpcenter do
get "hh/logout"
get 'hh/login'
end
it will match the url helpcenter/hh/logout
my question is how to let these method mapping to the url /hh/logout didn't contains the module name
You can use a scope to achieve this:
scope :module => 'helpcenter' do
resources :articles
end
This will generate a mapping for /articles, /articles/new etc and not helpcenter/articles. It will however still route to the articles controller in the helpcenter namespace. e.g.: app/controllers/helpcenter/articles_controller.rb
Hope that helps.

Resources