RailsルートのAPIバージョニング


141

StripeのようにAPIをバージョン管理しようとしています。以下に、最新のAPIバージョンが2であることを示します。

/api/users に301を返します /api/v2/users

/api/v1/users バージョン1で200のユーザーインデックスを返します

/api/v3/users に301を返します /api/v2/users

/api/asdf/users に301を返します /api/v2/users

つまり、指定されたバージョンが存在しない限り、基本的に、バージョンを指定しないものは最新のものにリンクし、リダイレクトされます。

これは私がこれまでに持っているものです:

scope 'api', :format => :json do
  scope 'v:api_version', :api_version => /[12]/ do
    resources :users
  end

  match '/*path', :to => redirect { |params| "/api/v2/#{params[:path]}" }
end

回答:


280

この回答の元の形式は大きく異なり、ここで見つけることができます。猫の皮をむく方法は複数あることを証明してください。

名前空間を使用し、デフォルトの302ではなく301リダイレクトを使用するようにしてから、回答を更新しました。これらについてのプロンプトを提供してくれたpixeltrixとBo Jeanesに感謝します。


これはあなたの心爆破するので、あなた本当に強いヘルメットを着用したいかもしれません。

Rails 3ルーティングAPIは非常に邪魔です。上記の要件に従って、APIのルートを作成するには、次のものが必要です。

namespace :api do
  namespace :v1 do
    resources :users
  end

  namespace :v2 do
    resources :users
  end
  match 'v:api/*path', :to => redirect("/api/v2/%{path}")
  match '*path', :to => redirect("/api/v2/%{path}")
end

この時点でまだ心が無傷なら、説明させてください。

まず、namespace同じ名前の特定のパスとモジュールをスコープとする一連のルートが必要な場合に、どれが非常に便利かを示します。この場合、ブロック内のすべてのルートをモジュールnamespace内のコントローラーにスコープ指定し、Apiこのルート内のパスへのすべてのリクエストにプレフィックスを付けapiます。などのリクエストは/api/v2/users知っていますか?

名前空間内で、さらに2つの名前空間を定義します(すごい!)。今回は「v1」名前空間を定義しているため、ここでのコントローラーのすべてのルートは、V1モジュール内のApiモジュール内にありますApi::V1resources :usersこのルート内で定義すると、コントローラーはに配置されApi::V1::UsersControllerます。これはバージョン1であり、のようなリクエストを行うことでそこに到達します/api/v1/users

バージョン2にのみある小さな少し異なります。それを提供するコントローラーの代わりにになりApi::V1::UsersControllerましたApi::V2::UsersController。あなたはのようなリクエストをすることでそこに着きます/api/v2/users

次に、a matchが使用されます。これは、などに向かうすべてのAPIルートに一致します/api/v3/users

これは私が調べなければならなかった部分です。この:to =>オプションを使用すると、特定のリクエストを別の場所にリダイレクトするように指定できます-私はそれを知っていました-しかし、それを別の場所にリダイレクトして、元のリクエストの一部を一緒に渡す方法を知りませんでした。

これを行うには、redirectメソッドを呼び出し、特別な補間%{path}パラメーターを使用して文字列を渡します。このfinal matchに一致するリクエストが届くと、文字列内のpath場所にパラメータを補間し%{path}、ユーザーを必要な場所にリダイレクトします。

最後に、別のmatchパスを使用して/api、接頭辞が付いた残りのすべてのパスをルーティングし、それらをにリダイレクトします/api/v2/%{path}。これは、のようなリクエスト/api/usersがに移動することを意味します/api/v2/users

私が取得する方法を見つけ出すことができなかった/api/asdf/usersことをに要求することになっている場合、どのように決定しないため、一致します/api/<resource>/<identifier>/api/<version>/<resource>

とにかく、これは調査するのが楽しかったです。


24
親愛なるライアン・ビッグ。あなたは素晴らしいです。
不正行為者、2012年

18
Rubyヒーローの評判を測定するだけではありません。
Waseem 2012年

1
ライアン...これは実際には正確ではないと思います。これにより、単一の正規URLではなく、/ apiおよび/ api / v2が同じコンテンツを提供します。/ apiは/ api / v2にリダイレクトする必要があります(元の作成者が指定したとおり)。私は正しいルートがgist.github.com/2044335のようになることを期待しています(もちろん、私はテストしていません)。/ api / v [12] のみが200を返し、/ api / <bad version>は301を/ api / v2に返す必要があります
Bo Jeanes

2
ルートファイルで、301がデフォルトのリダイレクトになっていることには、十分な理由があります。ガイドから: Please note that this redirection is a 301 “Moved Permanently” redirect. Keep in mind that some web browsers or proxy servers will cache this type of redirect, making the old page inaccessible.
不正行為者

3
パスが正しくない場合、無限のリダイレクトが作成されませんか?たとえば、/ api / v3 / path_that_dont_match_the_routesをリクエストすると、無限のリダイレクトが作成されますよね?
ロビン

38

追加するもののカップル:

