我开始在我所有模特的id字段中使用Postgres UUID类型。效果很好,并且在Rails 4中支持(大部分):
create_table :users, id: :uuid do |t|
# ...
end
问题是如果你试图找到一个id为X的行,但是X不是格式正确的UUID字符串,Postgres会引发错误。
> User.find "3ac093e2-3a5e-4744-b49f-117b032adc6c"
ActiveRecord::RecordNotFound # good, will cause a 404
> User.find "foobar"
PG::InvalidTextRepresentation: ERROR # bad, will cause a 500
因此,如果我的用户位于URL中UUID所在的页面上,然后他们尝试更改UUID,他们将获得500错误而不是404.或者他们可能会获得指向对象的链接存在的时间越长。
如何以干燥的方式避免这种情况?我不能拯救PG::InvalidTextRepresentation
并渲染404,因为other things也会导致此错误。
更新
我认为ID参数格式的正则表达式是干净的,如果它不匹配则会引发404:
resources :users, id: /uuid-regex-here/
但我仍然有待保持干燥的问题;我不想把它放在我的路线中的每一个资源上。我可以在一个语句中声明多个资源,但前提是不要像成员操作那样使用其他选项。 所以也许更好的问题是:有没有办法为所有路由设置id正则表达式?
答案 0 :(得分:7)
您可以通过constraints() do ... end
一次向多条路线添加路由约束。
我最终这样做并在所有:id
参数上设置全局约束以使其与UUID正则表达式匹配:
MyApp::Application.routes.draw do
constraints(id: /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i) do
# my routes here
end
end
这样,/ posts / 123或/ posts / foobar在调用控制器操作之前不再匹配/ posts /:id和404,从而避免了PG类型错误。
我的所有型号都会使用UUID作为ID,因此干净且干燥。如果我有一些带有整数ID的模型,那就不那么干净了。
答案 1 :(得分:2)
如果您不想为所有路由添加约束以捕获无效的UUID,那么您可以使用before_filter
进行修改,如下所示:
before_filter do
if(params.has_key?(:id))
uuid = params[:id].strip.downcase.gsub('-', '').gsub(/\A\{?(\h{32})\}?\z/, '\1')
raise ActiveRecord::RecordNotFound if(uuid.blank?)
end
end
请注意,UUID可以有多种形式(请参阅the fine manual),因此最好在验证它们之前对它们进行规范化,或者同时进行规范化和验证。
如果您知道所有ApplicationController
参数都应该是UUID,或者将逻辑放在:id
方法和ApplicationController
中,您可以将其放入before_filter :make_sure_id_is_a_uuid
需要它的控制器。