J'essaie de mettre à niveau mon API comme Stripe. Ci-dessous, la dernière version de l’API est la version 2.
/api/users
renvoie un 301 à /api/v2/users
/api/v1/users
renvoie un index de 200 utilisateurs en version 1
/api/v3/users
renvoie un 301 à /api/v2/users
/api/asdf/users
renvoie un 301 à /api/v2/users
Donc, si tout ce qui ne spécifie pas la version est lié à la version la plus récente, à moins que la version spécifiée n'existe, redirigez-la.
Voici ce que j'ai jusqu'à présent:
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
La forme originale de cette réponse est extrêmement différente et peut être trouvée ici . Preuve qu'il existe plus d'une façon de peler un chat.
Depuis, j'ai mis à jour la réponse pour utiliser des espaces de noms et des redirections 301 - plutôt que la valeur par défaut de 302. Merci à pixeltrix et Bo Jeanes pour les précisions à ce sujet.
Vous voudrez peut-être porter un casque vraiment fort parce que cela va vous étonner .
L'API de routage Rails 3 est super méchante. Pour écrire les routes pour votre API, conformément à vos exigences ci-dessus, il vous suffit de ceci:
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
Si votre esprit est toujours intact après ce point, laissez-moi vous expliquer.
Tout d’abord, nous appelons namespace
, ce qui est très pratique lorsque vous souhaitez regrouper plusieurs itinéraires menant à un chemin et à un module spécifiques portant le même nom. Dans ce cas, nous voulons que toutes les routes du bloc namespace
soient étendues aux contrôleurs du module Api
et que toutes les demandes d'accès aux chemins de cette route soient précédées de api
. Des demandes telles que /api/v2/users
, tu sais?
Dans l'espace de noms, nous définissons deux autres espaces de noms (woah!). Cette fois, nous définissons l’espace de noms "v1", de sorte que toutes les routes des contrôleurs ici seront dans le module V1
dans le module Api
: Api::V1
. En définissant resources :users
à l'intérieur de cette route, le contrôleur sera situé à Api::V1::UsersController
. C'est la version 1, et vous y arrivez en faisant des requêtes comme /api/v1/users
.
La version 2 n’est qu’un peu minuscule. Au lieu que le contrôleur le servant soit à Api::V1::UsersController
, il est maintenant à Api::V2::UsersController
. Vous y arrivez en faisant des requêtes comme /api/v2/users
.
Ensuite, un match
est utilisé. Cela correspondra à toutes les routes d'API qui vont à des choses comme /api/v3/users
.
C'est la partie que j'ai dû chercher. L'option :to =>
vous permet de spécifier qu'une requête spécifique doit être redirigée ailleurs - je le savais déjà - mais je ne savais pas comment la faire rediriger vers une autre destination et y passer un élément de la demande originale avec elle.
Pour ce faire, nous appelons la méthode redirect
et lui passons une chaîne avec un paramètre %{path}
spécial-interpolé. Lorsqu'une demande arrive qui correspond à cette dernière match
, il interpolera le paramètre path
dans l'emplacement de %{path}
à l'intérieur de la chaîne et redirigera l'utilisateur vers l'endroit où il doit aller.
Enfin, nous utilisons un autre match
pour router tous les chemins restants avec le préfixe /api
et les rediriger vers /api/v2/%{path}
. Cela signifie que les demandes comme /api/users
iront à /api/v2/users
.
Je n'arrivais pas à comprendre comment faire correspondre /api/asdf/users
, car comment déterminer si cela est supposé être une demande adressée à /api/<resource>/<identifier>
ou /api/<version>/<resource>
?
Quoi qu’il en soit, c’était amusant de faire de la recherche et j’espère que cela vous aidera!
Quelques choses à ajouter:
Votre correspondance de redirection ne fonctionnera pas pour certaines routes - la *api
param est gourmand et engloutit tout, par ex. /api/asdf/users/1
va rediriger vers /api/v2/1
. Vous feriez mieux d'utiliser un paramètre régulier comme :api
. Certes, cela ne correspondra pas à des cas comme /api/asdf/asdf/users/1
mais si vous avez des ressources imbriquées dans votre API, c'est une meilleure solution.
Ryan POURQUOI VOUS N'AIME PAS namespace
? :-), par exemple:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v2, ¤t_api_routes
namespace :v2, ¤t_api_routes
namespace :v1, ¤t_api_routes
match ":api/*path", :to => redirect("/api/v2/%{path}")
end
Qui a l’avantage supplémentaire des itinéraires nommés versionnés et génériques. Une note supplémentaire - la convention d'utilisation de :module
consiste à utiliser la notation de soulignement, par exemple: api/v1
pas 'Api :: V1'. À un moment donné, ce dernier ne fonctionnait pas mais je crois que cela a été corrigé dans Rails 3.1.
En outre, lorsque vous publiez la v3 de votre API, les itinéraires sont mis à jour comme suit:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v3, ¤t_api_routes
namespace :v3, ¤t_api_routes
namespace :v2, ¤t_api_routes
namespace :v1, ¤t_api_routes
match ":api/*path", :to => redirect("/api/v3/%{path}")
end
Bien sûr, il est probable que votre API utilise des routes différentes entre les versions, auquel cas vous pouvez le faire:
current_api_routes = lambda do
# Define latest API
end
namespace :api do
scope :module => :v3, ¤t_api_routes
namespace :v3, ¤t_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
Si possible, je suggérerais de repenser vos URL pour que la version ne soit pas dans l'URL, mais soit placée dans l'en-tête accept. Ce dépassement de pile répond bien:
Meilleures pratiques pour la gestion des versions d'API?
et ce lien montre exactement comment faire cela avec Rails routage:
Je ne suis pas un grand fan de la gestion des versions par routes. Nous avons construit VersionCake pour prendre en charge une forme plus simple de gestion des versions d'API.
En incluant le numéro de version de l’API dans le nom de fichier de chacune de nos vues respectives (jbuilder, RABL, etc.), nous gardons le contrôle de version discret et permettons une dégradation aisée pour prendre en charge la rétrocompatibilité (par exemple, si la v5 de la vue n’existe pas, rendre v4 de la vue).
Je ne sais pas pourquoi vous voulez rediriger vers une version spécifique si une version n'est pas explicitement demandée. On dirait que vous voulez simplement définir une version par défaut qui sera servie si aucune version n'est explicitement demandée. Je conviens également avec David Bock que le fait de garder les versions en dehors de la structure de l'URL est une manière plus simple de prendre en charge le versioning.
Prise sans vergogne: Le versionniste prend en charge ces cas d'utilisation (et plus).
Mis en œuvre aujourd'hui et trouvé ce que je crois être la "bonne manière" sur RailsCasts - REST Versioning API) . Si simple. Si maintenable. Si efficace.
Ajouter lib/api_constraints.rb
_ (il n'est même pas nécessaire de changer 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
Installer config/routes.rb
ainsi
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'
Editez votre contrôleur (ie /controllers/api/v1/squads_controller.rb
)
module Api
module V1
class SquadsController < BaseController
# my stuff was here
end
end
end
Ensuite, vous pouvez modifier tous les liens de votre application à partir de /api/v1/squads
à /api/squads
et vous pouvez [~ # ~] facilement [~ # ~] implémenter de nouvelles versions d’API sans même avoir à changer de lien
La réponse de Ryan Bigg a fonctionné pour moi.
Si vous souhaitez également conserver les paramètres de requête via la redirection, procédez comme suit:
match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }