Added API versioning

This commit is contained in:
mcmac 2018-02-21 12:09:22 +00:00
parent 595a6263c7
commit e85af664ce
10 changed files with 288 additions and 161 deletions

218
.idea/workspace.xml generated
View file

@ -3,11 +3,10 @@
<component name="ChangeListManager">
<list default="true" id="42443dfa-4405-4cad-b0d1-6c57eb86e644" name="Default" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" afterPath="$PROJECT_DIR$/.idea/workspace.xml" />
<change beforePath="$PROJECT_DIR$/Gemfile" afterPath="$PROJECT_DIR$/Gemfile" />
<change beforePath="$PROJECT_DIR$/app/controllers/articles_controller.rb" afterPath="$PROJECT_DIR$/app/controllers/articles_controller.rb" />
<change beforePath="$PROJECT_DIR$/config/application.rb" afterPath="$PROJECT_DIR$/config/application.rb" />
<change beforePath="$PROJECT_DIR$/config/database.yml" afterPath="$PROJECT_DIR$/config/database.yml" />
<change beforePath="$PROJECT_DIR$/db/seeds.rb" afterPath="$PROJECT_DIR$/db/seeds.rb" />
<change beforePath="$PROJECT_DIR$/app/controllers/articles_controller.rb" afterPath="$PROJECT_DIR$/app/controllers/v1/articles_controller.rb" />
<change beforePath="$PROJECT_DIR$/app/controllers/items_controller.rb" afterPath="" />
<change beforePath="$PROJECT_DIR$/app/controllers/todos_controller.rb" afterPath="" />
<change beforePath="$PROJECT_DIR$/config/routes.rb" afterPath="$PROJECT_DIR$/config/routes.rb" />
</list>
<ignored path="$PROJECT_DIR$/.bundle/" />
<ignored path="$PROJECT_DIR$/components/" />
@ -46,11 +45,61 @@
</provider>
</entry>
</file>
<file leaf-file-name="articles_controller.rb" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/app/controllers/articles_controller.rb">
<file leaf-file-name="articles_controller.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/app/controllers/v1/articles_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="198">
<caret line="11" column="12" lean-forward="false" selection-start-line="11" selection-start-column="12" selection-end-line="11" selection-end-column="12" />
<state relative-caret-position="288">
<caret line="16" column="21" lean-forward="false" selection-start-line="16" selection-start-column="21" selection-end-line="16" selection-end-column="21" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="articles_controller.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/app/controllers/v2/articles_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="36">
<caret line="2" column="54" lean-forward="false" selection-start-line="2" selection-start-column="54" selection-end-line="2" selection-end-column="54" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="api_version.rb" pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/app/lib/api_version.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="324">
<caret line="18" column="118" lean-forward="false" selection-start-line="18" selection-start-column="118" selection-end-line="18" selection-end-column="118" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="items_controller.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/app/controllers/v1/items_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="162">
<caret line="9" column="0" lean-forward="false" selection-start-line="9" selection-start-column="0" selection-end-line="9" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="todos_controller.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/app/controllers/v1/todos_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="396">
<caret line="22" column="14" lean-forward="false" selection-start-line="22" selection-start-column="14" selection-end-line="22" selection-end-column="14" />
<folding />
</state>
</provider>
</entry>
</file>
<file leaf-file-name="routes.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/config/routes.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="54">
<caret line="3" column="54" lean-forward="false" selection-start-line="3" selection-start-column="54" selection-end-line="3" selection-end-column="54" />
<folding />
</state>
</provider>
@ -59,7 +108,7 @@
<file leaf-file-name="application.rb" pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/config/application.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="392">
<state relative-caret-position="204">
<caret line="30" column="82" lean-forward="false" selection-start-line="30" selection-start-column="82" selection-end-line="30" selection-end-column="82" />
<folding />
</state>
@ -132,7 +181,6 @@
<option value="$PROJECT_DIR$/db/migrate/20180219100239_create_articles.rb" />
<option value="$PROJECT_DIR$/app/models/article.rb" />
<option value="$PROJECT_DIR$/app/models/user.rb" />
<option value="$PROJECT_DIR$/config/routes.rb" />
<option value="$PROJECT_DIR$/app/controllers/users_controller.rb" />
<option value="$PROJECT_DIR$/app/controllers/application_controller.rb" />
<option value="$PROJECT_DIR$/app/controllers/authentication_controller.rb" />
@ -142,6 +190,12 @@
<option value="$PROJECT_DIR$/db/seeds.rb" />
<option value="$PROJECT_DIR$/config/application.rb" />
<option value="$PROJECT_DIR$/app/controllers/articles_controller.rb" />
<option value="$PROJECT_DIR$/app/controllers/v1/items_controller.rb" />
<option value="$PROJECT_DIR$/app/controllers/v1/todos_controller.rb" />
<option value="$PROJECT_DIR$/app/controllers/v1/articles_controller.rb" />
<option value="$PROJECT_DIR$/config/routes.rb" />
<option value="$PROJECT_DIR$/app/controllers/v2/articles_controller.rb" />
<option value="$PROJECT_DIR$/app/lib/api_version.rb" />
</list>
</option>
</component>
@ -189,6 +243,26 @@
<item name="app" type="462c0819:PsiDirectoryNode" />
<item name="controllers" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="vereto-api" type="b2602c69:ProjectViewProjectNode" />
<item name="vereto-api" type="462c0819:PsiDirectoryNode" />
<item name="app" type="462c0819:PsiDirectoryNode" />
<item name="controllers" type="462c0819:PsiDirectoryNode" />
<item name="v1" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="vereto-api" type="b2602c69:ProjectViewProjectNode" />
<item name="vereto-api" type="462c0819:PsiDirectoryNode" />
<item name="app" type="462c0819:PsiDirectoryNode" />
<item name="controllers" type="462c0819:PsiDirectoryNode" />
<item name="v2" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="vereto-api" type="b2602c69:ProjectViewProjectNode" />
<item name="vereto-api" type="462c0819:PsiDirectoryNode" />
<item name="app" type="462c0819:PsiDirectoryNode" />
<item name="lib" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="vereto-api" type="b2602c69:ProjectViewProjectNode" />
<item name="vereto-api" type="462c0819:PsiDirectoryNode" />
@ -208,6 +282,11 @@
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/app/controllers/v1" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
@ -340,12 +419,12 @@
<workItem from="1518772064427" duration="15673000" />
<workItem from="1519029876792" duration="13672000" />
<workItem from="1519112531617" duration="11606000" />
<workItem from="1519202717236" duration="5377000" />
<workItem from="1519202717236" duration="7085000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="46358000" />
<option name="totallyTimeSpent" value="48066000" />
</component>
<component name="ToolWindowManager">
<frame x="0" y="28" width="1920" height="1026" extended-state="6" />
@ -382,34 +461,6 @@
<watches-manager />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/app/controllers/authentication_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="270">
<caret line="15" column="0" lean-forward="true" selection-start-line="15" selection-start-column="0" selection-end-line="15" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/spec/requests/items_spec.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="1260">
<caret line="123" column="81" lean-forward="false" selection-start-line="123" selection-start-column="81" selection-end-line="123" selection-end-column="81" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/models/user.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="108">
<caret line="6" column="32" lean-forward="false" selection-start-line="6" selection-start-column="32" selection-end-line="6" selection-end-column="32" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/channels/application_cable/connection.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/channels/application_cable/channel.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="0">
@ -466,13 +517,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/lib/json_web_token.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="360">
<caret line="20" column="3" lean-forward="false" selection-start-line="20" selection-start-column="3" selection-end-line="20" selection-end-column="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/concerns/response.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="72">
@ -487,13 +531,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/lib/message.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="527">
<caret line="32" column="3" lean-forward="false" selection-start-line="32" selection-start-column="3" selection-end-line="32" selection-end-column="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/spec/auth/authenticate_user_spec.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="527">
@ -608,13 +645,6 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/config/routes.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="108">
<caret line="6" column="16" lean-forward="true" selection-start-line="6" selection-start-column="16" selection-end-line="6" selection-end-column="16" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/items_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="36">
@ -722,18 +752,74 @@
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/lib/json_web_token.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="360">
<caret line="20" column="3" lean-forward="false" selection-start-line="20" selection-start-column="3" selection-end-line="20" selection-end-column="3" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/lib/message.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="576">
<caret line="32" column="3" lean-forward="false" selection-start-line="32" selection-start-column="3" selection-end-line="32" selection-end-column="3" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/v1/items_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="162">
<caret line="9" column="0" lean-forward="false" selection-start-line="9" selection-start-column="0" selection-end-line="9" selection-end-column="0" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/v1/todos_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="396">
<caret line="22" column="14" lean-forward="false" selection-start-line="22" selection-start-column="14" selection-end-line="22" selection-end-column="14" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/v1/articles_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="288">
<caret line="16" column="21" lean-forward="false" selection-start-line="16" selection-start-column="21" selection-end-line="16" selection-end-column="21" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/config/routes.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="54">
<caret line="3" column="54" lean-forward="false" selection-start-line="3" selection-start-column="54" selection-end-line="3" selection-end-column="54" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/config/application.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="392">
<state relative-caret-position="204">
<caret line="30" column="82" lean-forward="false" selection-start-line="30" selection-start-column="82" selection-end-line="30" selection-end-column="82" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/controllers/articles_controller.rb">
<entry file="file://$PROJECT_DIR$/app/controllers/v2/articles_controller.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="198">
<caret line="11" column="12" lean-forward="false" selection-start-line="11" selection-start-column="12" selection-end-line="11" selection-end-column="12" />
<state relative-caret-position="36">
<caret line="2" column="54" lean-forward="false" selection-start-line="2" selection-start-column="54" selection-end-line="2" selection-end-column="54" />
<folding />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/app/lib/api_version.rb">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="324">
<caret line="18" column="118" lean-forward="false" selection-start-line="18" selection-start-column="118" selection-end-line="18" selection-end-column="118" />
<folding />
</state>
</provider>

