Pular para o conteúdo

Relações

Duas consultas para cem registros — e por que a relação não carrega sozinha.

O problema do N+1#

Buscar cem usuários e ler usuario.pedidos de cada um faz cento e uma consultas. Isso não aparece em desenvolvimento, com três linhas na tabela, e derruba a produção com dez mil.

É o bug de ORM mais comum que existe. No Forge, a relação não carrega sozinha ao ser lida — ou você pede, ou ela não vem:

dataforge
adopt Forge

db := Forge.conectar(":memory:")

Usuario := Forge.modelo("Usuario", {
    "id": {"tipo": "Serial"},
    "nome": {"tipo": "Texto", "obrigatorio": yes}
}, {"conexao": db})

Pedido := Forge.modelo("Pedido", {
    "id": {"tipo": "Serial"},
    "usuario_id": {"tipo": "Inteiro", "indice": yes},
    "total": {"tipo": "Real", "padrao": 0}
}, {"conexao": db})

Usuario.tem_muitos("pedidos", "Pedido")
Pedido.pertence_a("usuario", "Usuario")
Forge.migrar_tudo(db)

ana := Usuario.criar({"nome": "Ana"})
bia := Usuario.criar({"nome": "Bia"})
Pedido.criar({"usuario_id": ana["id"], "total": 100.0})
Pedido.criar({"usuario_id": ana["id"], "total": 50.0})
Pedido.criar({"usuario_id": bia["id"], "total": 20.0})

// DUAS consultas, para qualquer quantidade de usuários
com_pedidos := Usuario.com(Usuario.todos(), "pedidos")
assert len(com_pedidos[0]["pedidos"]) is 2
assert len(com_pedidos[1]["pedidos"]) is 1

Os quatro tipos#

dataforge
// um usuário tem muitos pedidos
Usuario.tem_muitos("pedidos", "Pedido")

// um pedido pertence a um usuário
Pedido.pertence_a("usuario", "Usuario")

// um usuário tem um perfil
Usuario.tem_um("perfil", "Perfil")

// um post tem muitas etiquetas, e uma etiqueta muitos posts
Post.muitos_para_muitos("etiquetas", "Etiqueta")
RelaçãoA chave fica emPadrão do nome
tem_muitosna tabela do outro<modelo>_id
tem_umna tabela do outro<modelo>_id
pertence_anesta tabela<nome>_id
muitos_para_muitosnuma tabela ponteas duas chaves

Do outro lado#

dataforge
adopt Forge

db := Forge.conectar(":memory:")
U := Forge.modelo("Usuario", {
    "id": {"tipo": "Serial"}, "nome": {"tipo": "Texto"}
}, {"conexao": db})
P := Forge.modelo("Pedido", {
    "id": {"tipo": "Serial"}, "usuario_id": {"tipo": "Inteiro"},
    "total": {"tipo": "Real", "padrao": 0}
}, {"conexao": db})
P.pertence_a("usuario", "Usuario")
Forge.migrar_tudo(db)

ana := U.criar({"nome": "Ana"})
P.criar({"usuario_id": ana["id"], "total": 10.0})

com_dono := P.com(P.todos(), "usuario")
assert com_dono[0]["usuario"]["nome"] is "Ana"

Relação desconhecida avisa#

dataforge
adopt Forge

db := Forge.conectar(":memory:")
U := Forge.modelo("Usuario", {"id": {"tipo": "Serial"}}, {"conexao": db})
U.tem_muitos("pedidos", "Pedido")
Forge.migrar_tudo(db)

monitor:
    U.com(U.todos(), "inventada")
handle SchemaError as e:
    assert "inventada" in e.message

A mensagem lista as relações que existem — é mais útil que "não encontrada".