Избегать ошибки 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
в нужные ему контроллеры.