How to design firestore relations on the server and the client - javascript

I'm designing a firestore database, trying to understand no-sql. What's getting me stuck is relationships. I know I'm supposed to keep root level collections and relate them with references, but I don't understand how to manage the relations once the data is on the client. Say this is my data (without relations) ...
Schools (collection)
JFKElementary (doc)
address: 123 Elm Street
Classrooms (collection)
Room1 (doc)
roomNumber: 1
Room2 (doc) ...
Families (collection)
Jones.1234 (doc)
address: 456 Cherry Lane
Smith.2134 (doc) ...
Students
Billy.Jones.2323 (doc)
name: Billy Jones
I need to represent the idea that "Billy Jones" belongs to the Jones.1234 family, and that he belongs to the Room1 classroom, and that the classroom and the family belong to the JFKElementary school. I know I can place refs in the documents, like this...
Classrooms (collection)
Room1 (doc)
roomNumber: 1
school: ref to JFK Elementary
Students
Billy.Jones.2323 (doc)
name: Billy Jones
family: ref to Jones.1234
classroom: ref to Room1
school: ref to JFK Elementary
I want to get a whole school on the client, so I think I can query like this:
collection('classrooms').where("school", "==", <school ref>)
collection('students').where("school", "==", <school ref>)
// etc for families
That gets me the whole school, but leaves my problem: On the client, how do I assemble the relationships that UI needs? For example, say the UI wants to present the names of all the students in Room1...
let room1 = classroomsSnapshot.docs[0]
let room1Students = studentsSnapshot.docs.filter(student => {
return student.data().classroom.id === room1.id
})
I wouldn't want all that code run each time I need a room's students. I could build my own class on the client like this?
class ClientSideClassroom {
constructor(doc, studentsSnapshot) {
this.doc = doc
this.students = studentsSnapshot.docs.filter(student => {
return student.data().classroom.id === room1.id
})
}
and on a snapshot...
let clientClassrooms = classroomsSnapshot.docs().map(doc => {
return new ClientSideClassroom(doc, studentsSnapshot)
})
If so, then the student docs (and all of the others) are going to need their own class, too, e.g. to answer the ClientSideClassroom to which they belong. Do I now have to keep two objects for every firestore doc? The "real" one and a wrapper that keeps the relations?
Even if I have forward pointers on the collections, I don't think that puts me ahead. e.g.
Classrooms (collections)
Room1 (doc)
students: [ref to Billy.Jones.123, ...
I wouldn't query that classrooms' students with gets, because I got the whole collection in a students query. But I still need to turn the students array into an array of actual docs, not refs....
// ClientSideClassroom class constructor
this.students = this.doc.data().students.map(studentRef => {
return // find the student in the studentsSnapshot with studentRef
})
Am I going about this all wrong?

Put all student name inside classA document. - you can get all student name on specific class. classCollection will have a lot doc such as classA, classB, classC
Put all family member name inside familyJones doc - you can get all family member name on one family doc
On user jones document, put all his detail such as class, family, school, home address inside one doc
This video will help u
https://youtu.be/v_hR4K4auoQ

Related

Objection.js - Realtion with pivot table

i'm having hard times understanding how I should model many-to-many relationships in objections.js.
Basically I have an app with an Users, Games and UsersScore tables. A account can have score in game.
Tables are like this:
USERS TABLE
id
username
GAMES TABLE
id
name
UsersScore TABLE
id
userId
gameId
score
How do I create the relation?
I want to show the logged in user a list of all the games
And if he has score in the game then his score
Thanks!!
This is essentially identical to the requirements outlined at Join table extra properties in the Objection documentation. You could adapt this to create users, games, and users_games tables. Then your User model might look like this:
class User extends Model {
static get relationMappings() {
return {
relation: Model.ManyToManyRelation,
modelClass: Game,
join: {
from: 'users.id',
through: {
from: 'users_games.user_id',
to: 'users_games.game_id',
extra: ['score']
},
to: 'game.id'
}
};
}
}
Here you can see that we've added an extra score column to our users_games join table. Then I imagine you would get a list of games with something like:
const user = await User.query().findOne({ username: 'wombatsarefluffy' });
const games = await user.$relatedQuery('games');
See the documentation page for an example of how to construct the migrations.

Firebase Query for Products in Categories (database setup and best practice)

TL;DR
products have multiple categories.
View should show only all subcategories and products that have the category assigned.
How to setup the DB and queries?
I'm learning Vue and Firebase at the moment coming from a C# and SQL background, I need some help and advice on the noSQL side of things.
EDIT: categories and products are two separate collections (at the moment).
I have products, which can have multiple categories (see product 5)
products: {
prod1-id: {
name:'apple-type-A',
price: 2
cats: cat1-id
}
prod2-id: {
name:'apple-type-B',
price: 2.5
cats: cat1-id
}
prod3-id: {
name:'banana-type-A',
price: 1.6
cats: cat2-id
}
prod4-id: {
name:'banana-type-B',
price: 1.9
cats: cat2-id
}
prod5-id: {
name:'smoothie',
price: 5,
cats: [cat2-id, subCat1-id, subCat2-id]
}
}
Those categories are a tree.
categories: {
cat1-id: {
name: 'fruits',
subCat1-id: {
name: 'apple'
}
subCat2-id: {
name: 'banana'
}
},
cat2-id: {
name: 'MySmooth'
}
}
The customer should see the first tree.
The landing page should only show the first depth of the category tree and every product without a category (maybe add a category called 'no category').
When you click on a category is should show all the subcategories and products, that have this category.
This goes on until the deepest branch.
I tried to sketch my idea:
VIEW
For programming I use Vue, with vuex and vuexfire and as framework Vuetify.
I have the complete product management setup but I don't know how to query for this view.
My idea was to reuse a <v-card v-for="p of products"> I already have and is working fine.
But this shows only products, not categories. How do I get the categories into the mix?
QUERY
Working with vuexfire this is quite simple.
bindSoldProducts: firestoreAction(({ bindFirestoreRef }) => bindFirestoreRef('products', productsColl.where('isSold', '==', true))
but how can I get the categories and products now show side by side?
I've started to get the products together by querying, but have no idea how to put the categories into the mix:
firebase.firestore.collection('products').orderBy('name', 'asc').onSnapshot(snap => {
const productsArray: any = []
snap.forEach(doc => {
const product = doc.data()
product.id = doc.id
productsArray.push(product)
})
store.commit('setAllProducts', productsArray)
})
Do I need to structure my database differently?
Do I just query the products and use "some js logic/magic" to show the categories? But how would I then get the views.
Are the collection group queries of firebase the right way to go? If yes, how?
Please advice
Try a for in all Categories and get the fruits:
const categories = {
"cat1-id": []
}
Object.keys(categories).forEach(cat => {
const res = db.collection('products').where('cats', 'array-contains', cat).get()
// Resolve and put (push) in categories[cat]
})
See: https://firebase.google.com/docs/firestore/query-data/queries
I'm writing my response an answer as it was too long for a comment.
If I understood your use case correctly, you want to have a DB structure where you have a list of products and their respective categories which both will be displayed simultaneously side by side on your Vue application view.
While I'm not a Vue expert, I can suggest using the following structure for your Cloud Firestore database: Products/{product}/categories/{category}, which translates to collection/{document}/collection/{document}.
In the Cloud Firestore data model you can have a collection within another collection, and this is called a subcollection. For example, you could have Products/fruits/Categories/banana and inside the banana document you could have type, price, or id fields among many others.
Then, you could use Collection group queries to retrieve documents from a collection group instead of from a single collection. A collection group consists of all collections with the same ID. By default, queries retrieve results from a single collection in your database.
For example, you could create a categories collection group by adding a Categories subcollection to each product and then retrieve results from every product's categories subcollection at once, let's say bananas:
const querySnapshot = await db.collectionGroup('categories').where('productName', '==', 'banana').get();
querySnapshot.forEach((doc) => {
console.log(doc.id, ' => ', doc.data());
// Banana from Mexico, Banana from Honduras, etc.
});
Before using a collection group query, do not forget to create an index that supports your collection group query. You can create an index through an error message, the console, or the Firebase CLI.

Updating Query in Firestore

I am new to Firestore, need some help on firestore update.
I have following structure and wants to update "employee name" property. Not sure how to select and update.
Department:[
Name: Accounts
Employee:[
{Name :David,
Age :25},
{Name:Paul,
Age:27}
]
]
Here is what I was trying to do:
let depempCollectionRef = admin.firestore().collection('DepEmployee').doc('depempid')
depempCollectionRef.Department.Employee
.update({ name: 'Scott' },{merge:true})
.then(function() { console.log("Document successfully updated!"); })
Employee is just an embedded data structure in your Firestore document – so you can't address it through the reference directly. As far as Firestore is concerned, Employee is just an attribute on the Department document as Name is.
Before I propose a solution, let me point out two things:
If using update, you don't need {merge: true}. You use {merge: true} together with set to get an update-like behavior, if the document already exists.
I wouldn't use an Array of employees. It might make more sense to store the employees in their own collection in Firestore and then just list their reference IDs (= foreign keys) here. As a general rule of thumb: try to keep your data structure flat. Also use Arrays only, if you need to maintain a certain order of items.
A) If you have a separate collection for employees, updating the name is as easy as:
employeeCollection.doc('101').update({name: 'Scott'})
B) If you want to store employee data within your department document, I would still store them as a map with IDs (instead of an Array) and then access them with dot notation:
Department:[
Name: Accounts
Employees:{
101: {
Name :David,
Age :25
},
102: {
Name:Paul,
Age:27
}
}
]
depempCollectionRef.Department
.set({ ['101.name']: 'Scott' }, {merge:true})
C) And if you really want to store the data embedded in an Array, I believe you have to read and update the whole Array (not sure, if there is a better solution):
const employeesSnap = await depempCollectionRef.Department.get()
const updatedEmployees = changeNameOfScottInArray()
depempCollectionRef.Department
.update({ 'Employees': updatedEmployees })
I didn't test this code, but I hope you get the gist of it!
I'd recommend you flatten your data structure by creating a separate Employee collection and then just referencing them by their foreign key in your department (solution A).

