{"id":13463409,"url":"https://github.com/bwillis/versioncake","last_synced_at":"2025-05-15T04:05:42.484Z","repository":{"id":4426700,"uuid":"5564659","full_name":"bwillis/versioncake","owner":"bwillis","description":":cake: Version Cake is an unobtrusive way to version APIs in your Rails or Rack apps","archived":false,"fork":false,"pushed_at":"2022-07-21T22:54:25.000Z","size":518,"stargazers_count":656,"open_issues_count":5,"forks_count":47,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-04-24T14:01:55.179Z","etag":null,"topics":["api","jbuilder","rails","ruby","versioning"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bwillis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2012-08-26T23:35:38.000Z","updated_at":"2025-02-14T15:50:40.000Z","dependencies_parsed_at":"2022-08-08T07:15:08.397Z","dependency_job_id":null,"html_url":"https://github.com/bwillis/versioncake","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwillis%2Fversioncake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwillis%2Fversioncake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwillis%2Fversioncake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bwillis%2Fversioncake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bwillis","download_url":"https://codeload.github.com/bwillis/versioncake/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254270645,"owners_count":22042859,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","jbuilder","rails","ruby","versioning"],"created_at":"2024-07-31T13:00:52.994Z","updated_at":"2025-05-15T04:05:37.469Z","avatar_url":"https://github.com/bwillis.png","language":"Ruby","readme":"![Version Cake](https://raw.github.com/alicial/versioncake/master/images/versioncake-logo450x100.png)\n\n![Ruby](https://github.com/bwillis/versioncake/workflows/Ruby/badge.svg?branch=master)\n[![Code Climate](https://codeclimate.com/github/bwillis/versioncake.png)](https://codeclimate.com/github/bwillis/versioncake)\n[![Coverage Status](https://coveralls.io/repos/bwillis/versioncake/badge.png?branch=master)](https://coveralls.io/r/bwillis/versioncake)\n[![Gem Version](https://badge.fury.io/rb/versioncake.png)](http://badge.fury.io/rb/versioncake)\n\nCo-authored by Ben Willis ([bwillis](https://github.com/bwillis/)) and Jim Jones ([aantix](https://github.com/aantix)).\n\nVersion Cake is an unobtrusive way to version APIs in your Rails app.\n\n- Easily version any view with their API version:\n\n```ruby\napp/views/posts/\n - index.xml.v1.builder\n - index.xml.v3.builder\n - index.json.v1.jbuilder\n - index.json.v4.jbuilder\n```\n- Gracefully degrade requests to the latest supported version\n- Clients can request API versions through different strategies\n- Dry your controller logic with exposed helpers\n\nCheck out https://github.com/bwillis/350-rest-api-versioning for a comparison of traditional versioning approaches and a versioncake implementation.\n\n## Install\n\n```\ngem install versioncake\nrails g versioncake:install\n```\n\n### Requirements\n\n| Version | Rails 3.2 Support? | Rails 4 Support? | Rails \u003e4.1 Support? | Rails \u003e5 Support? | Rails \u003e5.2 Support? | Rails 6 Support? | Rails 7 Support? | [Rails API](https://github.com/rails-api/rails-api) 0.2 Support? |\n| -------:|:---------:| -------:| -------:| -------:| -------:| -------:| -------:| -------:|\n| [1.0](CHANGELOG.md#100-march-14-2013) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |\n| [1.1](CHANGELOG.md#110-may-18-2013)   | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |\n| [1.2](CHANGELOG.md#120-may-26-2013)   | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |\n| [1.3](CHANGELOG.md#130-sept-26-2013)  | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |\n| [\u003e2.0](CHANGELOG.md#200-feb-6-2014)   | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |\n| [\u003e2.4](CHANGELOG.md#200-feb-6-2014)   | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |\n| [\u003e3.0](CHANGELOG.md#300-aug-3-2015)   | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |\n| [\u003e3.3](CHANGELOG.md#330-may-7-2017)   | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |\n| [\u003e4.0](CHANGELOG.md)                  | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |\n| [\u003e4.1](CHANGELOG.md)                  | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |\n\n## Upgrade v2.0 -\u003e v3.0\n\n### Accept header name changes\n\nThe default accept header was changed from 'X-API-Version' to 'API-Version'. If you require the 'X-' or some other variant, you can specify a custom strategy as outlined in Extraction Strategy section below.\n\n### Configuration changes\n\nConfiguration is now done with an initializer-you can generate a default one with `rails g versioncake:install` and then modify the generated file to match your configuration.\n\n### Configuration changes\n\nThe configuration options for Version Cake have changed:\n\n| Old Name                                     | New Name               |\n| -------------------------------------------- | ---------------------- |\n| config.versioncake.supported_version_numbers | config.resources       |\n| config.versioncake.default_version           | config.missing_version |\n\n## Upgrade v1.* -\u003e v2.0\n\n### Filename changes\nThe major breaking change to require a bump to v2.0 was the order of the extensions. To avoid priority issues with the format (#14), the version number and the format have been swapped.\n\n`index.v1.json.jbuilder` -\u003e `index.json.v1.jbuilder`\n\nTo make it easier to upgrade, run the following command to automatically rename these files:\n\n`versioncake migrate` or `versioncake migrate path/to/views`\n\n### Configuration changes\n\nThe configuration options for Version Cake have been namespaced and slightly renamed. The following is a mapping of the old names to the new names:\n\n| Old Name | New Name |\n| --------------------------------------- | -------------------------------------------- |\n| config.view_versions                    | config.versioncake.supported_version_numbers |\n| config.view_version_extraction_strategy | config.versioncake.extraction_strategy       |\n| config.view_version_string              | config.versioncake.version_key               |\n| config.default_version                  | config.versioncake.default_version           |\n\n## Example\n\nIn this simple example we will outline the code that is introduced to support a change in a version.\n\n### config/application.rb\n```ruby\nVersionCake.setup do |config|\n  config.resources do |r|\n    r.resource %r{.*}, [], [], (1..4)\n  end\n  config.extraction_strategy = :query_parameter # for simplicity\n  config.missing_version = 4\nend\n```\n\nOften times with APIs, depending upon the version, different logic needs to be applied. With the following controller code, the initial value of @posts includes all Post entries.\nBut if the requested API version is three or greater, we're going to eagerly load the associated comments as well.\n\nBeing able to control the logic based on the api version allow you to ensure forwards and backwards compatibility for future changes.\n\n### PostsController\n```ruby\nclass PostsController \u003c ApplicationController\n  def index\n    # shared code for all versions\n    @posts = Post.scoped\n\n    # version 3 or greated supports embedding post comments\n    if request_version \u003e= 3\n      @posts = @posts.includes(:comments)\n    end\n  end\nend\n```\n\nSee the view samples below. The basic top level posts are referenced in views/posts/index.json.v1.jbuilder.\nBut for views/posts/index.json.v4.jbuilder, we utilize the additional related comments.\n\n### Views\n\nNotice the version numbers are denoted by the \"v{version number}\" extension within the file name.\n\n#### views/posts/index.json.v1.jbuilder\n```ruby\njson.array!(@posts) do |post|\n    json.(post, :id, :title)\nend\n```\n\n#### views/posts/index.json.v4.jbuilder\n```ruby\njson.array!(@posts) do |post|\n    json.(post, :id, :title)\n    json.comments post.comments, :id, :text\nend\n```\n\n### Sample Output\n\nWhen a version is specified for which a view doesn't exist, the request degrades and renders the next lowest version number to ensure the API's backwards compatibility.  In the following case, since views/posts/index.json.v3.jbuilder doesn't exist, views/posts/index.json.v1.jbuilder is rendered instead.\n\n#### http://localhost:3000/posts.json?api_version=3\n```javascript\n[\n  {\n    id: 1\n    title: \"Version Cake v0.1.0 Released!\"\n    name: \"Ben\"\n    updated_at: \"2012-09-17T16:23:45Z\"\n  },\n  {\n    id: 2\n    title: \"Version Cake v0.2.0 Released!\"\n    name: \"Jim\"\n    updated_at: \"2012-09-17T16:23:32Z\"\n  }\n]\n```\n\n\nFor a given request, if we specify the version number, and that version of the view exists, that version specific view version will be rendered.  In the below case, views/posts/index.json.v1.jbuilder is rendered.\n\n#### http://localhost:3000/posts.json?api_version=2 or http://localhost:3000/posts.json?api_version=1\n```javascript\n[\n  {\n    id: 1\n    title: \"Version Cake v0.1.0 Released!\"\n    name: \"Ben\"\n    updated_at: \"2012-09-17T16:23:45Z\"\n  },\n  {\n    id: 2\n    title: \"Version Cake v0.2.0 Released!\"\n    name: \"Jim\"\n    updated_at: \"2012-09-17T16:23:32Z\"\n  }\n]\n```\n\n\nWhen no version is specified, the configured `missing_version` will be used to render a view.  In this case, views/posts/index.json.v4.jbuilder.\n\n#### http://localhost:3000/posts.json\n```javascript\n[\n  {\n    id: 1\n    title: \"Version Cake v0.1.0 Released!\"\n    name: \"Ben\"\n    updated_at: \"2012-09-17T16:23:45Z\"\n    comments: [\n      {\n        id: 1\n        text: \"Woah interesting approach on versioning\"\n      }\n    ]\n  },\n  {\n     id: 2\n     title: \"Version Cake v0.2.0 Released!\"\n     name: \"Jim\"\n     updated_at: \"2012-09-17T16:23:32Z\"\n     comments: [\n      {\n        id: 4\n        text: \"These new features are greeeeat!\"\n      }\n    ]\n  }\n]\n```\n\n## How to use\n\n### Configuration\nThe configuration lives in `config/initializers/versioncake.rb`.\n\n#### Versioned Resources\nEach individual resource uri can be identified by a regular expression. For each one it can be customized to have obsolete, deprecated, supported versions.\n\n```ruby\n  config.resources do |r|\n    # r.resource uri_regex, obsolete, deprecated, supported\n\n    # version 2 and 3 are still supported on users resource\n    r.resource %r{/users}, [1], [2,3], [4]\n\n    # all other resources only allow v4\n    r.resource %r{.*}, [1,2,3], [], [4]\n  end\n```\n\n#### Extraction Strategy\n\nYou can also define the way to extract the version. The `extraction_strategy` allows you to set one of the default strategies or provide a proc to set your own. You can also pass it a prioritized array of the strategies.\n```ruby\nconfig.extraction_strategy = :query_parameter # [:http_header, :http_accept_parameter]\n```\nThese are the available strategies:\n\nStrategy | Description | Example\n--- | --- | ---\n:query_parameter | version in the url query parameter, for testing or to override for special case | `http://localhost:3000/posts.json?api_version=1`  (This is the default.)\n:path_parameter | version in the url path parameter | `api/v:api_version/`\n:request_parameter | version that is sent in the body of the request | Good for testing.\n:http_header | Api version HTTP header | `API-Version: 1`\n:http_accept_parameter | HTTP Accept header | `Accept: application/xml; api_version=1` [why do this?](http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned)\ncustom | takes the request object and must return an integer | lambda {\u0026#124;request\u0026#124; request.headers[\"HTTP_X_MY_VERSION\"].to_i } or class ExtractorStrategy; def execute(request);end;end\n\nIf you use the path_parameter strategy with resources routes, you will want to setup your routes.rb config file to capture the api version.  You can do that in a few ways.  If you have just a few api routes you might specify the path directly like this:\n```\nresources :cakes, path: '/api/v:api_version/cakes'\n```\nIf you are using a lot of routes it might be better to keep them all inside a scope like this:\n```\nscope '/api/v:api_version' do\n  resources :cakes\nend\n```\n\n#### Default Version\n\nWhen no version is supplied by a client, the version rendered will be the latest version by default. If you want to override this to another version, set the following property:\n```ruby\nconfig.missing_version = 4\n```\n\n#### Version String\n\nThe extraction strategies use a default string key of `api_version`, but that can be changed:\n```ruby\nconfig.version_key = \"special_version_parameter_name\"\n```\n\n#### Version String\nIf you do not wish to use the magic mapping of the version number to templates it can be disabled:\n```ruby\nconfig.rails_view_versioning = false\n```\n\n#### Response Version\n\nIf a client requests a specific version (or does not) and a version applies to the resource you can configure it to be in the response. Use the following configuration:\n```ruby\nconfig.response_strategy = [:http_content_type, :http_header]\n```\n\n### Version your views\n\nWhen a client makes a request to your controller the latest version of the view will be rendered. The latest version is determined by naming the template or partial with a version number that you configured to support.\n\n```\n- app/views/posts\n    - index.html.erb\n    - edit.html.erb\n    - show.html.erb\n    - show.json.jbuilder\n    - show.json.v1.jbuilder\n    - show.json.v2.jbuilder\n    - new.html.erb\n    - _form.html.erb\n```\n\nIf you start supporting a newer version, v3 for instance, you do not have to copy posts/show.v2 to posts/show.v3. By default, the request for v3 or higher will gracefully degrade to the view that is the newest, supported version, in this case posts/show.v2.\n\n### Controller\n\nYou don't need to do anything special in your controller, but if you find that you want to perform some tasks for a specific version you can use `request_version` and `version_context.resource.latest_version`. This may be updated in the [near future](https://github.com/bwillis/versioncake/issues/1).\n```ruby\ndef index\n  # shared code for all versions\n  @posts = Post.scoped\n\n  # version 3 or greated supports embedding post comments\n  if request_version \u003e= 3\n    @posts = @posts.includes(:comments)\n  end\nend\n```\n### Client requests\n\nWhen a client makes a request it will automatically receive the latest supported version of the view. The client can also request for a specific version by one of the strategies configured by ``view_version_extraction_strategy``.\n\n### Raised exceptions\n\nThese are the types of exceptions VersionCake will raise:\n\n|Exception type|Description|\n|--------------|-----------|\n|VersionCake::UnsupportedVersionError| The version is invalid, too high or too low for the resource.|\n|VersionCake::ObsoleteVersionError|The version is obsolete for the resource.|\n|VersionCake::MissingVersionError|If no `config.missing_version` is specified, this will be raised when no version is in the request.|\n\n### Handling Exceptions\n\nHandling exceptions can simply be done by using Rails `rescue_from` to return app specific messages to the client.\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n\n  ...\n\n  rescue_from VersionCake::UnsupportedVersionError, :with =\u003e :render_unsupported_version\n\n  private\n\n  def render_unsupported_version\n    headers['API-Version-Supported'] = 'false'\n    respond_to do |format|\n      format.json { render json: {message: \"You requested an unsupported version (#{request_version})\"}, status: :unprocessable_entity }\n    end\n  end\n\n  ...\n\nend\n\n```\n\n## How to test\n\nTesting can be painful but here are some easy ways to test different versions of your api using version cake.\n\n### Test configuration\n\nAllowing more extraction strategies during testing can be helpful when needing to override the version.\n```ruby\n# config/environments/test.rb\nconfig.extraction_strategy = [:query_parameter, :request_parameter, :http_header, :http_accept_parameter]\n```\n\n### Testing a specific version\n\nOne way to test a specific version for would be to stub the requested version in the before block:\n```ruby\nbefore do\n  @controller.stubs(:request_version).returns(3)\nend\n```\n\nYou can also test a specific version through a specific strategy such query_parameter or request_parameter strategies (configured in test environment) like so:\n```ruby\n# test/integration/renders_integration_test.rb#L47\ntest \"render version 1 of the partial based on the parameter _api_version\" do\n  get renders_path(\"api_version\" =\u003e \"1\")\n  assert_equal \"index.html.v1.erb\", @response.body\nend\n```\n\n### Testing all supported versions\n\nYou can iterate over all of the supported version numbers by accessing the ```VersionCake.config.versioned_resources.first.available_versions```.\n\n```ruby\nVersionCake.config.versioned_resources.first.available_versions.each do |supported_version|\n  before do\n    @controller.stubs(:request_version).returns(supported_version)\n  end\n\n  test \"all versions render the correct template\" do\n    get :index\n    assert_equal @response.body, \"index.html.v1.erb\"\n  end\nend\n```\n\n# Thanks!\n\nThanks to all those who have helped make Version Cake really sweet:\n\n* [Alicia](https://github.com/alicial)\n* [Rohit](https://github.com/rg)\n* [Sevag](https://github.com/sevagf)\n* [Billy](https://github.com/bcatherall)\n* [Jérémie Meyer de Ville](https://github.com/jeremiemv)\n* [Michael Elfassy](https://github.com/elfassy)\n* [Kelley Reynolds](https://github.com/kreynolds)\n* [Washington L Braga Jr](https://github.com/huoxito)\n* mbradshawabs\n* [Richard Nuno](https://github.com/richardnuno)\n* [Andres Camacho](https://github.com/andresfcamacho)\n* [Yukio Mizuta](https://github.com/untidy-hair)\n* [David Butler](https://github.com/dwbutler)\n* Jeroen K.\n* [Masaya Myojin](https://github.com/mmyoji)\n* [John Hawthorn](https://github.com/jhawthorn)\n* Ersin Akinci\n* [Bartosz Bonisławski](https://github.com/bbonislawski)\n* [Harry Lascelles](https://github.com/hlascelles)\n* [James Carscadden](https://github.com/JamesCarscadden)\n\n# Related Material\n\n## Libraries\n\n- https://github.com/bploetz/versionist\n- https://github.com/filtersquad/rocket_pants\n- https://github.com/lyonrb/biceps\n\n## Discussions\n\n- [Peter Williams on versioning rest web services](http://barelyenough.org/blog/2008/05/versioning-rest-web-services/)\n- [Steve Klabnik on how to version in a resful way](http://blog.steveklabnik.com/posts/2011-07-03-nobody-understands-rest-or-http#i_want_my_api_to_be_versioned)\n- [Rails API project disucssion on versioning](https://github.com/rails-api/rails-api/issues/8)\n- [Railscast on versioning](http://railscasts.com/episodes/350-rest-api-versioning)\n- [Rails core discussion](https://groups.google.com/forum/#!msg/rubyonrails-core/odwmEYYIum0/9POep66BvoMJ)\n- [RubyWeekly](http://rubyweekly.com/archive/119.html)\n- [API building tools on Ruby Toolbox](https://www.ruby-toolbox.com/categories/API_Builders)\n\n# Security issues?\n\nIf you think you have a security vulnerability, please submit the issue and the details to [https://hackerone.com/versioncake](https://hackerone.com/versioncake)\n\n# Questions?\n\nCreate a bug/enhancement/question on github or contact [aantix](https://github.com/aantix) or [bwillis](https://github.com/bwillis) through github.\n\n# License\n\nVersion Cake is released under the MIT license: www.opensource.org/licenses/MIT\n","funding_links":[],"categories":["Web Apps, Services \u0026 Interaction","Ruby","API Builder","API Builder and Discovery","Versions"],"sub_categories":["API Builders"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbwillis%2Fversioncake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbwillis%2Fversioncake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbwillis%2Fversioncake/lists"}