あなたのリダイレクトマッチは、特定のルートのために仕事に行くされていません- *apiparamは貪欲で、例えば、すべてを飲み込むだろう/api/asdf/users/1にリダイレクトされます/api/v2/1。のような通常のパラメータを使用したほうがよいでしょう:api。確かに/api/asdf/asdf/users/1、次のような場合には一致しませんが、APIにリソースをネストしている場合は、より良い解決策です。

ライアンなぜU NO LIKE namespace?:-)、例:

current_api_routes = lambda do
  resources :users
end

namespace :api do
  scope :module => :v2, &current_api_routes
  namespace :v2, &current_api_routes
  namespace :v1, &current_api_routes
  match ":api/*path", :to => redirect("/api/v2/%{path}")
end

これには、バージョン管理された名前付きルートの追加の利点があります。もう1つの注意- :module使用時の規則は、アンダースコア表記を使用することapi/v1です。例:'Api :: V1'ではありません。ある時点では後者は機能しませんでしたが、Rails 3.1で修正されたと思います。

また、APIのv3をリリースすると、ルートは次のように更新されます。

current_api_routes = lambda do
  resources :users
end

namespace :api do
  scope :module => :v3, &current_api_routes
  namespace :v3, &current_api_routes
  namespace :v2, &current_api_routes
  namespace :v1, &current_api_routes
  match ":api/*path", :to => redirect("/api/v3/%{path}")
end

もちろん、APIのバージョン間でルートが異なる可能性があります。その場合、これを行うことができます。

current_api_routes = lambda do
  # Define latest API
end

namespace :api do
  scope :module => :v3, &current_api_routes
  namespace :v3, &current_api_routes

  namespace :v2 do
    # Define API v2 routes
  end

  namespace :v1 do
    # Define API v1 routes
  end

  match ":api/*path", :to => redirect("/api/v3/%{path}")
end

最終的なケースにどのように対処しますか?すなわち/api/asdf/users?同様に/api/users/1?私は更新された回答でそれを理解できなかったので、あなたが方法を知っているかもしれないと思った
Ryan Bigg

簡単な方法はありません-すべてをキャッチする前にすべてのリダイレクトを定義する必要がありますが、たとえば/ api / users / * path => / api / v2 / usersのように、親リソースごとにリダイレクトを定義するだけで済みます。 /%{path}
pixeltrix

13

可能であれば、バージョンをURLに含めずにAcceptsヘッダーに入れるように、URLを再考することをお勧めします。このスタックオーバーフローの答えはうまくいきます:

APIバージョン管理のベストプラクティス

そしてこのリンクは、レールルーティングでそれを行う方法を正確に示しています:

http://freelancing-gods.com/posts/versioning_your_ap_is


これは、これを行うための優れた方法でもあり、おそらく "/ api / asdf / users"リクエストにも対応します。
ライアンビッグ2012年

9

ルートによるバージョン管理はあまり好きではありません。APIのバージョン管理のより簡単な形式をサポートするためにVersionCakeを構築しました。

それぞれのビュー(jbuilder、RABLなど)のファイル名にAPIバージョン番号を含めることで、バージョンを目立たなくし、下位互換性をサポートするための簡単な劣化を可能にします(たとえば、ビューのv5が存在しない場合、ビューのv4をレンダリングします)。


8

バージョンが明示的に要求されていない場合に、特定のバージョンにリダイレクトする理由がわかりません。バージョンが明示的に要求されていない場合に提供されるデフォルトバージョンを定義したいだけのようです。また、バージョンをURL構造に含めない方がバージョン管理をサポートするためのよりクリーンな方法であるとDavid Bockに同意します。

恥知らずなプラグイン:Versionistはこれらのユースケース(およびその他)をサポートします。

https://github.com/bploetz/versionist


2

Ryan Biggの回答がうまくいきました。

リダイレクトを通じてクエリパラメータも保持したい場合は、次のように実行できます。

match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }

2

今日これを実装し、RailsCasts-REST APIバージョニングで「正しい方法」であると私が信じるものを見つけました。とても簡単。メンテナンスが簡単です。とても効果的です。

追加lib/api_constraints.rb(vnd.exampleを変更する必要すらありません。)

class ApiConstraints
  def initialize(options)
    @version = options[:version]
    @default = options[:default]
  end

  def matches?(req)
    @default || req.headers['Accept'].include?("application/vnd.example.v#{@version}")
  end
end

config/routes.rbそのようなセットアップ

require 'api_constraints'

Rails.application.routes.draw do

  # Squads API
  namespace :api do
    # ApiConstaints is a lib file to allow default API versions,
    # this will help prevent having to change link names from /api/v1/squads to /api/squads, better maintainability
    scope module: :v1, constraints: ApiConstraints.new(version:1, default: true) do
      resources :squads do
        # my stuff was here
      end
    end
  end

  resources :squads
  root to: 'site#index'

編集あなたのコントローラ(すなわち/controllers/api/v1/squads_controller.rb

module Api
  module V1
    class SquadsController < BaseController
      # my stuff was here
    end
  end
end

そして、あなたからあなたのアプリ内のすべてのリンクを変更することができます/api/v1/squads/api/squads、あなたがすることができます簡単にでもリンクを変更することなく、新しいAPIバージョンを実装します

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.