api-versions 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -4,3 +4,4 @@ Gemfile.lock
4
4
  pkg/*
5
5
  spec/dummy/log/*
6
6
  log/*
7
+ tmp/*
@@ -6,7 +6,7 @@ rvm:
6
6
  branches:
7
7
  only:
8
8
  - master
9
- - develop
9
+ - production
10
10
 
11
11
  notifications:
12
12
  email:
@@ -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
- API-Versions [![Build Status](https://secure.travis-ci.org/erichmenge/api-versions.png)](http://travis-ci.org/erichmenge/api-versions)
2
- ======================================================================================================================================
1
+ # api-versions [![Build Status](https://secure.travis-ci.org/erichmenge/api-versions.png)](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
- Also, URL resources shouldn't change. Instead, API versions should be specified in the headers. The api-versions gem provides a nice DSL for this.
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.1.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 "application/vnd.myvendor+json;version=1". If no version is specified, the default version you set will be assumed. 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.
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
- License
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
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
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
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
187
+ This will set `request.format` accordingly.
@@ -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 = "Allows you to cache routing and then inherit it in other modules."
12
- s.description = "Useful for API versioning."
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
@@ -1,6 +1,8 @@
1
1
  require "api-versions/version"
2
2
  require "api-versions/version_check"
3
3
  require "api-versions/dsl"
4
+ require "api-versions/simplify_format"
5
+
4
6
 
5
7
  module ApiVersions
6
8
  def api(options = {}, &block)
@@ -17,7 +17,7 @@ module ApiVersions
17
17
  end
18
18
 
19
19
  def inherit(args)
20
- [*args[:from]].each do |block|
20
+ Array.wrap(args[:from]).each do |block|
21
21
  @resource_cache[block].call
22
22
  end
23
23
  end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ApiVersions
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -13,15 +13,15 @@ module ApiVersions
13
13
  private
14
14
 
15
15
  def accepts_proper_format?(request)
16
- !!(request.headers['Accept'] =~ /^application\/vnd\.#{self.class.vendor_string}\+.+/)
16
+ request.headers['Accept'] =~ /\Aapplication\/vnd\.#{self.class.vendor_string}\+.+/
17
17
  end
18
18
 
19
19
  def matches_version?(request)
20
- !!(request.headers['Accept'] =~ /version\s*?=\s*?#{@process_version}\b/)
20
+ request.headers['Accept'] =~ /version\s*?=\s*?#{@process_version}\b/
21
21
  end
22
22
 
23
23
  def unversioned?(request)
24
- @process_version == self.class.default_version && !(request.headers['Accept'] =~ /version\s*?=\s*?\d*\b/i)
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,2 @@
1
+ class <%= @current_new_controller %> < <%= @current_old_controller %>
2
+ 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
@@ -0,0 +1,2 @@
1
+ class Api::V2::UsersController < ApplicationController
2
+ end
@@ -0,0 +1,2 @@
1
+ class Api::V3::FooController < ApplicationController
2
+ end
@@ -0,0 +1,2 @@
1
+ class Api::V3::Nests::NestedController
2
+ end
@@ -17,4 +17,6 @@ Dummy::Application.routes.draw do
17
17
  inherit from: 'v2'
18
18
  end
19
19
  end
20
+
21
+ match '/app/index' => 'application#index'
20
22
  end
@@ -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
@@ -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.1.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-08-30 00:00:00.000000000 Z
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
- description: Useful for API versioning.
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.24
106
+ rubygems_version: 1.8.23
82
107
  signing_key:
83
108
  specification_version: 3
84
- summary: Allows you to cache routing and then inherit it in other modules.
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