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


141

Я намагаюся версію свого 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

Відповіді:


280

Оригінальна форма цієї відповіді дико відрізняється, і їх можна знайти тут . Тільки доказ того, що існує кілька способів шкіряти кота.

Я оновив відповідь, оскільки використовував простори імен та використовував 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.

Далі matchвикористовується a . Це відповідатиме всім маршрутам 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>?

У будь-якому випадку, це було цікаво для досліджень, і я сподіваюся, що це допоможе вам!


24
Шановний Райан Бігг. Ти геніальний.
малетор

18
Не просто вимірюється репутація Рубі Героя.
Waseem

1
Райан ... Я не думаю, що це насправді точно. Це матиме / api та / api / v2 однаковий вміст, а не єдину канонічну URL-адресу. / api має переспрямувати на / api / v2 (як вказав оригінальний автор). Я б очікував, що правильні маршрути виглядатимуть як щось на зразок gist.github.com/2044335 (надано, я цього не перевіряв). Тільки / api / v [12] має повернути 200, / api та / api / <погана версія> має повернути 301s до / api / v2
Бо 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

Ще кілька речей, які слід додати:

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

Як би ти вирішив остаточну справу? тобто /api/asdf/users?як і /api/users/1? Я не міг цього зрозуміти у своїй оновленій відповіді, тому зрозумів, що ви можете знати спосіб
Райан Бігг,

Непростий спосіб зробити це - вам доведеться визначити всі переадресації перед вилученням усіх, але вам потрібно буде зробити кожен для кожного батьківського ресурсу, наприклад / api / users / * path => / api / v2 / users /% {path}
pixeltrix

13

Якщо це взагалі можливо, я б запропонував переосмислити ваші URL-адреси, щоб версія не була в URL-адресі, а була розміщена в заголовку accept. Ця відповідь про переповнення стека добре відповідає:

Кращі практики для версії API?

і це посилання показує, як саме це зробити з маршрутизацією рейок:

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


Це також чудовий спосіб зробити це, і, ймовірно, може також задовольнити запит "/ api / asdf / users".
Райан Бігг

9

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

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


8

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

Безсоромний штекер: Versionist підтримує ці випадки використання (і багато іншого).

https://github.com/bploetz/versionist


2

Відповідь Райана Бігга працювала на мене.

Якщо ви також хочете зберегти параметри запиту через переспрямування, ви можете зробити це так:

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

2

Сьогодні я реалізував це і знайшов те, що, на мою думку, є «правильним шляхом» на RailsCasts - версії API REST . Так просто. Так ретельно. Так ефективно.

Додати 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, навіть не змінюючи посилання

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.