Rails — find_by и где работает не так, как ожидалось

#ruby-on-rails

Вопрос:

Версия Rails — 6.0.3.6

У меня есть одна таблица subscription_attempts, и в ней есть один столбец attempt_token , который является уникальным случайным маркером для каждой записи, и он генерируется на основе has_secure_token rails.

У меня есть один метод, который попытается найти запись на основе attempt_token или идентификатора. Ниже приведен код метода

 def random_method(value)
  find_by(id: value) || find_by(attempt_token: value)
end
 

Все работало нормально, пока я не получил одну попытку, которая начинается с цифры. Вот это значение attempt_token — 5HqBToVVbFVL4T6TLzuuKju8

Когда я использую этот метод, он выдает мне запись о подписке с идентификатором 5. Я также попробовал с помощью where, и результат тот же.

введите описание изображения здесь

Когда я попытался выполнить этот запрос с исключением where got ниже

 SubscriptionAttempt.where('id = ?', '5HqBToVVbFVL4T6TLzuuKju8')

ActiveRecord::StatementInvalid (PG::InvalidTextRepresentation: ERROR:  invalid input syntax for type bigint: "5HqBToVVbFVL4T6TLzuuKju8")
LINE 1: ...empts".* FROM "subscription_attempts" WHERE (id = '5HqBToVVb...
 

Я предполагаю, что эти методы должны вести себя не так. Это должно дать nil результат, если значение не совпадает со значением базы данных. Может кто-нибудь, пожалуйста, дать мне решение этой проблемы?

Ответ №1:

ActiveRecord автоматически выполняет приведение типов перед запросом к базе данных. Вы можете прочитать больше об этом здесь. В принципе, поскольку ActiveRecord знает, что id столбец является целым числом, он приведет value переданное find_by(id: value) значение к целому числу и:

 > '5HqBToVVbFVL4T6TLzuuKju8'.to_i
=> 5
 

Попробуйте изменить порядок:

 def random_method(value)
  find_by(attempt_token: value) || find_by(id: value)
end
 

Это все еще не является 100% пуленепробиваемым в случае, если у вас есть экземпляр с идентификатором attempt_token "1234" и другой экземпляр с идентификатором 1234 . Первое всегда будет возвращено, даже если вы намеревались забрать второе.

Другим решением было бы добавить постоянный префикс к вашему attempt_token :

 ATTEMPT_TOKEN_PREFIX = 'at_'.freeze

def attempt_token_with_prefix
  # provide this value to the user instead of just providing the raw attempt_token
  "#{ATTEMPT_TOKEN_PREFIX}#{attempt_token}"
end

def random_method(value)
  if value.starts_with?(ATTEMPT_TOKEN_PREFIX)
    find_by(attempt_token: value[ATTEMPT_TOKEN_PREFIX.length..])
  else
    find_by(id: value)
  end
end

 

Комментарии:

1. Я справлялся с этим по-другому value =~ /Ad Z/ ? find_by(id: value) : find_by(attempt_token: value) .