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.
@@ -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