【问题标题】:Avoid PG::InvalidTextRepresentation error when using Postgres UUID in Rails在 Rails 中使用 Postgres UUID 时避免 PG::InvalidTextRepresentation 错误
【发布时间】:2014-02-15 19:31:00
【问题描述】:

我开始对所有模型的 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。或者他们可能会获得指向不存在的对象的链接不再存在。

我怎样才能以 DRY 的方式避免这种情况?我不能只拯救PG::InvalidTextRepresentation 并渲染404,因为other things 也会导致此错误。

更新

我认为 ID 参数格式的正则表达式是干净的,如果不匹配,它会引发 404:

resources :users, id: /uuid-regex-here/

但我仍然有保持干燥的问题;我不想把它放在我路线中的每一个资源上。我可以在一个语句中声明多个资源,但前提是它的其他选项不像成员操作。 所以也许一个更好的问题是:有没有办法为所有路由设置 id 正则表达式?

【问题讨论】:

  • 你为什么要通过 foobar?你的路线难道不应该在它到达你的模型之前就捕捉到它吗?
  • @Denis 我想我可以对 id 参数施加约束,确保它与 UUID 正则表达式匹配。但是我必须对路线中的每一个资源都这样做......还是你有其他想法?
  • 也许你的控制器的before_filter 会起作用。
  • @tybro0103: 就我个人而言,如果我忘记让自己的路由验证 id 的格式是否正确,或者某个随机调用发送的 id 格式错误,我实际上会要他们咳一个错误。我对 Rails 不够熟悉,无法准确说明如何大规模自动验证它们,但我猜 mu 对他的建议有所了解。
  • @Denis:潜在的问题是 Rails 的人懒得把验证放在正确的地方,他们假设 Model.find 如果没有找到任何记录,就会引发 RecordNotFound原因(当 PK 为整数时,包括类型错误,例如 M.find('pancakes'))。在 Rails 模型中,为路线添加约束可能是正确的做法,过滤器是一种快速破解。 Rails 充斥着未说明的假设(尝试添加包含电子邮件地址的路由,看看会发生什么),当您偏离常规路径时,它会横向移动。

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


【解决方案1】:

您可以通过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 的模型,那会不太干净。

【讨论】:

  • 如果你使用 UUID gem,你可以使用他们的 validate 方法而不是滚动你自己的github.com/assaf/uuid/blob/master/lib/uuid.rb#L199
  • @BrianHahn 很高兴知道。虽然我并没有真正自己动手......这只是一个正则表达式。
  • 我的意思是它会在你已经拥有 UUID gem 的情况下滚动。当 UUID gem 不是项目中的依赖项时,您的正则表达式似乎很好!
【解决方案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),因此最好在验证它们之前对其进行规范化,或者同时进行规范化和验证。

如果您知道您的所有 :id 参数都应该是 UUID 或将逻辑放入 ApplicationController 方法和 before_filter :make_sure_id_is_a_uuid 中需要它的控制器,您可以将其放入您的 ApplicationController

【讨论】:

  • 这还不错。如果我找不到将正则表达式添加到路由中一次的方法,我最终会这样做。
  • 另一种猴子补丁可能是 AR PostgreSQL 驱动程序中的类型转换内容,其中某处有一个很大的 case 语句。
猜你喜欢
  • 1970-01-01
  • 2018-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-28
  • 1970-01-01
相关资源
最近更新 更多