versioncake 0.2.0 → 0.3.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/Gemfile.lock +1 -1
- data/README.md +162 -7
- data/images/versioncake-logo450x100.png +0 -0
- data/lib/versioncake/action_controller/versioning.rb +12 -7
- data/lib/versioncake/version.rb +1 -1
- data/test/functional/renders_controller_test.rb +32 -1
- metadata +3 -2
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
-
|
1
|
+

|
2
|
+
|
3
|
+
[](http://travis-ci.org/bwillis/versioncake)
|
2
4
|
|
3
5
|
Co-authored by Ben Willis ([@bwillis](https://github.com/bwillis/)) and Jim Jones ([@aantix](https://github.com/aantix)).
|
4
6
|
|
5
|
-
Version Cake is an unobtrusive way to version
|
7
|
+
Version Cake is an unobtrusive way to version APIs in your Rails app.
|
6
8
|
|
7
|
-
|
9
|
+
- Easily version any view with their API version:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
app/views/posts/
|
13
|
+
- index.v1.xml.builder
|
14
|
+
- index.v3.xml.builder
|
15
|
+
- index.v1.json.jbuilder
|
16
|
+
- index.v4.json.jbuilder
|
17
|
+
```
|
18
|
+
- Gracefully degrade requests to the latest supported version
|
19
|
+
- Clients can request API versions through different strategies
|
20
|
+
- Dry your controller logic with exposed helpers
|
8
21
|
|
9
22
|
## Install
|
10
23
|
|
@@ -12,6 +25,134 @@ We were tired of urls with version numbers, namespacing controllers with version
|
|
12
25
|
gem install versioncake
|
13
26
|
```
|
14
27
|
|
28
|
+
## Example
|
29
|
+
|
30
|
+
In this simple example we will outline the code that is introduced to support a change in a version.
|
31
|
+
|
32
|
+
### config/application.rb
|
33
|
+
```ruby
|
34
|
+
config.view_versions = (1...4)
|
35
|
+
config.view_version_extraction_strategy = :http_parameter # for simplicity
|
36
|
+
```
|
37
|
+
|
38
|
+
Often 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.
|
39
|
+
But if the requested API version is three or greater, we're going to eagerly load the associated comments as well.
|
40
|
+
|
41
|
+
Being able to control the logic based on the api version allow you to ensure forwards and backwards compatibility for future changes.
|
42
|
+
|
43
|
+
### PostsController
|
44
|
+
```ruby
|
45
|
+
class PostsController < ApplicationController
|
46
|
+
def index
|
47
|
+
# shared code for all versions
|
48
|
+
@posts = Post.scoped
|
49
|
+
|
50
|
+
# version 3 or greated supports embedding post comments
|
51
|
+
if requested_version >= 3
|
52
|
+
@posts = @posts.includes(:comments)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
See the view samples below. The basic top level posts are referenced in views/posts/index.v1.json.jbuilder.
|
59
|
+
But for views/posts/index.v4.json.jbuilder, we utilize the additional related comments.
|
60
|
+
|
61
|
+
### Views
|
62
|
+
|
63
|
+
Notice the version numbers are denoted by the "v{version number}" extension within the file name.
|
64
|
+
|
65
|
+
#### views/posts/index.v1.json.jbuilder
|
66
|
+
```ruby
|
67
|
+
json.array!(@posts) do |json, post|
|
68
|
+
json.(post, :id, :title)
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
#### views/posts/index.v4.json.jbuilder
|
73
|
+
```ruby
|
74
|
+
json.array!(@posts) do |json, post|
|
75
|
+
json.(post, :id, :title)
|
76
|
+
json.comments post.comments, :id, :text
|
77
|
+
end
|
78
|
+
```
|
79
|
+
|
80
|
+
### Sample Output
|
81
|
+
|
82
|
+
When 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.v3.json.jbuilder doesn't exist, views/posts/index.v1.json.jbuilder is rendered instead.
|
83
|
+
|
84
|
+
#### http://localhost:3000/posts.json?api_version=3
|
85
|
+
```javascript
|
86
|
+
[
|
87
|
+
{
|
88
|
+
id: 1
|
89
|
+
title: "Version Cake v0.1.0 Released!"
|
90
|
+
name: "Ben"
|
91
|
+
updated_at: "2012-09-17T16:23:45Z"
|
92
|
+
},
|
93
|
+
{
|
94
|
+
id: 2
|
95
|
+
title: "Version Cake v0.2.0 Released!"
|
96
|
+
name: "Jim"
|
97
|
+
updated_at: "2012-09-17T16:23:32Z"
|
98
|
+
}
|
99
|
+
]
|
100
|
+
```
|
101
|
+
|
102
|
+
|
103
|
+
For 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.v1.json.jbuilder is rendered.
|
104
|
+
|
105
|
+
#### http://localhost:3000/posts.json?api_version=2 or http://localhost:3000/posts.json?api_version=1
|
106
|
+
```javascript
|
107
|
+
[
|
108
|
+
{
|
109
|
+
id: 1
|
110
|
+
title: "Version Cake v0.1.0 Released!"
|
111
|
+
name: "Ben"
|
112
|
+
updated_at: "2012-09-17T16:23:45Z"
|
113
|
+
},
|
114
|
+
{
|
115
|
+
id: 2
|
116
|
+
title: "Version Cake v0.2.0 Released!"
|
117
|
+
name: "Jim"
|
118
|
+
updated_at: "2012-09-17T16:23:32Z"
|
119
|
+
}
|
120
|
+
]
|
121
|
+
```
|
122
|
+
|
123
|
+
|
124
|
+
When no version is specified, the latest version of the view is rendered. In this case, views/posts/index.v4.json.jbuilder.
|
125
|
+
|
126
|
+
#### http://localhost:3000/posts.json
|
127
|
+
```javascript
|
128
|
+
[
|
129
|
+
{
|
130
|
+
id: 1
|
131
|
+
title: "Version Cake v0.1.0 Released!"
|
132
|
+
name: "Ben"
|
133
|
+
updated_at: "2012-09-17T16:23:45Z"
|
134
|
+
comments: [
|
135
|
+
{
|
136
|
+
id: 1
|
137
|
+
text: "Woah interesting approach on versioning"
|
138
|
+
}
|
139
|
+
]
|
140
|
+
},
|
141
|
+
{
|
142
|
+
id: 2
|
143
|
+
title: "Version Cake v0.2.0 Released!"
|
144
|
+
name: "Jim"
|
145
|
+
updated_at: "2012-09-17T16:23:32Z"
|
146
|
+
comments: [
|
147
|
+
{
|
148
|
+
id: 4
|
149
|
+
text: "These new features are greeeeat!"
|
150
|
+
}
|
151
|
+
]
|
152
|
+
}
|
153
|
+
]
|
154
|
+
```
|
155
|
+
|
15
156
|
## How to use
|
16
157
|
|
17
158
|
### Configuration
|
@@ -23,12 +164,12 @@ config.view_versions = [1,3,4,5] # or (1...5)
|
|
23
164
|
```
|
24
165
|
You can also define the way to extract the version. The `view_version_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.
|
25
166
|
```ruby
|
26
|
-
config.view_version_extraction_strategy = :
|
167
|
+
config.view_version_extraction_strategy = :query_parameter # [:http_header, :http_accept_parameter]
|
27
168
|
```
|
28
|
-
These are the
|
169
|
+
These are the available strategies:
|
170
|
+
- **query_parameter**: version in the url query parameter, for testing or to override for special case i.e. ```http://localhost:3000/posts.json?api_version=1``` (This is the default.)
|
29
171
|
- **http_header**: Api version HTTP header ie. ```API-Version: 1```
|
30
172
|
- **http_accept_parameter**: HTTP Accept header ie. ```Accept: application/xml; 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)
|
31
|
-
- **query_parameter**: version in the url query parameter, for testing or to override for special case ie. ```http://localhost:3000/posts.json?api_version=1```
|
32
173
|
- **custom**: `lambda {|request| request.headers["HTTP_X_API_VERSION"].to_i }` takes the request object and must return an integer
|
33
174
|
|
34
175
|
### Version your views
|
@@ -52,7 +193,17 @@ If you start supporting a newer version, v3 for instance, you do not have to cop
|
|
52
193
|
### Controller
|
53
194
|
|
54
195
|
You 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 `requested_version` and `latest_version`. This may be updated in the [near future](https://github.com/bwillis/versioncake/issues/1).
|
55
|
-
|
196
|
+
```ruby
|
197
|
+
def index
|
198
|
+
# shared code for all versions
|
199
|
+
@posts = Post.scoped
|
200
|
+
|
201
|
+
# version 3 or greated supports embedding post comments
|
202
|
+
if requested_version >= 3
|
203
|
+
@posts = @posts.includes(:comments)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
```
|
56
207
|
### Client requests
|
57
208
|
|
58
209
|
When 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``.
|
@@ -63,11 +214,15 @@ When a client makes a request it will automatically receive the latest supported
|
|
63
214
|
|
64
215
|
- https://github.com/bploetz/versionist
|
65
216
|
- https://github.com/filtersquad/rocket_pants
|
217
|
+
- https://github.com/lyonrb/biceps
|
66
218
|
|
67
219
|
## Discussions
|
68
220
|
|
69
221
|
- [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)
|
70
222
|
- [Rails API project disucssion on versioning](https://github.com/spastorino/rails-api/issues/8)
|
223
|
+
- [Railscast on versioning](http://railscasts.com/episodes/350-rest-api-versioning)
|
224
|
+
- [Ruby5 on versioncake](http://ruby5.envylabs.com/episodes/310-episode-306-september-18th-2012/stories/2704-version-cake)
|
225
|
+
- [Rails core discussion](https://groups.google.com/forum/#!msg/rubyonrails-core/odwmEYYIum0/9POep66BvoMJ)
|
71
226
|
|
72
227
|
# Questions?
|
73
228
|
|
Binary file
|
@@ -4,7 +4,7 @@ module ActionController #:nodoc:
|
|
4
4
|
module Versioning
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
attr_accessor :requested_version, :is_latest_version
|
7
|
+
attr_accessor :requested_version, :is_latest_version, :derived_version
|
8
8
|
|
9
9
|
included do
|
10
10
|
prepend_before_filter :set_version
|
@@ -12,14 +12,19 @@ module ActionController #:nodoc:
|
|
12
12
|
|
13
13
|
protected
|
14
14
|
def set_version
|
15
|
-
requested_version = ActionView::Template::Versions.extract_version request
|
16
|
-
|
17
|
-
|
18
|
-
@
|
15
|
+
@requested_version = ActionView::Template::Versions.extract_version request
|
16
|
+
|
17
|
+
if @requested_version.nil?
|
18
|
+
@derived_version = ActionView::Template::Versions.latest_version
|
19
|
+
elsif ActionView::Template::Versions.supports_version? requested_version
|
20
|
+
@derived_version = @requested_version
|
21
|
+
elsif requested_version > ActionView::Template::Versions.latest_version
|
22
|
+
raise ActionController::RoutingError.new("No route match for version")
|
19
23
|
else
|
20
|
-
|
24
|
+
raise ActionController::RoutingError.new("Version is deprecated")
|
21
25
|
end
|
22
|
-
@
|
26
|
+
@_lookup_context.versions = ActionView::Template::Versions.supported_versions(@derived_version)
|
27
|
+
@is_latest_version = @derived_version == ActionView::Template::Versions.latest_version
|
23
28
|
end
|
24
29
|
end
|
25
30
|
end
|
data/lib/versioncake/version.rb
CHANGED
@@ -15,7 +15,7 @@ class RendersControllerTest < ActionController::TestCase
|
|
15
15
|
end
|
16
16
|
|
17
17
|
test "exposes latest version when requesting the latest" do
|
18
|
-
get :index, "api_version" => "
|
18
|
+
get :index, "api_version" => "3"
|
19
19
|
assert @controller.is_latest_version
|
20
20
|
end
|
21
21
|
|
@@ -23,6 +23,16 @@ class RendersControllerTest < ActionController::TestCase
|
|
23
23
|
get :index, "api_version" => "1"
|
24
24
|
assert !@controller.is_latest_version
|
25
25
|
end
|
26
|
+
|
27
|
+
test "exposes the derived version when the version is not set" do
|
28
|
+
get :index
|
29
|
+
assert_equal 3, @controller.derived_version
|
30
|
+
end
|
31
|
+
|
32
|
+
test "requested version is blank when the version is not set" do
|
33
|
+
get :index
|
34
|
+
assert_blank @controller.requested_version
|
35
|
+
end
|
26
36
|
end
|
27
37
|
|
28
38
|
class ParameterStragegyTest < ActionController::TestCase
|
@@ -142,4 +152,25 @@ class MultipleStrategyTest < ActionController::TestCase
|
|
142
152
|
get :index, "api_version" => "1"
|
143
153
|
assert_equal @response.body, "index.v2.html.erb"
|
144
154
|
end
|
155
|
+
end
|
156
|
+
|
157
|
+
class UnsupportedVersionTest < ActionController::TestCase
|
158
|
+
tests RendersController
|
159
|
+
|
160
|
+
setup do
|
161
|
+
ActionView::Template::Versions.extraction_strategy = :query_parameter
|
162
|
+
end
|
163
|
+
|
164
|
+
test "responds with 404 when the version is larger than the supported version" do
|
165
|
+
assert_raise ActionController::RoutingError do
|
166
|
+
get :index, "api_version" => "4"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
test "responds with 404 when the version is lower than the latest version, but not an available version" do
|
171
|
+
ActionView::Template::Versions.supported_version_numbers = [2,3]
|
172
|
+
assert_raise ActionController::RoutingError do
|
173
|
+
get :index, "api_version" => "1"
|
174
|
+
end
|
175
|
+
end
|
145
176
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: versioncake
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-10-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: actionpack
|
@@ -106,6 +106,7 @@ files:
|
|
106
106
|
- Gemfile.lock
|
107
107
|
- README.md
|
108
108
|
- Rakefile
|
109
|
+
- images/versioncake-logo450x100.png
|
109
110
|
- lib/versioncake.rb
|
110
111
|
- lib/versioncake/action_controller/versioning.rb
|
111
112
|
- lib/versioncake/action_view/lookup_context.rb
|