Версии API для маршрутов Rails

Я пытаюсь изменить свой 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

Ответ 1

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

Я обновил ответ, так как для использования пространств имен и для использования 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, вы знаете?

Внутри пространства имен мы определяем еще два пространства имен (woah!). На этот раз мы определяем пространство имен "v1", поэтому все маршруты для контроллеров здесь будут внутри модуля V1 внутри модуля Api: Api::V1. Определив resources :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}. Когда приходит запрос, который соответствует этому окончательному match, он будет интерполировать параметр path в местоположение %{path} внутри строки и перенаправить пользователя туда, куда ему нужно идти.

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

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

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

Ответ 2

Несколько вещей, которые нужно добавить:

Ваше соответствие переадресации не будет работать для определенных маршрутов - параметр *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 not '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

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

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

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

https://github.com/bploetz/versionist

Ответ 6

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

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

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