View file

@ -1,47 +0,0 @@
class ItemsController < ApplicationController
before_action :set_todo
before_action :set_todo_item, only: [:show, :update, :destroy]
# GET /todos/:todo_id/items
def index
json_response(@todo.items)
end
# GET /todos/:todo_id/items/:id
def show
json_response(@item)
end
# POST /todos/:todo_id/items
def create
@todo.items.create!(item_params)
json_response(@todo, :created)
end
# PUT /todos/:todo_id/items/:d
def update
@item.update(item_params)
head :no_content
end
# DELETE /todos/:todo_id/items/:id
def destroy
@item.destroy
head :no_content
end
private
def item_params
params.permit(:name, :done)
end
def set_todo
@todo = Todo.find(params[:todo_id])
end
def set_todo_item
@item = @todo.items.find_by!(id: params[:id]) if @todo
end
end

View file

@ -1,44 +0,0 @@
class TodosController < ApplicationController
before_action :set_todo, only: [:show, :update, :destroy]
# GET /todos
def index
@todos = current_user.todos
json_response(@todos)
end
# POST /todos
def create
@todo = current_user.todos.create!(todo_params)
json_response(@todo, :created)
end
# GET /todos/:id
def show
json_response(@todo)
end
# PUT /todos/:id
def update
@todo.update(todo_params)
head :no_content
end
# DELETE /todos/:id
def destroy
@todo.destroy
head :no_content
end
private
def todo_params
# whitelist params
params.permit(:title)
end
def set_todo
@todo = Todo.find(params[:id])
end
end

