Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - ace/versionist: A plugin for versioning Rails based RESTful APIs. · GitHub
Skip to content

Repository files navigation

versionist

A plugin for versioning Rails based RESTful APIs. Versionist supports three versioning strategies out of the box:

  • Specifying version via an HTTP header
  • Specifying version by prepending paths with a version slug
  • Specifying version via a request parameter

A version of your API consists of:

  • Namespaced controllers/routes
  • Namespaced presenters
  • Namespaced tests
  • Documentation

Versionist includes Rails generators for generating new versions of your API as well as new components within an existing version.

Installation

Add the following dependency to your Rails application's Gemfile file and run bundle install:

gem 'versionist'

Configuration

Versionist provides the method api_version that you use in your Rails application's config/routes.rb file to constrain a collection of routes to a specific version of your API. The versioning strategies used by the collection of routes constrained by api_version is set by specifying :header, :path, and/or :parameter (and their supporting values) in the configuration Hash passed to api_version. You configure the module namespace for your API version by specifying :module in the configuration Hash passed to api_version.

Upgrading from Versionist 0.x to 1.x+

A backwards incompatible change was made to the format of the configuration hash passed to api_version starting in Versionist 1.0. Prior to 1.0, api_version expected hashes with the following structure:

api_version(:module=>"V1",:header=>"Accept",:value=>"application/vnd.mycompany.com; version=1")do
...
end

In order to support multiple concurrent versioning strategies per api version, api_version expects that the :header, :parameter, and :path keys point to hashes and contain the required keys.

api_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})do
...
endapi_version(:module=>"V1",:parameter=>{:name=>"version",:value=>"1"})do
...
endapi_version(:module=>"V1",:path=>{:value=>"v1"})do
...
end

An error will be thrown at startup if your config/routes.rb file contains 0.x style api_version entries when running with Versionist 1.x+.

Versioning Strategies

HTTP Header

This strategy uses an HTTP header to request a specific version of your API.

Accept: application/vnd.mycompany.com; version=1,application/json
GET /foos

You configure the header to be inspected and the header value specifying the version in the configuration Hash passed to api_version.

Examples:

Content negotiation via the Accept header:
MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Accept Header Gotcha

