Search code examples
ruby-on-railspostgresqlruby-on-rails-4rails-activerecordrails-postgresql

Avoid PG::InvalidTextRepresentation error when using Postgres UUID in Rails


I started using Postgres UUID type for all my models' id fields. Works great and is supported (for the most part) in Rails 4:

create_table :users, id: :uuid do |t|
  # ...
end

The problem is that Postgres will raise an error if you attempt to find a row where id is X, but X is not a properly formatted UUID string.

> 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

So if my user is on a page where a UUID is in the URL, and they then try to change the UUID, they'll get a 500 error instead of 404. Or perhaps they get a link to an object that no longer exists.

How can I go about avoiding this scenario in a DRY way? I can't just rescue the PG::InvalidTextRepresentation and render 404 because other things can cause this error as well.

UPDATE

I think that a regex on the format of the ID param is clean, and it raises a 404 if it doesn't match:

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

But I still have the problem of staying DRY; I don't want to put this on every single resource in my routes. I can declare multiple resources in one statement, but only if don't other options to it like member actions. So perhaps a better question is: Is there a way to set the id regex for all routes?


Solution

  • You can add a routing constraint to multiple routes at a time via constraints() do ... end.

    I ended up doing this and setting a global constraint on all :id params to match it to a UUID regexp:

    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
    

    This way, /posts/123 or /posts/foobar no longer match /posts/:id and 404 before ever invoking the controller action, thus avoiding the PG type error.

    All of my models will use UUID for their IDs so this is clean and DRY. If I had some models with integer IDs as well, it'd be a little less clean.