Filtering based on belongsTo relation in loopback

I would like to filter a model, based on its parent belongsTo relation.
For example, I have a Customer model and a Books model.
A Customer hasMany Books and a Book belongsTo Customer, with each table potentially being quite huge.
I'd like to get a list of unique books, where the owning Customer has a name of John.
So far, I know I can go from the Book model with a query that looks something like:
Book.find({
include: {
relation: 'customer',
scope: {
where: {name: 'John'}
}
}
}, function(err, books) {
// Loop through the books here
});
Or I could approach it from the Customer model, and manually merge the book lists (removing duplicates):
Customer.find({
where: {name: 'John'}
}, function(err, customers) {
var books = customers.map(customer => customer.books);
// Remove duplicates here
});
I would prefer the first approach, since it seems like it would do the duplication logic on the querying side.
However, that approach seems to include all books, but only for the ones where the customer name is John does it add on the customer attribute.
Am I approaching this the wrong way?

Modelling blogs and ratings in mongodb and nodejs

I have a blogs collection that contains title, body and agrregate rating that the users have given to them. Another collection 'Ratings' whose schema has reference to the blog, user who rated(if at all he rates them) it in the form of their ObjectIds and the rating they have given ie., +1 or -1.
When a particular user browses through blogs in the 'latest first' order (say 40 of them per page. Call them an array of blogs[0] to blogs[39]) I have to retrieve the rating documents related to this particular user and those 40 blogs if at all the user rated them and notify him of what ratings he has given those blogs.
I tried to extract all rating documents of a particular user in which blog reference objectIds lie between blogs[0]._id and blogs[39]._id which returns empty list in my case. May be objectIds cant be compared using $lt and $gt queries. In that case how should I go about it? Should I redesign my schemas to fit to this scenario?
I am using mongoosejs driver for this case. Here are the relevant parts of the code which differ a bit in execution but youu get the idea.
Schemas:
Client= new mongoose.Schema({
ip:String
})
Rates = new mongoose.Schema({
client:ObjectId,
newsid:ObjectId,
rate:Number
})
News = new mongoose.Schema({
title: String,
body: String,
likes:{type:Number,default:0},
dislikes:{type:Number,default:0},
created:Date,
// tag:String,
client:ObjectId,
tag:String,
ff:{type:Number,default:20}
});
models:
var newsm=mongoose.model('News', News);
var clientm=mongoose.model('Client', Client);
var ratesm=mongoose.model('Rates', Rates);
Logic:
newsm.find({tag:tag[req.params.tag_id]},[],{ sort:{created:-1},limit: buffer+1 },function(err,news){
ratesm.find({client:client._id,newsid:{$lte:news[0]._id,$gte:news.slice(-1)[0]._id}},function(err,ratings){
})
})
Edit:
While implementing the below said schema, I had to do this query in mongoose.js
> db.blogposts.findOne()
{ title : "My First Post", author: "Jane",
comments : [{ by: "Abe", text: "First" },
{ by : "Ada", text : "Good post" } ]
}
> db.blogposts.find( { "comments.by" : "Ada" } )
How do I do this query in mongoose?
A good practice with MongoDB (and other non-relational data stores) is to model your data so it is easy to use/query in your application. In your case, you might consider denormalizing the structure a bit and store the rating right in the blog collection, so a blog might look something like this:
{
title: "My New Post",
body: "Here's my new post. It is great. ...",
likes: 20,
dislikes: 5,
...
rates: [
{ client_id: (id of client), rate: 5 },
{ client_id: (id of another client), rate: 3 },
{ client_id: (id of a third client), rate: 10 }
]
}
The idea being that the objects in the rates array contains all the data you'll need to display the blog entry, complete with ratings, right in the single document. If you also need to query the rates in another way (e.g. find all the ratings made by user X), and the site is read-heavy, you may consider also storing the data in a Rates collection as you're doing now. Sure, the data is in two places, and it's harder to update, but it may be an overall win after you analyze your app and how it accesses your data.
Note that you can apply indexes deep into a document's structure, so for example you can index News.rates.client_id, and then you can quickly find any documents in the News collection that a particular user has rated.

Categories

Resources