Пытаясь избежать n + 1 запроса
Я работаю над веб-приложением для учета двойной записи, которое имеет следующие основные модели:
ruby
class Account < ApplicationRecord
has_many :splits
has_many :entries, through: :splits
end
class Entry < ApplicationRecord
has_many :splits, -> {order(:account_id)}, dependent: :destroy, inverse_of: :entry
attribute :amount, :integer
attribute :reconciled
end
class Split < ApplicationRecord
belongs_to :entry, inverse_of: :splits
belongs_to :account
attribute :debit, :integer
attribute :credit, :integer
attribute :transfer, :string
end
Это довольно классический учетМодель, по крайней мере, она создана по образцу после GnuCash, но это приводит к несколько сложным запросам.(Из древней истории это в значительной степени третья нормальная структура формы!)
Первая Account
- это иерархическая древовидная структура (учетная запись принадлежит родителю (кроме ROOT), и у меня может быть много детей, дети также могуту меня много детей, которых я называю семьей).Большинство из этих отношений описаны в модели Account и оптимизированы, насколько это возможно, для рекурсивной структуры.
У учетной записи много записей (транзакций), и записи должны иметь как минимум два разбиения, равных сумме атрибута Amount.(или Дебит / Кредиты) должны равняться 0.
Основное использование этой структуры - создание книг, представляющих собой просто список Entries
и связанных с ним Splits
, обычно фильтруемых по диапазону дат.Это довольно просто, если в учетной записи нет семьи / детей
ruby
# self = a single Account
entries = self.entries.where(post_date:@bom..@eom).includes(:splits).order(:post_date,:numb)
Ситуация усложняется, если вы хотите, чтобы в бухгалтерской книге было много детей (мне нужна книга всех Current Assets
)
ruby
def self.scoped_acct_range(family,range)
# family is a single account_id or array of account_ids
Entry.where(post_date:range).joins(:splits).
where(splits: {account_id:family}).
order(:post_date,:numb).distinct
end
Хотя это работает, я предполагаю, что у меня есть запрос n + 1, потому что, если я использую includes instead of joins
, я не получу все расщепления для записи, только те из семейства - я хочу все расщепления.Это означает, что он перезагружает (запрашивает) разбиения в представлении.Также необходимо различение, потому что разделение может ссылаться на учетную запись несколько раз.
У меня вопрос, есть ли лучший способ обработать этот три модельных запроса?
Я собрал несколько хаков, один возвращался назадиз сплитов:
ruby
def self.scoped_split_acct_range(family,range)
# family is a single account_id or array of account_ids
# get filtered Entry ids
entry_ids = Split.where(account_id:family).
joins(:entry).
where(entries:{post_date:range}).
pluck(:entry_id).uniq
# use ids to get entries and eager loaded splits
Entry.where(id:eids).includes(:splits).order(:post_date,:numb)
end
Это также работает, и, согласно сообщению в журнале, может быть даже быстрее.При обычном использовании любой из них будет примерно 50 записей в месяц, но затем вы сможете отфильтровать транзакции по годам - но вы получите то, что просили.При обычном использовании регистр за месяц составляет около 70 мсек. Даже квартал составляет около 100 мсек.
Я использовал несколько атрибутов в разделениях и учетных записях, которые избавили от нескольких запросов уровня представления.Передача в основном связана с именами учетных записей, идущими вверх по дереву.
Опять же, просто смотрю, не пропустил ли я что-то, и есть ли лучший способ.