Дублирующая логика в методах и областях (и sql) - PullRequest
2 голосов
/ 17 ноября 2011

Именованные области действительно упростили эту проблему, но она далека от решения.Обычная ситуация - переопределение логики как в именованных областях, так и в модельных методах.

Я попытаюсь продемонстрировать крайний случай этого, используя несколько сложный пример.Допустим, у нас есть Message модель, в которой много Recipients.Каждый получатель может пометить сообщение как прочитанное для себя.

Если вы хотите получить список непрочитанных сообщений для данного пользователя, вы должны сказать что-то вроде этого:

Message.unread_for(user)

Это будет использовать именованную область действия unread_for, которая будет генерировать sql, который будет возвращать непрочитанные сообщения для данного пользователя.Этот sql, вероятно, собирается объединить две таблицы вместе и фильтровать сообщения по тем получателям, которые их еще не прочитали.

С другой стороны, когда мы используем модель Message в нашем коде, мы используем следующее:

message.unread_by?(user)

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

Для более простых проектов это действительно не так уж и важно.Реализация одной и той же простой логики в sql и ruby ​​в этом случае не является проблемой.

Но когда приложение начинает становиться действительно сложным, оно становится проблемой.Если у нас реализована система разрешений, которая проверяет, кто может получить доступ к какому сообщению на основе десятков критериев, определенных в десятках таблиц, это становится очень сложным.Вскоре доходит до того, что вам нужно объединить 5 таблиц и написать действительно сложный SQL вручную, чтобы определить область действия.

Единственное «чистое» решение проблемы - заставить области использовать фактический код ruby.Они будут получать ВСЕ сообщения, а затем фильтровать их с помощью ruby.Однако это вызывает две основные проблемы:

  1. Производительность
  2. Разбиение на страницы

Производительность: мы создаем намного больше запросов к базе данных.Я не уверен насчет внутренних компонентов 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, который будет сложно поддерживать.

1 Ответ

0 голосов
/ 17 ноября 2011

Звучит так, как будто вы должны удалить некоторое дублирование и, возможно, больше использовать логику БД. Нет причин, по которым вы не можете делиться кодом между именованными областями между другими методами.

Можете ли вы опубликовать проблемный код для проверки?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...