Banco de Dados

5 mar, 2018

Trabalhando com “JOINS” no Firebase

Publicidade

O fato do Firebase não ter operações como join, innerjoin e groupBy; pode ser meio frustrante em algumas situações. Neste artigo, vou descrever o que eu tenho feito para suprir as minhas necessidades.

O Firebase possui dois event listeners chamados on e once, onde podemos aplicar alguns valores, como: child_added, value, child_removed e child_changed. Pra ficar mais claro, vamos ver um exemplo prático:

let ref = firebase.database().ref();

ref.child('livros').on('child_added', => snapshot { console.log(snapshot.val()); })
ref.child('livros').on('value', snapshot  => { console.log(snapshot.val()); })
ref.child('livros').on('child_removed', snapshot => { console.log(snapshot.val()); })
ref.child('livros').on('child_changed', snapshot => { console.log(snapshot.val()); })

A diferença entre on e once é que o segundo escuta uma vez só, enquanto o primeiro fica escutando sempre. Agora imagine que temos uma lista de livros, e para cada um queremos trazer o autor e todos que já compraram esse livro. Com SQL ou até mesmo com MongoDB, isso seria uma tarefa trivial, mas com Firebase pode ser tornar um pouco mais complicado.

No cenário mais simples, usamos o child_added e para cada livro consulta o autor e os clientes. Vamos ver como fica essa query!

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;
  console.log('nome do livro: ' + livro.nome);
  ref.child('author').orderByChild("authorId").equalTo(authorId).on('value', author => {
    console.log('nome do author:' + author.val().nome;
  })
})

Sabe o que aconteceria nesse caso? Primeiro, ia dar o console.log() de todos os nomes dos nossos livros e depois iria dar o console do nome dos autores. Ex:

nome do livro é: SENHOR DOS ANÉIS
nome do livro é: O ALQUIMISTA
nome do livro é: TUDO É POSSÍVEL

nome do author: J. R. R. Tolkien
nome do author: Paulo Coelho
nome do author: Allan Percy

Mas o que queremos fazer é:

nome do livro é: SENHOR DOS ANEIS
nome do author: J. R. R. Tolkien

nome do livro é: O ALQUIMISTA
nome do author: Paulo Coelho

nome do livro é: TUDO É POSSÍVEL
nome do author: Allan Percy

Você pode pensar: “Ah, fácil de resolver! No retorno da segunda consulta eu mostro o nome do livro junto com o nome do autor!”. Vamos ver se essa abordagem funciona:

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;

  ref.child('author').orderByChild("authorId").equalTo(authorId).on('value', author => {
    console.log('nome do livro: ' + livro.nome + '\n');
    console.log('nome do author:' + author.val().nome;
  })
})

Funciona! Mas e agora, se adicionarmos o terceiro listener – que é o caso dos clientes que compraram – o que podemos fazer? Ao mesmo tempo que você está consultando o autor, também está consultando os clientes, você não sabe qual vai ser o primeiro ou o último a responder. Por isso não pode jogar o console no listener do autor ou no listener do cliente, certo?

Nesse caso, usamos o conceito de Promise. Ao invés de usar o on, usamos o once que retorna uma Promise. Só uma observação: Quando a gente usa orderByChild, é como se fosse um filtro, então, ele retorna um array podendo conter 1 ou mais objetos.

A implementação fica da seguinte maneira:

let promises = [];

ref.child('livros').on('child_added', => snapshot {
  let livro = snapshot.val();
  let authorId = livro.authorId;

  let promise_author = ref.child('author').orderByChild("authorId").equalTo(authorId).once('value') //no caso do author, vai trazer 1 só
  let promise_pedidos = ref.child('pedidos').orderByChild("livroId").equalTo(livroId).once('value') //no caso de pedidos, vai trazer varios

  promises.push(promise_author, promise_pedidos) //inserindo as promises na fila

  // Quando terminar todas as minhas promises, ai sim eu monto a resposta final

  Promise.all(promisses).then(function(resp) {

	let nomeLivro = livro.nome;
	let nomeAuthor = resp[0].val().nome;
	let clientePedidos = resp[1].val().map(pedido => {
  	return pedido.nomeCliente
	});

	array.push(livro.nome + ' - ' + nomeAuthor + ' - ' + clientePedidos.join('\n'))

	console.log(array.join('\n'))

  })

})

Essa foi a saída para a implementação usando uma espécie de join. Claro que ao invés de colocar no array, eu fiz um objeto bonitinho, mapeado, e tal, mas isso fica a critério da sua implementação. Espero que sirva de base!