SIGN IN SIGN UP

Elimina o N+1 de categoria da home e do filtro do catálogo

Fase 17, com medição antes e depois. Os N+1 não cresciam com o tamanho
do catálogo, cresciam com o número de categorias — por isso o seed de 10
produtos os escondia. Medido com 23 categorias de topo:

    home      antes  118 queries, 242 ms   depois  9 queries,  29 ms
    catálogo  antes   40 queries,  60 ms   depois 17 queries,  41 ms

Três correções:

* A capa de cada categoria na home era uma busca isolada, e cada uma
  arrastava anexo, blob e variant records atrás de si — 5 queries por
  categoria de topo. Agora são duas leituras fixas: um DISTINCT ON
  (category_id) escolhe as capas, e uma segunda carrega as escolhidas
  com o anexo em lote. O custo acompanha o número de categorias, não o
  tamanho do catálogo.

* O filtro lateral do catálogo mostra o breadcrumb de cada categoria, e
  breadcrumb_name/self_and_descendant_ids sobem e descem a árvore uma
  query por nível. Daí Category::Tree, que carrega a árvore inteira em
  uma leitura e responde em memória. A home passa a usar a mesma árvore:
  a travessia manual que ela tinha era essa lógica duplicada.

* Catálogo e PDP perguntavam `any?` em relação não carregada antes de
  renderizar, um SELECT ... LIMIT 1 jogado fora por página. Três queries
  constantes a menos.

Category#breadcrumb_name continua existindo e em uso: a PDP e o admin
renderizam uma categoria só, e para isso includes(category: :parent) já
resolve. Category::Tree é para páginas que renderizam a árvore inteira, e
carrega sempre a árvore completa de propósito — um breadcrumb calculado
sobre um recorte devolveria um caminho truncado, sem erro nenhum.

Achado de método que mudou uma conclusão: contar toda notificação de
sql.active_record superestima o problema, porque o query cache do Active
Record serve repetições idênticas dentro da mesma requisição. Com a
contagem corrigida (separando payload[:cached]), uma otimização que eu já
tinha escrito — combinar average_rating e reviews_count numa leitura só,
memoizada — valia 1 query, não 6, ao custo de SQL cru e de uma memoização
que envelhece no objeto. Revertida (§3, §51, §70).

Também deliberadamente de fora: índices para price_cents/name (o plano
atual é Seq Scan + Sort, o PostgreSQL ignoraria — §53) e o mesmo N+1 de
breadcrumb no admin, que não foi medido e é de baixo tráfego.

Os testes assertam crescimento, nunca um total absoluto: o total muda a
cada alteração legítima de página, a inclinação é que não pode voltar.
Ambos os guards falham no código anterior (home 13→33 queries, catálogo
13→15). Um quarto teste que escrevi NÃO falhava no código antigo — a
redundância era absorvida pelo query cache — e foi removido em vez de
deixar confiança falsa. O comportamento de qual produto vira capa de
categoria não tinha teste nenhum antes deste refactor; agora tem.

spec/rails_helper.rb passa a apontar file_fixture_path para
test/fixtures/files, que as duas suítes compartilham, em vez de duplicar
o PNG de exemplo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPSKmT4DrESfWGVvvEJ29Z
U
Uriel Juliatti committed
5ed56f20f89933f312624a7d1070745ffd39d575
Parent: a17c75c