Проверка Rails: уникальность нескольких полей: ненужные запросы в базе данных

У меня есть приложение Rails 4.2/Ruby 2.2, где у меня есть таблица пользователей и таблица серверов. Сервер "принадлежит" пользователю (внешний ключ user_id), а имя сервера должно быть уникальным для конкретного пользователя. В PostgreSQL это выглядит так:

CREATE TABLE users (
  id integer NOT NULL,
  name character varying(255)
);

CREATE TABLE servers (
  id integer NOT NULL, 
  name character varying(255),
  user_id integer NOT NULL
);

ALTER TABLE servers ADD CONSTRAINT servers_user_id_fk FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT; 

ALTER TABLE servers ADD PRIMARY KEY (user_id, name);

Этот SQL не позволит мне добавить сервер, ссылающийся на недопустимого пользователя, и не даст указанному пользователю использовать имя сервера более одного раза.

Модели Rails выглядят примерно так:

class User << ActiveRecord::Base
  has_many :servers
end

class Server << ActiveRecord::Base
  belongs_to :user, foreign_key: "user_id"

  validates :must_have_unique_name_and_user_id, on: :create

  def must_have_unique_name_and_user_id
    name_count = Server.where(user_id: user_id, name: name).count
    if name_count > 0
      errors[:base] << "Server name is already taken for name #{name}, and user #{user.name}"
  end
end

Моя проблема в том, что когда я запускаю набор тестов с покрытием кода, must_have_unique_name_and_user_id() вызывается сотни раз, потому что эти две модели являются основополагающими для моей общей модели данных. Каждый из этих вызовов проверки приводит к запросу к базе данных, чтобы проверить, является ли имя сервера уникальным для пользователя.

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

Можно ли структурировать проверки Rails так, чтобы сообщение об ошибке, которое я имею в проверке Rails, must_have_unique_name_and_user_id() отображалось, если пользователь пытается создать дублированную комбинацию (user_id, name) для сервера, но так, чтобы проверка Rails не выполнялась бегать каждый раз?

1 ответ

База данных - ваш источник правды, для которого пользователи и серверы встречаются вместе, поэтому Rails не может проверить это без запроса.

Однако вы можете удалить проверку в Rails и оставить ее для БД, поместив уникальный индекс в servers.user_id, servers.name, Это вызовет исключение, если вы попытаетесь обновить или создать недопустимую строку, которую вам затем придется перехватить и обработать.

Таким образом, ваши варианты - это компромисс между дополнительными запросами и сложностью кода. Мой совет: пусть Rails будет выполнять дополнительные запросы, пока вы не столкнетесь с проблемами производительности.

Если это проблема, характерная для вашего набора тестов (т. Е. Вы пытаетесь заставить его работать быстрее), вы можете попытаться избежать создания объектов в тестах. Прикосновение к БД часто является большим ударом по сравнению с выполнением всего в памяти одного процесса, поэтому, если вы можете создавать, связывать и использовать объекты без их сохранения, вы, вероятно, получите значительное ускорение.

Другие вопросы по тегам