Управление версиями API для Rails Routes - PullRequest
139 голосов
/ 09 марта 2012

Я пытаюсь создать версию своего API, как в Stripe.Ниже приводится последняя версия API: 2.

/api/users возвращает от 301 до /api/v2/users

/api/v1/users возвращает индекс 200 пользователей в версии 1

/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

Ответы [ 7 ]

274 голосов
/ 09 марта 2012

Оригинальная форма этого ответа дико отличается, и можно найти здесь .Просто доказательство того, что существует более одного способа шкурить кошку.

Я обновил ответ с тех пор, чтобы использовать пространства имен и использовать перенаправления 301 - вместо значения по умолчанию 302. Благодаря pixeltrix и Bo Jeanesпобуждение к этим вещам.


Возможно, вы захотите надеть действительно сильный шлем, потому что это может взорвать ваш мозг .

API-интерфейс маршрутизации Rails 3 - супер злой.Чтобы написать маршруты для вашего 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, знаете ли?

Внутри пространства имен мы определяем еще два пространства имен (вау!).На этот раз мы определяем пространство имен «v1», поэтому все маршруты для контроллеров здесь будут внутри модуля V1 внутри модуля Api: Api::V1.Определив resources :users внутри этого маршрута, контроллер будет расположен на Api::V1::UsersController.Это версия 1, и вы получаете это, делая запросы вроде /api/v1/users.

Версия 2 только немного отличается крошечный .Вместо того, чтобы обслуживающий его контроллер находился на Api::V1::UsersController, теперь он на Api::V2::UsersController.Вы попадаете туда, делая запросы вроде /api/v2/users.

Далее, используется match.Это будет соответствовать всем маршрутам API, которые идут к таким вещам, как /api/v3/users.

Это та часть, которую мне пришлось искать.Опция :to => позволяет вам указать, что конкретный запрос должен быть перенаправлен куда-то еще - я знал это очень много - но я не знал, как заставить его перенаправить куда-то еще и передать часть исходного запросавместе с ним.

Для этого мы вызываем метод redirect и передаем ему строку со специальным интерполированным параметром %{path}.Когда поступает запрос, соответствующий этому окончательному match, он интерполирует параметр path в расположение %{path} внутри строки и перенаправляет пользователя туда, куда ему нужно перейти.

Наконец,мы используем другой match для маршрутизации всех оставшихся путей с префиксом /api и перенаправления их на /api/v2/%{path}.Это означает, что запросы типа /api/users перейдут на /api/v2/users.

. Я не мог понять, как заставить /api/asdf/users соответствовать, потому что, как вы определяете, должен ли это быть запрос к /api/<resource>/<identifier> или /api/<version>/<resource>?

В любом случае, это было интересно исследовать, и я надеюсь, что это поможет вам!

37 голосов
/ 09 марта 2012

Несколько вещей для добавления:

Ваше совпадение с перенаправлением не будет работать для определенных маршрутов - параметр *api является жадным и поглощает все, например /api/asdf/users/1 будет перенаправлять на /api/v2/1.Тебе лучше использовать обычный параметр типа :api.По общему признанию это не будет соответствовать случаям как /api/asdf/asdf/users/1, но если у вас есть вложенные ресурсы в вашем API, это лучшее решение.

Райан, ПОЧЕМУ НЕТ КАК 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

, который имеет дополнительное преимущество версий и общих именованных маршрутов.Еще одно примечание - при использовании :module принято использовать символ подчеркивания, например: api/v1, а не 'Api :: V1'.В какой-то момент последний не работал, но я считаю, что это было исправлено в Rails 3.1.

Кроме того, когда вы выпускаете v3 вашего API, маршруты будут обновляться следующим образом:

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
13 голосов
/ 15 марта 2012

Если это вообще возможно, я бы предложил переосмыслить ваши URL, чтобы версия не была в URL, а помещалась в заголовок accept.Этот ответ о переполнении стека хорошо вписывается в него:

Рекомендации по управлению версиями API?

, и эта ссылка показывает, как именно это сделать с маршрутизацией рельсов:

http://freelancing -gods.com / сообщений / versioning_your_ap_is

9 голосов
/ 22 ноября 2012

Я не большой поклонник версий по маршрутам. Мы создали VersionCake для поддержки более простой формы управления версиями API.

Включая номер версии API в имя файла каждого из наших соответствующих представлений (jbuilder, RABL и т. Д.), Мы сохраняем управление версиями ненавязчиво и допускаем простую деградацию для поддержки обратной совместимости (например, если v5 представления не существует, мы визуализируем v4 представления).

8 голосов
/ 12 апреля 2012

Я не уверен, почему вы хотите перенаправить на конкретную версию, если версия явно не запрашивается.Похоже, вы просто хотите определить версию по умолчанию, которая обслуживается, если явно не запрашивается версия.Я также согласен с Дэвидом Боком в том, что сохранение версий вне структуры URL - более чистый способ поддержки управления версиями.

Бесстыдный плагин: Versionist поддерживает эти варианты использования (и более).

https://github.com/bploetz/versionist

1 голос
/ 02 июня 2019

Реализовал это сегодня и нашел, как я считаю, «правильный путь» на RailsCasts - REST API Versioning .Так просто.Так что ремонтопригодно.Так эффективно.

Добавить 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, и вы сможете EASILY внедрять новые версии API бездаже необходимость изменить ссылки

0 голосов
/ 17 января 2014

Ответ Райана Бигга работал для меня.

Если вы также хотите сохранить параметры запроса при перенаправлении, вы можете сделать это следующим образом:

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