api-versions 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/License.txt +22 -0
- data/README.md +54 -12
- data/api-versions.gemspec +4 -3
- data/lib/api-versions.rb +2 -0
- data/lib/api-versions/dsl.rb +1 -1
- data/lib/api-versions/simplify_format.rb +15 -0
- data/lib/api-versions/version.rb +1 -1
- data/lib/api-versions/version_check.rb +3 -3
- data/lib/generators/api_versions/bump_generator.rb +29 -0
- data/lib/generators/api_versions/templates/controller.rb +2 -0
- data/spec/controllers/application_controller_spec.rb +31 -0
- data/spec/dummy/app/controllers/api/v2/users_controller.rb +2 -0
- data/spec/dummy/app/controllers/api/v3/foo_controller.rb +2 -0
- data/spec/dummy/app/controllers/api/v3/nests/nested_controller.rb +2 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/generators/bump_generator_spec.rb +40 -0
- data/spec/spec_helper.rb +1 -0
- metadata +39 -9
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/License.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2012 Erich Menge
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,13 +1,39 @@
|
|
1
|
-
|
2
|
-
======================================================================================================================================
|
1
|
+
# api-versions [](http://travis-ci.org/erichmenge/api-versions) #
|
3
2
|
|
4
|
-
If you have multiple versions of an API in your Rails app, it is not very DRY to include the same resources over and over again.
|
5
3
|
|
6
|
-
|
4
|
+
#### api-versions is a Gem to help you manage your Rails API endpoints.
|
5
|
+
api-versions is very lightweight. It adds a generator and only one method to the Rails route mapper.
|
7
6
|
|
7
|
+
##### It helps you in three ways:
|
8
|
+
|
9
|
+
* Provides a DSL for versioning your API in your routes file, favoring client headers vs changing the resource URLs.
|
10
|
+
* Provides methods to cache and retrieve resources in your routes file to keep it from getting cluttered
|
11
|
+
* Provides a generator to bump your API controllers to the next version, while inheriting the previous version.
|
12
|
+
|
13
|
+
*See below for more details on each of these topics*
|
14
|
+
|
15
|
+
#### Assumptions api-versions makes:
|
16
|
+
* You want the client to use headers to specify the API version instead of changing the URL. (`Accept` header of `application/vnd.myvendor+json;version=1` for example)
|
17
|
+
* You specify your API version in whole integers. v1, v2, v3, etc.
|
18
|
+
If you need semantic versioning for an API you're likely making too many backwards incompatible changes. API versions should not change all that often.
|
19
|
+
* Your API controllers will live under the `api/v{n}/` directory. For example `app/controllers/api/v1/authorizations_controller.rb`.
|
20
|
+
|
21
|
+
## Installation
|
8
22
|
In your Gemfile:
|
9
23
|
|
10
|
-
gem "api-versions", "~> 0.
|
24
|
+
gem "api-versions", "~> 0.2.0"
|
25
|
+
|
26
|
+
## Versions are specified by header, not by URL
|
27
|
+
A lot of APIs are versioned by changing the URL. `http://test.host/api/v1/some_resource/new` for example.
|
28
|
+
But is some_resource different from version 1 to version 2? It is likely the same resource, it is simply the interface that is changing.
|
29
|
+
api-versions prefers the URLs stay the same. `http://test.host/api/some_resource/new` need not ever change (so long as the resource exists). The client specifies how it wants to interface with
|
30
|
+
this resource with the `Accept` header. So if the client wants version 2 of the API, the `Accept` header might look like this: `application/vnd.myvendor+json;version=2`. A complete example is
|
31
|
+
below.
|
32
|
+
|
33
|
+
## DSL ##
|
34
|
+
api-versions provides a (very) lightweight DSL for your routes file. Everything having to do with your routes API lives in the api block. This DSL helps you version your API as well as providing a caching mechanism to prevent the need of copy/pasting the same resources into new versions of the API.
|
35
|
+
|
36
|
+
For example:
|
11
37
|
|
12
38
|
In your routes.rb file:
|
13
39
|
|
@@ -44,7 +70,8 @@ In your routes.rb file:
|
|
44
70
|
DELETE /api/authorizations/:id(.:format) api/v2/authorizations#destroy
|
45
71
|
|
46
72
|
|
47
|
-
Then the client simply sets the Accept header
|
73
|
+
Then the client simply sets the Accept header `application/vnd.myvendor+json;version=1`. If no version is specified, the default version you set will be assumed.
|
74
|
+
You'll of course still need to copy all of your controllers over, even if they haven't changed from version to version. At least you'll remove a bit of the mess in your routes file.
|
48
75
|
|
49
76
|
A more complicated example:
|
50
77
|
|
@@ -133,13 +160,28 @@ And finally `rake routes` outputs:
|
|
133
160
|
GET /api/my_new_resource/:id(.:format) api/v3/my_new_resource#show
|
134
161
|
PUT /api/my_new_resource/:id(.:format) api/v3/my_new_resource#update
|
135
162
|
DELETE /api/my_new_resource/:id(.:format) api/v3/my_new_resource#destroy
|
163
|
+
## api_versions:bump
|
164
|
+
The api-versions gem provides a Rails generator called `api_versions:bump`. This generator will go through all of your API controllers and find the highest version number and bump
|
165
|
+
all controllers with it up to the next in sequence.
|
136
166
|
|
137
|
-
|
138
|
-
=======
|
139
|
-
Copyright (c) 2012 Erich Menge
|
167
|
+
If for example you have a controller `api/v1/authorizations_controller.rb` it will create `api/v2/authorizations_controller.rb` and inside:
|
140
168
|
|
141
|
-
|
169
|
+
``` ruby
|
170
|
+
class Api::V2::AuthorizationsController < Api::V1::AuthorizationsController
|
171
|
+
end
|
172
|
+
```
|
173
|
+
|
174
|
+
So instead of copying your prior version controllers over to the new ones and duplicating all the code in them, you can redefine specific methods,
|
175
|
+
or start from scratch by removing the inheritance.
|
142
176
|
|
143
|
-
|
177
|
+
## A word on Rails responders
|
178
|
+
If you use `responds_to` and `responds_with` you'll run into a bit of a problem. Rails doesn't understand the Accept header so you'll get a 406 Unacceptable when you use `respond_to`.
|
179
|
+
To get around this I suggest creating a base API controller and including ApiVersions::SimplifyFormat, which will set the format to the one specified with the + symbol in the Accept header.
|
180
|
+
|
181
|
+
``` ruby
|
182
|
+
class Api::V1::BaseController < ActionController::Base
|
183
|
+
include ApiVersions::SimplifyFormat
|
184
|
+
end
|
185
|
+
```
|
144
186
|
|
145
|
-
|
187
|
+
This will set `request.format` accordingly.
|
data/api-versions.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Erich Menge"]
|
9
9
|
s.email = ["erich.menge@me.com"]
|
10
10
|
s.homepage = "https://github.com/erichmenge/api-versions"
|
11
|
-
s.summary = "
|
12
|
-
s.description = "
|
11
|
+
s.summary = "An API versioning gem for Rails."
|
12
|
+
s.description = "api-versions helps manage your app's API endpoints."
|
13
13
|
|
14
14
|
s.rubyforge_project = "api-versions"
|
15
15
|
|
@@ -18,5 +18,6 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_development_dependency "rspec-rails"
|
21
|
+
s.add_development_dependency "rspec-rails", "~> 2.0"
|
22
|
+
s.add_development_dependency 'ammeter', '0.2.5'
|
22
23
|
end
|
data/lib/api-versions.rb
CHANGED
data/lib/api-versions/dsl.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
module ApiVersions
|
2
|
+
module SimplifyFormat
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_filter :simplify_format
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def simplify_format
|
12
|
+
request.headers['Accept'] && request.headers['Accept'].match(/\+\s*(.+?)[;\s\z]/) { |m| request.format = m[1] }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/api-versions/version.rb
CHANGED
@@ -13,15 +13,15 @@ module ApiVersions
|
|
13
13
|
private
|
14
14
|
|
15
15
|
def accepts_proper_format?(request)
|
16
|
-
|
16
|
+
request.headers['Accept'] =~ /\Aapplication\/vnd\.#{self.class.vendor_string}\+.+/
|
17
17
|
end
|
18
18
|
|
19
19
|
def matches_version?(request)
|
20
|
-
|
20
|
+
request.headers['Accept'] =~ /version\s*?=\s*?#{@process_version}\b/
|
21
21
|
end
|
22
22
|
|
23
23
|
def unversioned?(request)
|
24
|
-
|
24
|
+
@process_version == self.class.default_version && !(request.headers['Accept'] =~ /version\s*?=\s*?\d*\b/i)
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ApiVersions
|
2
|
+
module Generators
|
3
|
+
class BumpGenerator < Rails::Generators::Base
|
4
|
+
desc "Bump API version to next version, initializing controllers"
|
5
|
+
source_root File.expand_path('../templates', __FILE__)
|
6
|
+
|
7
|
+
def get_controllers
|
8
|
+
Dir.chdir File.join(Rails.root, 'app', 'controllers') do
|
9
|
+
@controllers = Dir.glob('api/v**/**/*.rb')
|
10
|
+
end
|
11
|
+
|
12
|
+
@highest_version = @controllers.map do |controller|
|
13
|
+
controller.match(/api\/v(\d+?)\//)[1]
|
14
|
+
end.max
|
15
|
+
|
16
|
+
@controllers.keep_if { |element| element =~ /api\/v#{@highest_version}\// }
|
17
|
+
end
|
18
|
+
|
19
|
+
def generate_new_controllers
|
20
|
+
@controllers.each do |controller|
|
21
|
+
new_controller = controller.gsub /api\/v#{@highest_version}\//, "api/v#{@highest_version.to_i + 1}/"
|
22
|
+
@current_new_controller = new_controller.chomp(File.extname(controller)).camelize
|
23
|
+
@current_old_controller = controller.chomp(File.extname(controller)).camelize
|
24
|
+
template 'controller.rb', File.join('app', 'controllers', new_controller)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ApplicationController < ActionController::Base
|
4
|
+
include ApiVersions::SimplifyFormat
|
5
|
+
|
6
|
+
def index
|
7
|
+
render nothing: true, status: 200
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe ApplicationController do
|
12
|
+
describe "simplify format" do
|
13
|
+
it "should set the format" do
|
14
|
+
request.env['Accept'] = 'application/vnd.myvendor+json;version=1'
|
15
|
+
get :index
|
16
|
+
request.format.should == 'application/json'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when the header is not set" do
|
21
|
+
it "should be successful" do
|
22
|
+
get :index
|
23
|
+
response.should be_success
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not change the format" do
|
27
|
+
get :index
|
28
|
+
request.format.should == "text/html"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/dummy/config/routes.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'generators/api_versions/bump_generator'
|
3
|
+
|
4
|
+
describe ApiVersions::Generators::BumpGenerator do
|
5
|
+
before do
|
6
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
7
|
+
prepare_destination
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Generated files" do
|
11
|
+
before { run_generator }
|
12
|
+
|
13
|
+
describe "Bar Controller" do
|
14
|
+
subject { file('app/controllers/api/v4/bar_controller.rb') }
|
15
|
+
|
16
|
+
it { should exist }
|
17
|
+
it { should contain /Api::V4::BarController < Api::V3::BarController/ }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "Foo Controller" do
|
21
|
+
subject { file('app/controllers/api/v4/foo_controller.rb') }
|
22
|
+
|
23
|
+
it { should exist }
|
24
|
+
it { should contain /Api::V4::FooController < Api::V3::FooController/ }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "Nested Controller" do
|
28
|
+
subject { file('app/controllers/api/v4/nests/nested_controller.rb') }
|
29
|
+
|
30
|
+
it { should exist }
|
31
|
+
it { should contain /Api::V4::Nests::NestedController < Api::V3::Nests::NestedController/ }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "Users Controller" do
|
35
|
+
subject { file('app/controllers/api/v4/users_controller.rb') }
|
36
|
+
|
37
|
+
it { should_not exist }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,7 @@ ENV["RAILS_ENV"] ||= 'test'
|
|
3
3
|
require File.expand_path("../dummy/config/environment", __FILE__)
|
4
4
|
require 'rspec/rails'
|
5
5
|
require 'rspec/autorun'
|
6
|
+
require 'ammeter/init'
|
6
7
|
|
7
8
|
# Requires supporting ruby files with custom matchers and macros, etc,
|
8
9
|
# in spec/support/ and its subdirectories.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api-versions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,25 +9,41 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec-rails
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '0'
|
21
|
+
version: '2.0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '0'
|
30
|
-
|
29
|
+
version: '2.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: ammeter
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.2.5
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.2.5
|
46
|
+
description: api-versions helps manage your app's API endpoints.
|
31
47
|
email:
|
32
48
|
- erich.menge@me.com
|
33
49
|
executables: []
|
@@ -37,17 +53,25 @@ files:
|
|
37
53
|
- .gitignore
|
38
54
|
- .travis.yml
|
39
55
|
- Gemfile
|
56
|
+
- License.txt
|
40
57
|
- README.md
|
41
58
|
- Rakefile
|
42
59
|
- api-versions.gemspec
|
43
60
|
- lib/api-versions.rb
|
44
61
|
- lib/api-versions/dsl.rb
|
62
|
+
- lib/api-versions/simplify_format.rb
|
45
63
|
- lib/api-versions/version.rb
|
46
64
|
- lib/api-versions/version_check.rb
|
65
|
+
- lib/generators/api_versions/bump_generator.rb
|
66
|
+
- lib/generators/api_versions/templates/controller.rb
|
67
|
+
- spec/controllers/application_controller_spec.rb
|
47
68
|
- spec/dummy/app/controllers/api/v1/bar_controller.rb
|
48
69
|
- spec/dummy/app/controllers/api/v2/bar_controller.rb
|
49
70
|
- spec/dummy/app/controllers/api/v2/foo_controller.rb
|
71
|
+
- spec/dummy/app/controllers/api/v2/users_controller.rb
|
50
72
|
- spec/dummy/app/controllers/api/v3/bar_controller.rb
|
73
|
+
- spec/dummy/app/controllers/api/v3/foo_controller.rb
|
74
|
+
- spec/dummy/app/controllers/api/v3/nests/nested_controller.rb
|
51
75
|
- spec/dummy/app/controllers/application_controller.rb
|
52
76
|
- spec/dummy/config.ru
|
53
77
|
- spec/dummy/config/application.rb
|
@@ -56,6 +80,7 @@ files:
|
|
56
80
|
- spec/dummy/config/environments/test.rb
|
57
81
|
- spec/dummy/config/routes.rb
|
58
82
|
- spec/dummy/log/.gitkeep
|
83
|
+
- spec/generators/bump_generator_spec.rb
|
59
84
|
- spec/routing/routing_spec.rb
|
60
85
|
- spec/spec_helper.rb
|
61
86
|
homepage: https://github.com/erichmenge/api-versions
|
@@ -78,15 +103,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
103
|
version: '0'
|
79
104
|
requirements: []
|
80
105
|
rubyforge_project: api-versions
|
81
|
-
rubygems_version: 1.8.
|
106
|
+
rubygems_version: 1.8.23
|
82
107
|
signing_key:
|
83
108
|
specification_version: 3
|
84
|
-
summary:
|
109
|
+
summary: An API versioning gem for Rails.
|
85
110
|
test_files:
|
111
|
+
- spec/controllers/application_controller_spec.rb
|
86
112
|
- spec/dummy/app/controllers/api/v1/bar_controller.rb
|
87
113
|
- spec/dummy/app/controllers/api/v2/bar_controller.rb
|
88
114
|
- spec/dummy/app/controllers/api/v2/foo_controller.rb
|
115
|
+
- spec/dummy/app/controllers/api/v2/users_controller.rb
|
89
116
|
- spec/dummy/app/controllers/api/v3/bar_controller.rb
|
117
|
+
- spec/dummy/app/controllers/api/v3/foo_controller.rb
|
118
|
+
- spec/dummy/app/controllers/api/v3/nests/nested_controller.rb
|
90
119
|
- spec/dummy/app/controllers/application_controller.rb
|
91
120
|
- spec/dummy/config.ru
|
92
121
|
- spec/dummy/config/application.rb
|
@@ -95,5 +124,6 @@ test_files:
|
|
95
124
|
- spec/dummy/config/environments/test.rb
|
96
125
|
- spec/dummy/config/routes.rb
|
97
126
|
- spec/dummy/log/.gitkeep
|
127
|
+
- spec/generators/bump_generator_spec.rb
|
98
128
|
- spec/routing/routing_spec.rb
|
99
129
|
- spec/spec_helper.rb
|