.merge: reusando um scope do model juntado

Prévia grátis 3 min de leitura

O que "JOINs no Rails" já cobre

A aula "JOINs no Rails: associações ActiveRecord", de SQL: fundamentos de JOIN, é completa. Ela trata de joins (um INNER JOIN que filtra e não carrega), de left_joins (um LEFT OUTER JOIN, inclusive anti-joins) e das três estratégias de carregamento includes, eager_load e preload. Ela percorre o problema N+1 de ponta a ponta, inclusive a forma aninhada includes(lessons: :challenge), a diferença essencial entre joins e includes, uma matriz de decisão e a gem bullet. Este curso não repete nada disso. Ele começa por .merge, que aquela aula nunca menciona, e entra no terreno que Associações ActiveRecord: through e Associações ActiveRecord: polimórficas prepararam mas nunca consultaram.

Filtrando por uma tabela juntada, de dois jeitos

Catalog::Course tem um scope active: scope :active, -> { where(active: true) }. Encontrar aulas que pertencem a um curso ativo pode ser escrito como um .where na tabela juntada:

Catalog::Lesson.joins(:course).where(courses: { active: true })

Ou pode reusar o scope que já existe em Catalog::Course, em vez de digitar a condição de novo:

Catalog::Lesson.joins(:course).merge(::Catalog::Course.active)

O .to_sql dos dois é idêntico: SELECT "lessons".* FROM "lessons" INNER JOIN "courses" ON "courses"."id" = "lessons"."course_id" WHERE "courses"."active" = TRUE. .merge pega as condições WHERE (e outras partes da consulta) da relation que você passa, Catalog::Course.active, e as junta na relation que chama, como se você tivesse escrito o .where à mão.

Esta linha já roda neste app

# app/queries/catalog/search_query.rb
lessons = ::Catalog::Lesson.joins(:course).merge(::Catalog::Course.active)
  .includes(:course)
  .where(title_match(:lessons, locale), pattern)
  .order(:course_id, :position).limit(limit).to_a

Esta é a busca do catálogo, SearchQuery. Ela encontra aulas cujo título bate com um termo de busca, mas só em cursos active. .joins(:course) traz a tabela courses para filtrar, o que "JOINs no Rails" já cobre. .merge(::Catalog::Course.active) reusa o scope active de Course em vez de repetir where(active: true) na linha. Se o significado de "ativo" mudasse, digamos com uma segunda condição no scope, todo .merge(Course.active) do código pegaria a mudança. Um .where(courses: { active: true }) escrito à mão em vários query objects precisaria de cada cópia atualizada à mão.

Por que esta é uma mecânica diferente de joins/includes

joins, left_joins, includes, eager_load e preload respondem todos a "como esta associação é carregada, e ela filtra?" .merge responde a outra pergunta: "como reusar um scope já definido no model que juntei, em vez de reescrever a lógica na linha?" Ele funciona com qualquer uma das cinco estratégias de carregamento. .merge não liga se a associação entrou por joins ou por includes; ele pega condições de uma relation e as aplica a outra.

Pontos-chave

  • "JOINs no Rails: associações ActiveRecord" trata de joins, left_joins, includes, eager_load, preload e do problema N+1 por inteiro. Este curso começa depois disso, com .merge.
  • Lesson.joins(:course).merge(Course.active) gera o mesmo SQL que Lesson.joins(:course).where(courses: { active: true }): .merge junta as condições WHERE da outra relation na que chama.
  • O Catalog::SearchQuery deste app usa joins(:course).merge(Course.active), reusando o scope active de Course em vez de repetir where(active: true), e assim "ativo" fica definido num lugar só.
  • .merge responde a "reusar um scope do model juntado", uma pergunta diferente da que joins, includes, eager_load e preload respondem sobre carregamento, e funciona com qualquer um deles.
Aula 1 de 7 · grátis

Continue com a aula 2

A armadilha joins + .count: linhas duplicadas do JOIN

Por que joins(:lessons).count conta pares curso-aula, como .distinct.count corrige isso e por que includes(:lessons).count dispensa o JOIN.

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

Digite para buscar...