Rails路由的API版本控制

时间:2012-03-09 01:04:02

标签: ruby-on-rails ruby-on-rails-3 routes versioning

我正在尝试像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

7 个答案:

答案 0 :(得分:274)

The original form of this answer is wildly different, and can be found here。只是证明有一种方法可以给猫皮肤。

我更新了答案,因为要使用命名空间并使用301重定向 - 而不是默认值302.感谢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之类的请求,你知道吗?

在命名空间内,我们定义了两个名称空间(哇!)。这次我们定义了“v1”命名空间,因此这里控制器的所有路由都位于V1模块内Api模块内:Api::V1。通过在此路由中定义resources :users,控制器将位于Api::V1::UsersController。这是版本1,您可以通过发出/api/v1/users等请求来实现目标。

版本2只是 tiny 位不同。而不是控制器服务于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>的请求?

无论如何,研究很有趣,希望对你有帮助!

答案 1 :(得分:37)

要添加的几件事情:

您的重定向匹配对某些路由不起作用 - *api param是贪婪的并且会吞噬所有内容,例如: /api/asdf/users/1会重定向到/api/v2/1。你最好使用像:api这样的常规参数。不可否认,它不会与/api/asdf/asdf/users/1之类的情况相匹配,但如果你的api中有嵌套资源,这是一个更好的解决方案。

Ryan为什么你不喜欢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中修复。

此外,当您发布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

答案 2 :(得分:13)

如果可能的话,我建议您重新考虑您的网址,以便版本不在网址中,但会被放入接受标头中。这个堆栈溢出答案很好地解决了:

Best practices for API versioning?

此链接显示了如何使用rails routing执行此操作:

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

答案 3 :(得分:9)

我不是路由版本控制的忠实粉丝。我们构建了VersionCake来支持更简单的API版本控制。

通过在我们各自的视图(jbuilder,RABL等)的文件名中包含API版本号,我们保持版本控制不引人注意并允许容易降级以支持向后兼容性(例如,如果视图的v5不存在,我们渲染视图的v4)。

答案 4 :(得分:8)

如果未明确请求版本,我不确定您为什么要重定向到特定版本。您似乎只想定义在未明确请求版本的情况下提供的默认版本。我同意David Bock的说法,保留版本的URL结构是一种更简洁的方式来支持版本控制。

无耻插件:Versionist支持这些用例(等等)。

https://github.com/bploetz/versionist

答案 5 :(得分:1)

今天实现了这一点,并在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,并且可以轻松实施新的api版本,甚至不必更改链接

答案 6 :(得分:0)

Ryan Bigg的回答对我有用。

如果您还想通过重定向保留查询参数,可以这样做:

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