versioncake 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- versioncake (0.1.0)
4
+ versioncake (0.3.0)
5
5
  actionpack (>= 3.0)
6
6
  activesupport (>= 3.0)
7
7
  railties (>= 3.0)
data/README.md CHANGED
@@ -1,10 +1,23 @@
1
- # Version Cake [![Build Status](https://secure.travis-ci.org/bwillis/versioncake.png?branch=master)](http://travis-ci.org/bwillis/versioncake)
1
+ ![Version Cake](https://raw.github.com/alicial/versioncake/master/images/versioncake-logo450x100.png)
2
+
3
+ [![Build Status](https://secure.travis-ci.org/bwillis/versioncake.png?branch=master)](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 views in your Rails app.
7
+ Version Cake is an unobtrusive way to version APIs in your Rails app.
6
8
 
7
- We were tired of urls with version numbers, namespacing controllers with versions, and bumping all resources everytime we supported a new version. Simply configure your supported version numbers, create a versioned view if you need one and let versioncake render the requested version or gracefully degrade to the latest supported view version.
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 = :http_parameter # [:http_header, :http_accept_parameter, :query_parameter]
167
+ config.view_version_extraction_strategy = :query_parameter # [:http_header, :http_accept_parameter]
27
168
  ```
28
- These are the default strategies:
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
 
@@ -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
- if ActionView::Template::Versions.supports_version? requested_version
17
- @requested_version = requested_version
18
- @_lookup_context.versions = ActionView::Template::Versions.supported_versions(requested_version)
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
- @requested_version = ActionView::Template::Versions.latest_version
24
+ raise ActionController::RoutingError.new("Version is deprecated")
21
25
  end
22
- @is_latest_version = @requested_version == ActionView::Template::Versions.latest_version
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
@@ -1,3 +1,3 @@
1
1
  module VersionCake
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -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" => "4"
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.2.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-09-16 00:00:00.000000000 Z
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