Каждый рубист, поработавший с Ruby On Rails знаком с ORM ActiveRecord. Обсудим одну из предложенных из коробки валидаций, а именно, валидации на уникальность, и почему database_validations gem спасет консистенцию вашей базы данных.
Допустим, у вас есть модель пользователей с уникальностью на поле email, т.е.
Вы, возможно, уже знаете, что данная валидация выполняет следующий запрос
каждый раз, когда мы пытаемся сохранить запись в базу данных.
У данного подхода, есть несколько недостатков:
Во-первых, исполнение дополнительного запроса, и в случае, если в модели инициализировано несколько валидаций на уникальность, запрос будет выполнен на каждую из них. Это не эффективно, а также требует наличие индексов, если мы хотим, чтобы данные запросы исполнялись быстро.
Во-вторых, данное решение не гарантирует уникальность из-за возможной гонки за данными. Несколько конкурентных операций могут одновременно узнать об отсутствии конкретной записи, в следствии чего, сохранить одни и те же данные.
Конечно, редкие случаи с гонкой данных возможно разрешить добавив ограничение на уникальность на уровне базы данных. Но в данном случае, вы не получите ошибку валидации, запрос к БД просто упадет и вся транзакция откатится.
В этой ситуации поможет gem database_validations, который предоставляет совместимость между ограничениями в базе данных и валидациями.
Основной смысл работы gem представлен в следующем коде:
Таким образом, мы пробуем сохранить данные, если все другие валидации пройдены, если транзакция падает и откатывается, мы парсим ошибку и присваиваем правильные значения в
Ознакомившись с документацией и бенчмарками, можно прийти к выводу, что данный gem ускорит процесс сохранения записей в базу данных минимум в два раза.
Благодаря поддержке таких баз данных, как PostgreSQL, SQLite, MySQL и обратной совместимости с
Удобный matcher для RSpec также присутствует из коробки:
При переходе на новую валидацию, вам необходимо иметь ограничения на уникальность в базе данных, но если их еще нет, gem об этом укажет во время запуска приложения.
Гем протестирован на приложении с 100+ валидациями на уникальность среди 50+ моделей.
Используйте гем и делитесь мнением. Любой вклад в дальнейшее развитие приветствуется!
Допустим, у вас есть модель пользователей с уникальностью на поле email, т.е.
class User < ApplicationRecord
validates :email, uniqueness: true
end
Вы, возможно, уже знаете, что данная валидация выполняет следующий запрос
SELECT 1 FROM users WHERE email = $1
каждый раз, когда мы пытаемся сохранить запись в базу данных.
У данного подхода, есть несколько недостатков:
Во-первых, исполнение дополнительного запроса, и в случае, если в модели инициализировано несколько валидаций на уникальность, запрос будет выполнен на каждую из них. Это не эффективно, а также требует наличие индексов, если мы хотим, чтобы данные запросы исполнялись быстро.
Во-вторых, данное решение не гарантирует уникальность из-за возможной гонки за данными. Несколько конкурентных операций могут одновременно узнать об отсутствии конкретной записи, в следствии чего, сохранить одни и те же данные.
Конечно, редкие случаи с гонкой данных возможно разрешить добавив ограничение на уникальность на уровне базы данных. Но в данном случае, вы не получите ошибку валидации, запрос к БД просто упадет и вся транзакция откатится.
В этой ситуации поможет gem database_validations, который предоставляет совместимость между ограничениями в базе данных и валидациями.
Основной смысл работы gem представлен в следующем коде:
def save(options = {})
ActiveRecord::Base.connection.transaction(requires_new: true) { super }
rescue ActiveRecord::RecordNotUnique => e
Helpers.handle_unique_error!(self, e)
false
end
Таким образом, мы пробуем сохранить данные, если все другие валидации пройдены, если транзакция падает и откатывается, мы парсим ошибку и присваиваем правильные значения в
errors
нашего объекта. Ознакомившись с документацией и бенчмарками, можно прийти к выводу, что данный gem ускорит процесс сохранения записей в базу данных минимум в два раза.
Благодаря поддержке таких баз данных, как PostgreSQL, SQLite, MySQL и обратной совместимости с
validates_uniqueness_of
, процесс замены на validates_db_uniqueness_of
занимает считанные минуты. Удобный matcher для RSpec также присутствует из коробки:
specify do
expect(described_class)
.to validate_db_uniqueness_of(:field)
.with_message('duplicate')
.with_where('(some_field IS NULL)')
.scoped_to(:another_field)
.with_index(:unique_index)
end
При переходе на новую валидацию, вам необходимо иметь ограничения на уникальность в базе данных, но если их еще нет, gem об этом укажет во время запуска приложения.
Гем протестирован на приложении с 100+ валидациями на уникальность среди 50+ моделей.
Используйте гем и делитесь мнением. Любой вклад в дальнейшее развитие приветствуется!