Module: Maturate

Defined in:
lib/maturate.rb

Overview

Add version behaviors to a controller that extends this module.

If you have an API-only rails 4 application, you’ll want to extend ApplicationController with this module.

Example:

class ApplicationController < ActionController::Base
  extend Maturate
  self.api_versions = ['alpha', 'beta', 'gamma']

  # by default the current_api_version would be the last in the list
  # but maybe that is in pre-release status
  self.current_api_version = 'beta'
end

Routing

Maturate requires the api_version parameter to be set. Putting the api_version in the route is a good interface for clients and makes it easy to manage your versions through the routes file.

Example:

scope '/api' do
  scope '/:api_version' do
    # ...
  end
end

This method means keeping all endpoints from all your api versions defined in the scope. You can return 404 from your controllers based on api_version to manage endpoints that are not in particular versions of the API.

class HumansController
  def index
    four_oh_four if api_version != 'alpha'
    @resources = Human.all
  end

  private

  def four_oh_four
    render text: 'Page Not Found', status: 404
  end
end

You could also be more explicit in your route file:

namespace '/api' do
  namespace '/alpha', defaults: {api_version: 'alpha'} do
    # ....
  end

  namespace '/gamma', defaults {api_version: 'gamma'} do
    # ...
  end

  # The for the current api should still be parametrized,
  # so +current+ and invalid versions will match.
  namespace '/:api_version' do
    # ...
  end
end

You can also use lambdas to share routes, if that makes sense for you:

shared_routes = lambda do
  resources :humans, only: [:show]
end

namespace '/alpha', defaults: {api_version: 'alpha'} do
  shared_routes.call
  # more routes just in alpha...
end

# if the namespace *only* has the common routes
namespace '/gamma', defaults: {api_version: 'gamma'}, &shared_routes

Views and Variants

It adds a before_action that will set request.variant to the current api version. This allows you to create different view files for different versions of your api, but allows you to use the same controller action to serve all versions.

For example, given API versions “1.0”, “1.1”, and “2.0” that all serve the same Human resource, you can have files “humans/index.json+1.0.jbuilder”, “humans/index.html+1.1.jbuilder”, and “humans/index.json.jbuilder”, where the first two revisions are explictily served their correct variant (as denoted by “+1.x” in the file name) and the 2.0 API is served by the less explicit view file with no variant.

Rails introduced variants to deal with different clients, eg render a tablet or phone optimized view for content. In the case of an API you don’t want to discriminate against types of user-agents, so the only variant you would reasonably introduce is based on versioning.

Controllers

If you have one controller that serves many versions of the API, you may want to add some includes or other methods to your query scopes.

def show
  @resoruces = Humans.all
  if api_version == 'gamma'
    @resources.includes(:compensation_packages)
  end
end

If the methods diverge by a large amount:

def show
  if api_version == 'gamma'
    show_gamma
  else
    show_legacy
  end
end

private

def show_legacy
end

def show_gamma
end

Or, if the available actions in a controller greatly diverge between versions you can stick a version’s controllers in their own namespace, a la Versionist.

# config/routes.rb
namespace '/alpha', defaults: {api_version: 'alpha'} do
  resources :humans, controller: AlphaVersion::Humans
end

Defined Under Namespace

Modules: InstanceMethods Classes: InvalidVersion

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(kls) ⇒ Object



145
146
147
148
149
# File 'lib/maturate.rb', line 145

def self.extended kls
  kls.send :include, InstanceMethods
  setup_api_versioning kls
  setup_request_handling kls
end

Instance Method Details

#current_api_version=(str) ⇒ Object



151
152
153
154
155
156
157
158
# File 'lib/maturate.rb', line 151

def current_api_version= str
  unless api_versions.include?(str)
    msg = "#{str} is not a known version. Known: #{api_versions}. " +
          "Use `self.api_versions = [...]` to set known versions."
    raise InvalidVersion, msg
  end
  self._current_api_version = str
end

#skip_versioned_url_generation(opts = {}) ⇒ Object



160
161
162
# File 'lib/maturate.rb', line 160

def skip_versioned_url_generation opts={}
  prepend_before_action :skip_versioned_url_generation, opts
end