Please note: when your routes do not include an explicit format in the URL (i.e. match 'foos.(:format)' => foos#index), Rails inspects the Accept header to determine the requested format. Since an Accept header can have multiple values, Rails uses the first one present to determine the format. If your custom version header happens to be the first value in the Accept header, Rails would incorrectly try to interpret it as the format. If you use the Accept header, Versionist will move your custom version header (if found) to the end of the Accept header so as to not interfere with Rails' format resolution logic. This is the only case where Versionist will alter the incoming request.

Custom header:
MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Path

This strategy uses a URL path prefix to request a specific version of your API.

GET /v3/foos

You configure the path version prefix to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V3",:path=>{:value=>"v3"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Request Parameter

This strategy uses a request parameter to request a specific version of your API.

GET /foos?version=v2

You configure the parameter name and value to be applied to the routes.

Example:

MyApi::Application.routes.drawdoapi_version(:module=>"V2",:parameter=>{:name=>"version",:value=>"v2"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Default Version

If a request is made to your API without specifying a specific version, by default a RoutingError (i.e. 404) will occur. You can optionally configure Versionist to return a specific version by default when none is specified. To specify that a version should be used as the default, include :default => true in the config hash passed to the api_version method.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

If you attempt to specify more than one default version, an error will be thrown at startup.

Rails Route :defaults Hash

The api_version method also supports Rails' :defaults hash (note that this is different than the :default key which controls the default API version described above). If a :defaults hash is passed to api_version, it will be applied to the collection of routes constrainted by api_version.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V20120317",:header=>{:name=>"API-VERSION",:value=>"v20120317"},:defaults=>{:format=>:json},:default=>true)domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

Multiple Versioning Strategies Per API Version

An API version may optionally support multiple concurrent versioning strategies.

Example.

MyApi::Application.routes.drawdoapi_version(:module=>"V1",:header=>{:name=>"Accept",:value=>"application/vnd.mycompany.com; version=1"},:path=>{:value=>"v1"})domatch'/foos.(:format)'=>'foos#index',:via=>:getmatch'/foos_no_format'=>'foos#index',:via=>:getresources:barsendend

A Note About Testing When Using The HTTP Header or Request Parameter Strategies

Rails functional tests (ActionController::TestCase) and RSpec Controller specs are for testing controller action methods in isolation. They do not go through the full Rails stack, specifically the Rails dispatcher code path, which is where versionist hooks in to do its thing.

In order to test your versioned API routes which rely on the HTTP Header or Request Parameter strategies, use integration tests (ActionDispatch::IntegrationTest) if you're using Test::Unit, or Request specs if you're using RSpec.

Test::Unit Example:

# test/integration/v1/test_controller_test.rbrequire'test_helper'classV1::TestControllerTest < ActionDispatch::IntegrationTesttest"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",@response.bodyendend

RSpec Example:

# spec/requests/v1/test_controller_spec.rbrequire'spec_helper'describeV1::TestControllerdoit"should get v1"doget'/test',{},{'Accept'=>'application/vnd.mycompany.com; version=1'}assert_response200assert_equal"v1",response.bodyendendend

Generators

Versionist comes with generators to facilitate managing the versions of your API. To see the available generators, simply run rails generate, and you will see the versionist generators under the versionist namespace.

The following generators are available:

versionist:new_api_version

creates the infrastructure for a new API version. This will create:

  • A new controller namespace, base controller and test
  • A new presenters namespace, base presenter and test
  • A new documentation directory and base files

Usage

rails generate versionist:new_api_version <version> <module namespace> [options]

Examples:

# HTTP header versioning strategy
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
# request parameter versioning strategy
rails generate versionist:new_api_version v2 V2 --parameter=name:version value:2
# path versioning strategy
rails generate versionist:new_api_version v2 V2 --path=value:v2
# multiple versioning strategies
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2" --parameter=name:version value:2
# default version
rails generate versionist:new_api_version v2 V2 --path=value:v2 --default
# route :defaults hash
rails generate versionist:new_api_version v2 V2 --path=value:v2 --defaults=format:json
rails generate versionist:new_api_version v2 V2 --header=name:Accept value:"application/vnd.mycompany.com; version=2"
route api_version(:module => "V2", :header => {:name => "Accept", :value => "application/vnd.mycompany.com; version=2"}) do
end
create app/controllers/v2
create app/controllers/v2/base_controller.rb
create spec/controllers/v2
create spec/controllers/v2/base_controller_spec.rb
create spec/requests/v2
create spec/requests/v2/base_controller_spec.rb
create app/presenters/v2
create app/presenters/v2/base_presenter.rb
create spec/presenters/v2
create spec/presenters/v2/base_presenter_spec.rb
create app/helpers/v2
create spec/helpers/v2
create public/docs/v2
create public/docs/v2/index.html
create public/docs/v2/style.css

versionist:new_controller

creates a new controller class with the given name under the given version module.

Usage

rails generate versionist:new_controller <name> <module namespace>

Example:

rails generate versionist:new_controller foos V2
create app/controllers/v2/foos_controller.rb
create spec/controllers/v2/foos_controller_spec.rb
create spec/requests/v2/foos_controller_spec.rb

versionist:new_presenter

creates a new presenter class with the given name under the given version module.

Usage

rails generate versionist:new_presenter <name> <module namespace>

Example:

rails generate versionist:new_presenter foos V2
create app/presenters/v2/foos_presenter.rb
create spec/presenters/v2/foos_presenter_spec.rb

versionist:copy_api_version

copies an existing API version to a new API version. This will do the following:

  • Copy all existing routes in config/routes.rb from the old API version to routes for the new API version in config/routes.rb (see note below)
  • Copy all existing controllers and tests from the old API version to the new API version
  • Copy all existing presenters and tests from the old API version to the new API version
  • Copy all existing helpers and tests from the old API version to the new API version
  • Copy all documentation from the old API version to the new API version

Note: routes can only be copied with MRI Ruby 1.9 and above, as this feature relies on Ripper which is only available in stdlib in MRI Ruby 1.9 and above. Outside of routes copying, the other copy steps will work just fine in Ruby 1.8 and other non-MRI Ruby implementations.

Usage

rails generate versionist:copy_api_version <old version> <old module namespace> <new version> <new module namespace>

Example:

rails generate versionist:copy_api_version v2 V2 v3 V3
route api_version(:module => "V3", :header=>"Accept", :value=>"application/vnd.mycompany.com; version=3") do
end
Copying all files from app/controllers/v2 to app/controllers/v3
Copying all files from spec/controllers/v2 to spec/controllers/v3
Copying all files from app/presenters/v2 to app/presenters/v3
Copying all files from spec/presenters/v2 to spec/presenters/v3
Copying all files from app/helpers/v2 to app/helpers/v3
Copying all files from spec/helpers/v2 to spec/helpers/v3
Copying all files from public/docs/v2 to public/docs/v3

About

A plugin for versioning Rails based RESTful APIs.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages