在Rails中使用Postgres UUID时,请避免PG :: InvalidTextRepresentation错误

时间:2014-01-24 17:35:30

标签: ruby-on-rails postgresql ruby-on-rails-4 rails-activerecord rails-postgresql

我开始在我所有模特的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正则表达式?

2 个答案:

答案 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需要它的控制器。