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!