Избегать ошибки PG:: InvalidTextRepresentation при использовании UUID Postgres в Rails

Я начал использовать тип UUID Postgres для всех полей id моих моделей. Отлично работает и поддерживается (по большей части) в Rails 4:

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

Проблема заключается в том, что Postgres будет вызывать ошибку, если вы попытаетесь найти строку, где id является X, но X не является правильно форматированной строкой UUID.

> 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

Итак, если мой пользователь находится на странице, где UUID находится в URL-адресе, и затем они пытаются изменить UUID, они получат ошибку 500 вместо 404. Или, возможно, они получают ссылку на объект, который нет дольше.

Как я могу избежать этого сценария сухим способом? Я не могу просто спасти PG::InvalidTextRepresentation и отображать 404, потому что другие вещи могут также вызвать эту ошибку.

UPDATE

Я думаю, что регулярное выражение в формате идентификационного параметра является чистым, и оно вызывает 404, если оно не соответствует:

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

Но у меня все еще есть проблема оставаться сухим; Я не хочу ставить это на каждый ресурс на своих маршрутах. Я могу объявить несколько ресурсов в одном выражении, но только если для него нет других вариантов, подобных действиям членов. Итак, возможно, лучший вопрос: есть ли способ установить регулярное выражение id для всех маршрутов?

Ответ 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 для своих идентификаторов, чтобы они были чистыми и сухими. Если бы у меня были некоторые модели с целыми идентификаторами, это было бы немного менее чисто.

Ответ 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 могут быть в разных формах (см. точное руководство), поэтому лучше всего их нормализовать, прежде чем проверять их или выполнять как нормализацию и проверки в то же время.

Вы можете поместить это в свой ApplicationController, если знаете, что все ваши параметры :id должны быть UUID или помещать логику в метод ApplicationController и before_filter :make_sure_id_is_a_uuid в нужные ему контроллеры.