View file

@ -1,4 +1,4 @@
class ArticlesController < ApplicationController
class V1::ArticlesController < ApplicationController
before_action :set_article, only: [:show, :update, :destroy]
skip_before_action :authorize_request, only: [:index, :show]

View file

@ -0,0 +1,48 @@
module V1
class ItemsController < ApplicationController
before_action :set_todo
before_action :set_todo_item, only: [:show, :update, :destroy]
# GET /todos/:todo_id/items
def index
json_response(@todo.items)
end
# GET /todos/:todo_id/items/:id
def show
json_response(@item)
end
# POST /todos/:todo_id/items
def create
@todo.items.create!(item_params)
json_response(@todo, :created)
end
# PUT /todos/:todo_id/items/:d
def update
@item.update(item_params)
head :no_content
end
# DELETE /todos/:todo_id/items/:id
def destroy
@item.destroy
head :no_content
end
private
def item_params
params.permit(:name, :done)
end
def set_todo
@todo = Todo.find(params[:todo_id])
end
def set_todo_item
@item = @todo.items.find_by!(id: params[:id]) if @todo
end
end
end

View file

@ -0,0 +1,45 @@
module V1
class TodosController < ApplicationController
before_action :set_todo, only: [:show, :update, :destroy]
# GET /todos
def index
@todos = current_user.todos
json_response(@todos)
end
# POST /todos
def create
@todo = current_user.todos.create!(todo_params)
json_response(@todo, :created)
end
# GET /todos/:id
def show
json_response(@todo)
end
# PUT /todos/:id
def update
@todo.update(todo_params)
head :no_content
end
# DELETE /todos/:id
def destroy
@todo.destroy
head :no_content
end
private
def todo_params
# whitelist params
params.permit(:title)
end
def set_todo
@todo = Todo.find(params[:id])
end
end
end

View file

@ -0,0 +1,5 @@
class V2::ArticlesController < ApplicationController
def index
json_response({message: 'Version 2 not implemented'})
end
end

21
app/lib/api_version.rb Normal file
View file

@ -0,0 +1,21 @@
class ApiVersion
attr_reader :version, :default
def initialize(version, default = false)
@version = version
@default = default
end
# check whether version is specified or is default
def matches?(request)
check_headers(request.headers) || default
end
private
def check_headers(headers)
# check version from Accept headers; expect custom media type `todos`
accept = headers[:accept]
accept && accept.include?("application/vnd.rails.#{version}+json") # header Accept: application/vnd.rails.v#+json
end
end

View file

@ -1,9 +1,17 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :todos do
resources :items
scope module: :v2, constraints: ApiVersion.new('v2') do
resources :articles
end
resources :articles
scope module: :v1, constraints: ApiVersion.new('v1', true) do
resources :todos do
resources :items
end
resources :articles
end
resources :users
post 'auth/login', to: 'authentication#authenticate'
post 'signup', to: 'users#create'

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe V2::ArticlesController, type: :controller do
end