default_scope: toda query, uma linha, sem exceções

Prévia grátis 3 min de leitura

Uma linha que reescreve toda consulta de um model

Todo curso de querying do ActiveRecord até aqui filtrou de forma explícita, com .where(status: 0) ou Catalog::Course.active: uma chamada, aplicada quando a consulta escolhia aplicá-la. default_scope é outro tipo de ferramenta. É uma condição declarada uma vez, no model, e embutida automaticamente em toda consulta desse model, seja .all, .count ou .first, sem nada no ponto da chamada pedindo por ela.

A condição que ninguém digitou

Uma tabela de rascunho com uma coluna active e um default_scope declarado uma vez:

class ScratchDsCourse < ActiveRecord::Base
  default_scope { where(active: true) }
end

ScratchDsCourse.create!(title: "Visible", active: true)
ScratchDsCourse.create!(title: "Hidden", active: false)

ScratchDsCourse.count               # => 1, não 2
ScratchDsCourse.all.pluck(:title)   # => ["Visible"]

As duas linhas existem; as duas inserções deram certo. Mesmo assim .count e .all, chamados sem nenhum .where à vista, deixam a inativa de fora. Toda consulta seguinte em ScratchDsCourse, de qualquer arquivo, leva WHERE active = TRUE, saiba ou não o código que pergunta que a condição existe.

A escolha deste app: nenhum default_scope

Nenhum model deste app usa default_scope. Catalog::Course, o paralelo mais próximo de ScratchDsCourse, tem uma coluna active e esconde os cursos inativos do roadmap público, o formato que default_scope resolveria sozinho. A implementação dele:

# app/models/catalog/course.rb
scope :active, -> { where(active: true) }

É um scope explícito, com nome, e não um padrão. Catalog::Course.all devolve todo curso, ativo ou não. Filtrar os cursos ativos é uma chamada deliberada a .active, como na linha Catalog::Course.active.ordered... de Catalog::ListCoursesQuery, e não algo embutido em toda consulta, queira quem chama ou não.

Por que essa escolha

Admin::Catalog::ListCoursesQuery precisa mostrar a um admin todo curso, inclusive os inativos; é o propósito de uma listagem de admin. Ela nunca chama .active, porque Catalog::Course já devolve tudo. Se Catalog::Course usasse default_scope { where(active: true) }, essa consulta de admin teria de sair do padrão a cada chamada, com .unscoped ou .unscope(where: :active) (as duas próximas aulas), só para ver os cursos inativos que ela existe para gerenciar. O scope :active explícito deixa o caso público (só cursos ativos) a uma palavra de distância, .active. O caso do admin (tudo) não precisa de nada a mais, porque o padrão é "sem filtro", que é o que .all sempre significou.

Pontos-chave

  • Com default_scope { where(...) }, a condição vale para toda consulta do model automaticamente, .all, .count, .first, sem nada no ponto da chamada pedindo por ela.
  • Nenhum model deste app usa default_scope. Catalog::Course filtra os cursos ativos com um scope :active, -> { where(active: true) } explícito.
  • Catalog::Course.all devolve todo curso, ativo ou não. O filtro só acontece quando .active é chamado, como o ListCoursesQuery faz.
  • O motivo: Admin::Catalog::ListCoursesQuery precisa de todo curso, sem filtro. Um scope explícito torna isso o caso gratuito e "só ativos" uma escolha de uma palavra, o inverso do que um default_scope em active forçaria.
Aula 1 de 7 · grátis

Continue com a aula 2

A armadilha do find(id)

O default_scope também filtra find(id), que levanta RecordNotFound para uma linha real fora da condição do scope.

Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.

Digite para buscar...