Именованные области действительно упростили эту проблему, но она далека от решения.Обычная ситуация - переопределение логики как в именованных областях, так и в модельных методах.
Я попытаюсь продемонстрировать крайний случай этого, используя несколько сложный пример.Допустим, у нас есть Message
модель, в которой много Recipients
.Каждый получатель может пометить сообщение как прочитанное для себя.
Если вы хотите получить список непрочитанных сообщений для данного пользователя, вы должны сказать что-то вроде этого:
Message.unread_for(user)
Это будет использовать именованную область действия unread_for
, которая будет генерировать sql, который будет возвращать непрочитанные сообщения для данного пользователя.Этот sql, вероятно, собирается объединить две таблицы вместе и фильтровать сообщения по тем получателям, которые их еще не прочитали.
С другой стороны, когда мы используем модель Message
в нашем коде, мы используем следующее:
message.unread_by?(user)
Этот метод определен в классе сообщений, и даже этоделая в основном то же самое, теперь он имеет другую реализацию.
Для более простых проектов это действительно не так уж и важно.Реализация одной и той же простой логики в sql и ruby в этом случае не является проблемой.
Но когда приложение начинает становиться действительно сложным, оно становится проблемой.Если у нас реализована система разрешений, которая проверяет, кто может получить доступ к какому сообщению на основе десятков критериев, определенных в десятках таблиц, это становится очень сложным.Вскоре доходит до того, что вам нужно объединить 5 таблиц и написать действительно сложный SQL вручную, чтобы определить область действия.
Единственное «чистое» решение проблемы - заставить области использовать фактический код ruby.Они будут получать ВСЕ сообщения, а затем фильтровать их с помощью ruby.Однако это вызывает две основные проблемы:
- Производительность
- Разбиение на страницы
Производительность: мы создаем намного больше запросов к базе данных.Я не уверен насчет внутренних компонентов DMBS, но насколько сложнее для базы данных выполнить 5 запросов каждый на одну таблицу или 1 запрос, который собирается объединить 5 таблиц одновременно?
Разбиение на страницы: мы хотим сохранитьвыборка записей до тех пор, пока не будет получено указанное количество записей.Мы выбираем их один за другим и проверяем, принимается ли это рубиновой логикой.Как только 10 из них будут приняты, процесс остановится.
Любопытно услышать ваши мысли по этому поводу.У меня нет опыта работы с nosql dbms, могут ли они решить проблему по-другому?
ОБНОВЛЕНИЕ:
Я говорил только гипотетически, но вот один пример из реальной жизни.Допустим, мы хотим отобразить все транзакции на одной странице (как платежи, так и расходы).
Я создал SQL UNION QUERY, чтобы получить их обе, затем просмотрите каждую запись, проверьте, может ли это быть: readтекущим пользователем и, наконец, разбил его на страницы как массив.
def form_transaction_log
sql1 = @project.payments
.select("'Payment' AS record_type, id, created_at")
.where('expense_id IS NULL')
.to_sql
sql2 = @project.expenses
.select("'Expense' AS record_type, id, created_at")
.to_sql
result = ActiveRecord::Base.connection.execute %{
(#{sql1} UNION #{sql2})
ORDER BY created_at DESC
}
result = result.map do |record|
klass = Object.const_get record["record_type"]
klass.find record["id"]
end.select do |record|
can? :read, record
end
@transactions = Kaminari.paginate_array(result).page(params[:page]).per(7)
end
Оба payments
и expenses
должны отображаться в одной таблице, упорядоченные по дате создания и разбитые на страницы.
Обаpayments
и expenses
имеют совершенно разные разрешения :read
(определенные в классе способностей, CanCan
gem).Эти разрешения довольно сложны и требуют запроса нескольких других таблиц.
«Идеально» было бы написать один ОГРОМНЫЙ SQL-запрос, который вернул бы то, что мне нужно.Это сделало бы нумерацию страниц и все остальное намного проще.Но это дублирует мою логику, определенную в классе skill.rb.
Мне известно, что CanCan
предоставляет способ определения sql-запроса для способности, но способности настолько сложны, что их невозможно определить таким образом.
То, что я сделал, работает, но я загружаю ВСЕ транзакции, а затем проверяю, какие из них я мог прочитать.Я считаю это большой проблемой производительности.Пагинация здесь кажется бессмысленной, потому что я уже загружаю все записи (это только экономит пропускную способность).Альтернатива - написать действительно сложный SQL, который будет сложно поддерживать.