versionable_api 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -6
- data/lib/versionable_api/api_versioning.rb +12 -10
- data/lib/versionable_api/version.rb +1 -1
- data/test/spec/versionable_api_test.rb +30 -17
- metadata +8 -2
data/README.md
CHANGED
@@ -6,13 +6,13 @@ VersionableApi is a small gem that helps you create versionable apis (initially
|
|
6
6
|
|
7
7
|
The most common way to start trying to version APIs is to create URIs (and routes and controllers) that look somewhat like this:
|
8
8
|
```
|
9
|
-
/api/v1/
|
9
|
+
/api/v1/people.json
|
10
10
|
```
|
11
11
|
and route that to a controller in `app/controllers/api/v1/people_controller.rb`
|
12
12
|
|
13
13
|
Then, when you want to make a change to the person API, you create:
|
14
14
|
```
|
15
|
-
/api/v2/
|
15
|
+
/api/v2/people.json
|
16
16
|
```
|
17
17
|
and you create `app/controllers/api/v2/people_controller.rb`.
|
18
18
|
|
@@ -20,20 +20,20 @@ But do you make `Api::V2::PeopleController` inherit from `Api::V1::PeopleControl
|
|
20
20
|
|
21
21
|
# How VersionableApi tries to solve this problem
|
22
22
|
|
23
|
-
`VersionableApi` proposes that you create tiny controllers and then put version-specific behavior in modules that are included in that controller. `VersionableApi` provides a module that does a tiny bit of magic to determine which "versioned" method gets called based on an HTTP Accept header
|
23
|
+
`VersionableApi` proposes that you create tiny controllers and then put version-specific behavior in modules that are included in that controller. `VersionableApi` provides a module that does a tiny bit of magic to determine which "versioned" method gets called based on an HTTP Accept header.
|
24
24
|
|
25
25
|
Instead of putting the version of the API you want to call in the request URI, it's specified in the Accept Header by adding `;version=X` to one of the acceptable types. The easiest way is to specify an accept type of `*/*;version=X` (where X is the version you want).
|
26
26
|
|
27
27
|
# Maintaining backwards compatibility with clients who are already using the "old" URI style
|
28
28
|
|
29
|
-
If you're transitioning an existing API to using VersionableApi and you need to be able to handle 'old' style routes (like `/api/v2/something.json`) VersionableApi provides a simple piece of Rack middleware that can help.
|
29
|
+
If you're transitioning an existing API to using VersionableApi and you need to be able to handle 'old' style routes (like `/api/v2/something.json`) `VersionableApi` provides a simple piece of Rack middleware that can help.
|
30
30
|
|
31
31
|
The `VersionableApi::ApiVersionInterceptor` can intercept requests to the 'old' api style and massage them to fit your new style. You can include it by adding the following line inside your `config/application.rb` class:
|
32
32
|
```
|
33
33
|
config.middleware.use "VersionableApi::ApiVersionInterceptor"
|
34
34
|
```
|
35
35
|
|
36
|
-
By default, it will look for requests to paths that look like `/api/v#/something` and transform them into `/api/something` with `*/*;version=#` prepended to the HTTP_ACCEPT header and then forward the request on to your Rails app. You can configure most of how it behaves via initialization parameters if you don't like the defaults, for example:
|
36
|
+
By default, it will look for requests to paths that look like `/api/v#/something` and transform them into `/api/something` with `*/*;version=#` prepended to the `HTTP_ACCEPT` header and then forward the request on to your Rails app. You can configure most of how it behaves via initialization parameters if you don't like the defaults, for example:
|
37
37
|
```
|
38
38
|
config.middleware.use "VersionableApi::ApiVersionInterceptor", {version_regex: /\/API\/version-(?<version>\d+)\/(?<path>.*)/}
|
39
39
|
```
|
@@ -87,7 +87,7 @@ the `Api::V1::People#show_v1` method will handle the request.
|
|
87
87
|
```
|
88
88
|
GET /api/people.json {HTTP_ACCEPT: text/json;version=2}
|
89
89
|
```
|
90
|
-
|
90
|
+
a 404 will be returned because there is no version 2 of the `index` action.
|
91
91
|
|
92
92
|
|
93
93
|
# How to use it
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module VersionableApi
|
2
2
|
module ApiVersioning
|
3
3
|
|
4
|
-
# Public: The default version
|
5
|
-
# this themselves if they don't want the default version to be 1
|
4
|
+
# Public: The default version. Classes including ApiVersioning should specify
|
5
|
+
# this themselves if they don't want the default version to be 1. If you would
|
6
|
+
# like to force the caller to specify a version in their request, override
|
7
|
+
# this method have have it return nil.
|
6
8
|
#
|
7
9
|
# Returns the duplicated String.
|
8
10
|
def default_version
|
9
11
|
1
|
10
12
|
end
|
11
|
-
|
13
|
+
|
12
14
|
# Public: Returns a versioned action name based on the requested action name
|
13
15
|
# and an optional version specification on the request. If no versioned
|
14
16
|
# action matching what we think the request is trying to access is defined on
|
@@ -22,18 +24,18 @@ module VersionableApi
|
|
22
24
|
#
|
23
25
|
# Returns a versioned action name, "_handle_action_missing", or nil
|
24
26
|
def method_for_action(action)
|
25
|
-
version = (requested_version || self.default_version
|
26
|
-
|
27
|
-
version.
|
28
|
-
|
29
|
-
|
27
|
+
version = (requested_version || self.default_version)
|
28
|
+
|
29
|
+
unless version.nil?
|
30
|
+
version = version.to_i
|
31
|
+
name = "#{action}_v#{version}"
|
32
|
+
method = self.respond_to?(name) ? name : nil
|
30
33
|
end
|
31
34
|
|
32
35
|
method ||= self.respond_to?("action_missing") ? "_handle_action_missing" : nil
|
33
|
-
|
34
36
|
end
|
35
37
|
|
36
|
-
|
38
|
+
|
37
39
|
# Public: Finds the API version requested in the request
|
38
40
|
#
|
39
41
|
# Returns the requested version, or nil if no version was specifically requested
|
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
require 'ostruct'
|
3
3
|
|
4
4
|
describe VersionableApi::ApiVersioning do
|
5
|
-
class Testable
|
5
|
+
class Testable
|
6
6
|
include VersionableApi::ApiVersioning
|
7
7
|
attr_accessor :request
|
8
8
|
def initialize
|
@@ -11,62 +11,75 @@ describe VersionableApi::ApiVersioning do
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
|
14
|
+
class TestableRequireVersion < Testable
|
15
|
+
def default_version
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
15
21
|
@test_me = Testable.new
|
16
22
|
end
|
17
23
|
|
18
|
-
|
19
|
-
|
24
|
+
describe "default version" do
|
25
|
+
it "should have a default version of 1" do
|
26
|
+
assert_equal 1, @test_me.default_version
|
27
|
+
end
|
28
|
+
it "should require a version from the client if default version is nil" do
|
29
|
+
@test_me = TestableRequireVersion.new
|
30
|
+
assert_nil @test_me.default_version
|
31
|
+
assert_nil @test_me.method_for_action("show")
|
32
|
+
end
|
20
33
|
end
|
21
34
|
|
22
|
-
describe "#requested_version" do
|
23
|
-
it "should determine the requested version from the request headers" do
|
35
|
+
describe "#requested_version" do
|
36
|
+
it "should determine the requested version from the request headers" do
|
24
37
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=10"
|
25
38
|
assert_equal 10, @test_me.requested_version
|
26
39
|
end
|
27
40
|
|
28
|
-
it "should return nil if no explicit version is requested" do
|
41
|
+
it "should return nil if no explicit version is requested" do
|
29
42
|
assert_nil @test_me.requested_version
|
30
43
|
end
|
31
44
|
|
32
|
-
it "should return nil if the version requested is non-numeric" do
|
45
|
+
it "should return nil if the version requested is non-numeric" do
|
33
46
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=FOO"
|
34
47
|
assert_nil @test_me.requested_version
|
35
48
|
end
|
36
49
|
end
|
37
50
|
|
38
|
-
describe "#method_for_action" do
|
39
|
-
before do
|
51
|
+
describe "#method_for_action" do
|
52
|
+
before do
|
40
53
|
def @test_me.show_v1; 1; end;
|
41
54
|
def @test_me.show_v2; 2; end;
|
42
55
|
def @test_me.index_v1; 1; end;
|
43
56
|
end
|
44
57
|
|
45
|
-
it "should return the default version when no version is requested" do
|
58
|
+
it "should return the default version when no version is requested" do
|
46
59
|
assert_equal "show_v1", @test_me.method_for_action("show")
|
47
60
|
end
|
48
61
|
|
49
|
-
it "should return the requested version if specified and available" do
|
62
|
+
it "should return the requested version if specified and available" do
|
50
63
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=2"
|
51
64
|
assert_equal "show_v2", @test_me.method_for_action("show")
|
52
65
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=1"
|
53
66
|
assert_equal "show_v1", @test_me.method_for_action("show")
|
54
67
|
end
|
55
68
|
|
56
|
-
it "should return
|
69
|
+
it "should return nil if the version specified is higher than the available versions" do
|
57
70
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=10"
|
58
|
-
|
71
|
+
assert_nil @test_me.method_for_action("show")
|
59
72
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=2"
|
60
|
-
|
73
|
+
assert_nil @test_me.method_for_action("index")
|
61
74
|
end
|
62
75
|
|
63
|
-
it "should return _handle_action_missing if #action_missing is defined and unkown action is called for" do
|
76
|
+
it "should return _handle_action_missing if #action_missing is defined and unkown action is called for" do
|
64
77
|
def @test_me.action_missing; true; end;
|
65
78
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=2"
|
66
79
|
assert_equal "_handle_action_missing", @test_me.method_for_action("foobar")
|
67
80
|
end
|
68
81
|
|
69
|
-
it "should return nil if #action_missing is not defined and an unkown action is specified" do
|
82
|
+
it "should return nil if #action_missing is not defined and an unkown action is specified" do
|
70
83
|
@test_me.request.headers["HTTP_ACCEPT"] = "*/*;version=2"
|
71
84
|
assert_nil @test_me.method_for_action("foobar")
|
72
85
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: versionable_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -57,12 +57,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
57
57
|
- - ! '>='
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: '0'
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
hash: -1992996191972482845
|
60
63
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
64
|
none: false
|
62
65
|
requirements:
|
63
66
|
- - ! '>='
|
64
67
|
- !ruby/object:Gem::Version
|
65
68
|
version: '0'
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
hash: -1992996191972482845
|
66
72
|
requirements: []
|
67
73
|
rubyforge_project:
|
68
74
|
rubygems_version: 1.8.23
|