Ruby-on-rails Rails 路由的 API 版本控制
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9627546/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
API Versioning for Rails Routes
提问by maletor
I'm trying to version my API like Stripe has. Below is given the latest API version is 2.
我正在尝试像 Stripe 一样对我的 API 进行版本控制。下面给出了最新的 API 版本是 2。
/api/usersreturns a 301 to /api/v2/users
/api/users返回 301 到 /api/v2/users
/api/v1/usersreturns a 200 of users index at version 1
/api/v1/users在版本 1 中返回 200 个用户索引
/api/v3/usersreturns a 301 to /api/v2/users
/api/v3/users返回 301 到 /api/v2/users
/api/asdf/usersreturns a 301 to /api/v2/users
/api/asdf/users返回 301 到 /api/v2/users
So that basically anything that doesn't specify the version links to the latest unless the specified version exists then redirect to it.
所以基本上任何没有指定版本链接到最新的东西,除非指定的版本存在,然后重定向到它。
This is what I have so far:
这是我到目前为止:
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
回答by Ryan Bigg
The original form of this answer is wildly different, and can be found here. Just proof that there's more than one way to skin a cat.
这个答案的原始形式大不相同,可以在这里找到。只是证明有不止一种方法可以给猫剥皮。
I've updated the answer since to use namespaces and to use 301 redirects -- rather than the default of 302. Thanks to pixeltrix and Bo Jeanes for the prompting on those things.
我已经更新了答案,因为使用命名空间和使用 301 重定向——而不是默认的 302。感谢 pixeltrix 和 Bo Jeanes 对这些事情的提示。
You might want to wear a reallystrong helmet because this is going to blow your mind.
你可能想戴一个非常结实的头盔,因为这会让你大吃一惊。
The Rails 3 routing API is super wicked. To write the routes for your API, as per your requirements above, you need just this:
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
If your mind is still intact after this point, let me explain.
如果在这之后你的头脑仍然完好无损,让我解释一下。
First, we call namespacewhich is super handy for when you want a bunch of routes scoped to a specific path and module that are similarly named. In this case, we want all routes inside the block for our namespaceto be scoped to controllers within the Apimodule and all requests to paths inside this route will be prefixed with api. Requests such as /api/v2/users, ya know?
首先,我们调用namespacewhich 非常方便,当您想要一组范围限定于特定路径和具有相似名称的模块的路由时。在这种情况下,我们希望块内的所有路由都适用namespace于Api模块内的控制器,并且对该路由内路径的所有请求都将以api. 诸如/api/v2/users,你知道吗?
Inside the namespace, we define two more namespaces (woah!). This time we're defining the "v1" namespace, so all routes for the controllers here will be inside the V1module inside the Apimodule: Api::V1. By defining resources :usersinside this route, the controller will be located at Api::V1::UsersController. This is version 1, and you get there by making requests like /api/v1/users.
在命名空间内,我们定义了另外两个命名空间(哇!)。这次我们定义了“v1”命名空间,所以这里控制器的所有路由都将在V1模块内的Api模块内:Api::V1。通过resources :users在此路由内定义,控制器将位于Api::V1::UsersController。这是第 1 版,您可以通过发出类似/api/v1/users.
Version 2 is only a tinybit different. Instead of the controller serving it being at Api::V1::UsersController, it's now at Api::V2::UsersController. You get there by making requests like /api/v2/users.
第2版只是一个很小的有点不同。服务它的控制器不是在Api::V1::UsersController,而是现在在Api::V2::UsersController。您可以通过发出诸如/api/v2/users.
Next, a matchis used. This will match all API routes that go to things like /api/v3/users.
接下来,使用 a match。这将匹配所有指向诸如/api/v3/users.
This is the part I had to look up. The :to =>option allows you to specify that a specific request should be redirected somewhere else -- I knew that much -- but I didn't know how to get it to redirect to somewhere else and pass in a piece of the original request along with it.
这是我必须查找的部分。该:to =>选项允许您指定应将特定请求重定向到其他地方——我知道很多——但我不知道如何让它重定向到其他地方并连同它一起传递原始请求的一部分.
To do this, we call the redirectmethod and pass it a string with a special-interpolated %{path}parameter. When a request comes in that matches this final match, it will interpolate the pathparameter into the location of %{path}inside the string and redirect the user to where they need to go.
为此,我们调用该redirect方法并向其传递一个带有特殊插值%{path}参数的字符串。当一个请求与这个 final 匹配时match,它会将path参数插入到%{path}字符串内部的位置并将用户重定向到他们需要去的地方。
Finally, we use another matchto route all remaining paths prefixed with /apiand redirect them to /api/v2/%{path}. This means requests like /api/userswill go to /api/v2/users.
最后,我们使用另一个match路由所有以 为前缀的剩余路径/api并将它们重定向到/api/v2/%{path}. 这意味着像这样的请求/api/users将转到/api/v2/users.
I couldn't figure out how to get /api/asdf/usersto match, because how do you determine if that is supposed to be a request to /api/<resource>/<identifier>or /api/<version>/<resource>?
我不知道如何/api/asdf/users匹配,因为你如何确定这是否应该是对/api/<resource>/<identifier>或的请求/api/<version>/<resource>?
Anyway, this was fun to research and I hope it helps you!
无论如何,这对研究很有趣,我希望它对您有所帮助!
回答by pixeltrix
A couple of things to add:
补充几点:
Your redirect match isn't going to work for certain routes - the *apiparam is greedy and will swallow up everything, e.g. /api/asdf/users/1will redirect to /api/v2/1. You'd be better off using a regular param like :api. Admittedly it won't match cases like /api/asdf/asdf/users/1but if you have nested resources in your api it's a better solution.
您的重定向匹配不适用于某些路由 -*api参数是贪婪的并且会吞噬一切,例如/api/asdf/users/1将重定向到/api/v2/1. 你最好使用像:api. 诚然,它不会匹配这样的情况,/api/asdf/asdf/users/1但如果您的 api 中有嵌套资源,这是一个更好的解决方案。
Ryan WHY U NO LIKE namespace? :-), e.g:
Ryan 为什么你不喜欢namespace?:-),例如:
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
Which has the added benefit of versioned and generic named routes. One additional note - the convention when using :moduleis to use underscore notation, e.g: api/v1not 'Api::V1'. At one point the latter didn't work but I believe it was fixed in Rails 3.1.
它具有版本化和通用命名路由的额外好处。附加说明 - 使用时的约定:module是使用下划线表示法,例如:api/v1不是 'Api::V1'。有一次后者不起作用,但我相信它已在 Rails 3.1 中修复。
Also, when you release v3 of your API the routes would be updated like this:
此外,当您发布 API 的 v3 时,路由将更新如下:
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
Of course it's likely that your API has different routes between versions in which case you can do this:
当然,您的 API 可能在版本之间具有不同的路由,在这种情况下,您可以这样做:
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
回答by David Bock
If at all possible, I would suggest rethinking your urls so that the version isn't in the url, but is put into the accepts header. This stack overflow answer goes into it well:
如果可能的话,我建议重新考虑您的网址,以便版本不在网址中,而是放入接受标头中。这个堆栈溢出答案很好地说明了这一点:
Best practices for API versioning?
and this link shows exactly how to do that with rails routing:
这个链接确切地显示了如何使用 rails 路由来做到这一点:
回答by aantix
I'm not a big fan of versioning by routes. We built VersionCaketo support an easier form of API versioning.
我不喜欢按路线进行版本控制。我们构建了VersionCake来支持更简单的 API 版本控制形式。
By including the API version number in the filename of each of our respective views (jbuilder, RABL, etc), we keep the versioning unobtrusive and allow for easy degradation to support backwards compatibility (e.g. if v5 of the view doesn't exist, we render v4 of the view).
通过在我们每个视图(jbuilder、RABL 等)的文件名中包含 API 版本号,我们保持版本控制不显眼,并允许轻松降级以支持向后兼容性(例如,如果视图的 v5 不存在,我们渲染视图的 v4)。
回答by Brian Ploetz
I'm not sure why you want to redirectto a specific version if a version isn't explicitly requested. Seems like you simply want to define a default version that gets served up if no version is explicitly requested. I also agree with David Bock that keeping versions out of the URL structure is a cleaner way to support versioning.
如果未明确请求版本,我不确定为什么要重定向到特定版本。似乎您只是想定义一个默认版本,如果没有明确请求版本,则会提供该版本。我也同意 David Bock 的观点,即在 URL 结构之外保留版本是支持版本控制的更简洁的方式。
Shameless plug: Versionist supports these use cases (and more).
无耻的插件:Versionist 支持这些用例(以及更多)。
回答by Amed Rodríguez
Ryan Bigg answer worked for me.
Ryan Bigg 的回答对我有用。
If you also want to keep query parameters through the redirect, you can do it like this:
如果您还想通过重定向保留查询参数,您可以这样做:
match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }
回答by weteamsteve
Implemented this today and found what I believe to be the 'right way' on RailsCasts - REST API Versioning. So simple. So maintainable. So effective.
今天实现了这一点,并在RailsCasts - REST API Versioning上找到了我认为的“正确方法” 。很简单。所以可维护。如此有效。
Add lib/api_constraints.rb(don't even have to change vnd.example.)
添加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
Setup config/routes.rblike so
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'
Edit your controller (ie /controllers/api/v1/squads_controller.rb)
编辑您的控制器(即/controllers/api/v1/squads_controller.rb)
module Api
module V1
class SquadsController < BaseController
# my stuff was here
end
end
end
Then you can change all links in your app from /api/v1/squadsto /api/squadsand you can EASILYimplement new api versions without even having to change links
然后你就可以改变你的应用程序的各个环节,从/api/v1/squads到/api/squads,你可以轻轻松松实现新的API版本,甚至无需更改链接

