In my database, I currently have two kinds of objects, users and jobs. I am already storing userIDs in jobs. Do I also need to store jobIDs in each user?
A typical user:
"-JqzUjcOfddBNd_HtjKb" : {
"contact" : {
"-JqzWcIyD77ZwatEKALp" : {
"email" : "someguy#yahoo.com"
},
"-JqzWrtyni3ZGOKooNF7" : {
"email" : "someguy#outlook.com"
}
},
"country" : "234",
"cv" : "https://linktourl.com",
"dateAdded" : 1433436879708,
"ethnicity" : "0",
"firstName" : "John",
"lastName" : "Smith",
"notes" : {
"-JqzhvtNcueUsPr8xwh8" : {
"date" : 1433440599702,
"user" : "iwrotethisnote#example.com",
"value" : "interested in job; need to interview"
}
},
"roles" : [ true ]
},
And a typical job:
"-Jqz5mOr-DmLcxmTVRPi" : {
"age" : [ "2" ],
"city" : "0",
"clientID" : "-Jqz7goZC76vl94VT0dq",
"dateModified" : 1433431226687,
"longDesc" : "Teacher should have experience",
"notes" : {
"-Jqz6SO74OJOESwOVfkG" : {
"date" : 1433430513294,
"notevalue" : "bill spoke with her",
"userid" : "name#gmail.com"
}
},
"schedule" : "Evening or weekend",
"status" : "needDetail",
"subjects" : {
"15" : true,
"42" : true
},
"title" : "She wants a native speaker"
},
As you can see, jobs have a field for clientID, which is a foreign key (so to speak) of a user's id. When I access a user's information, I want to know those jobs that they are associated with (i.e. for which they have supplied their client ID). How to do this in Firebase?
Should I:
Update both objects, and keep an array of jobIDs in the client object?
Query all jobs, then pass in those that have the user's ID as the client ID. Something like:
// user controller
var jobs = [];
jobsRef.$on('value', function(snapshot) {
snapshot.val().forEach(function(job) {
if (job.clientID = $scope.userID) {
jobs.push(job);
}
}
}
Option 1 makes the data redundant (which I guess is ok, because of the emphasis on denormalization), but it also makes it more likely to become out of sync, for example if one of the two updates I would be making fails.
Option 2 seems like it would run a lot slower.
You can use equalTo() of firebase query
var jobsRef = new Firebase();
jobsRef.orderByChild("clientID").equalTo($scope.userID)
.on("value", function(snapshot) {
console.log(snapshot.key());
});
For more info, please check out Query.equalTo()
Related
I am trying to filter firebase data using startAt and/or endAt.
My data is structured as below.
{
"notes" : {
"-LOs0Ikx4ydM5RatREM1" : {
"data" : {
"dueDate" : 1561629600000,
"description" : "Korewa nan desuka?!",
"createdAt" : 1539611900000,
"title" : "First "
},
"members" : {
"author" : "1212121212121212121212121212"
}
},
"-LOs0Ikx4ydM5RatREM2" : {
"data" : {
"dueDate" : 4004870448000,
"description": "Test"
"createdAt" : 1539611900000,
"title" : "Second"
},
"members" : {
"author" : "1212121212121212121212121212"
}
},
"-LhBt9msLFKqUQ-koI9W" : {
"data" : {
"dueDate" : 1564653600000,
"description" : "abc",
"createdAt" : 1560363158279,
"title" : "August 1"
},
"members" : {
"author" : "3434343434343434343434343434"
}
},
"-LhBtKdDrQv9eKuYdfCi" : {
"data" : {
"dueDate" : 1564653600000,
"description" : "abcdef",
"createdAt" : 1560363158279,
"title" : "August 2"
},
"members" : {
"author" : "3434343434343434343434343434"
}
}
}
}
What I wish is to fetch all "notes" where dueDate has passed.
const now = moment().valueOf() //Eg. 1561629500000
database.ref('notes/')
.orderByChild("dueDate")
.endAt(now)
.once("value", (snapshot) => {
console.log('Process expired notes')
snapshot.forEach( (data) => {
const obj = data.val()
console.log('Date comparison:', (now >= obj.data.alertDate))
...
The code above does not work, it returns all the objects from the example JSON. The console.log logs "False" for three out of four returned objects.
I could do a comparison and only process the objects that meets my criteria, but that would defeat the purpose.
I have indexed the database on ["notes\data\alertDate"].
What am I missing? I must have misinterpreted the documentation somehow. :)
Your dueDate property is nested under data, so you need to address is as data/dueDate:
database.ref('notes')
.orderByChild("data/dueDate")
You might want to include both a startAt() and endAt() clause, with just a reasonable value for startAt() and the specific value you're already using for endAt().
I'm making chat app and storing the messages in firebase and this is my structure:
{
"1405093" : {
"172341" : {
"bot" : {
"msg" : [ {
"msg" : "hi",
"timestamp" : "12:09:02"
}, {
"msg" : "bye",
"timestamp" : "12:11:03"
} ]
},
"consultant" : {
"msg" : "Hi"
},
"resolved" : false,
"user" : {
"module" : "",
"msg" : [ {
"msg" : "hi",
"timestamp" : "12:09:01"
}, {
"msg" : "bye",
"timestamp" : "12:11:01"
} ]
}
},
"172351" : {
"bot" : {
"msg" : [ {
"msg" : "hi",
"timestamp" : "12:09:02"
}, {
"msg" : "bye",
"timestamp" : "12:11:03"
} ]
},
"consultant" : {
"msg" : "Hi"
},
"resolved" : true,
"user" : {
"module" : "",
"msg" : [ {
"msg" : "hi",
"timestamp" : "12:09:01"
}, {
"msg" : "bye",
"timestamp" : "12:11:01"
} ]
}
}}
And here is my code
if(method === "addUser&Bot")
{
umsg=data.umsg;
bmsg=data.bmsg;
ref.child(user+"/"+ticket_id).set({
"user":{
"msg":umsg,
"module":""
},
"bot":{
"msg":bmsg
},
"consultant":{
"msg":"Hi"
},
"resolved":false
});
data={"name":user,"id":ticket_id}
res.json(data);
}
As You can see I'm using set function and that's because of the field like 172341 which is actually a ticket_no which will always be unique and I don't want the randomly generated values created by firebase. So the problem is, this works great for the first time when the ticket_no is new but when again I'm sending a message the previous messages are overwritten. So how do I write a code that handles the new ticket_no and messages and also the messages that are sent afterwards.
If you want to only update the values that you're passing to the database, and leave anything you're not passing unmodified, use update(...) instead of set(...):
ref.child(user+"/"+ticket_id).update({
"user":{
"msg":umsg,
"module":""
},
"bot":{
"msg":bmsg
},
"consultant":{
"msg":"Hi"
},
"resolved":false
});
This will write only the user, bot, consultant, and resolved properties, and leave any other properties unmodified. But it will overwrite the complete value of the user, bot, consultant, and resolved properties. If you want to do a deep update, you can specify the precise path of what you want to update:
ref.child(user+"/"+ticket_id).update({
"user/msg": umsg,
"user/module": "",
"bot/msg": bmsg,
"consultant/msg": "Hi",
"resolved":false
});
This last statement will write/update only the precise paths indicated, and leave everything else unmodified.
I'm having some trouble determining how to find a document within a collection, and a value within an object in a subarray of that document — and then update a value within an object in that array.
I need to do the following:
find by _id
find object in ratings array that matches the user + post keys
update the report value within that object
For example, the documents in my collection are set up like below.
{
"_id" : "mz32AcxhgBLoviRWs",
"ratings" : [
{
"user" : "mz32AcxhgBLoviRWs",
"post" : "SMbR6s6SaSfsFn5Bv",
"postTitle" : "fdsfasdf",
"date" : "2017-09-27",
"rating" : "4",
"review" : "sdfa",
"report" : "a report"
},
{
"user" : "mz32AcxhgBLoviRWs",
"post" : "iZbjMCFR3cDNMo57W",
"postTitle" : "today",
"date" : "2017-09-27",
"rating" : "4",
"review" : "sdfa",
"report" : "some report"
}
]
}
It seems that you want just one update, not three separated queries.
Collection.update({
_id: <id>,
ratings: {
$elemMatch: {
user: <user>,
post: <post>
}
}
}, {
$set: {
'ratings.$.report': <report>
}
});
Documentation: $elemMatch, <array>.$.
I have a simple application with registration/login and it is basically a coursera/udemy type, where the app lists specific courses and users can like them or enroll in them. I have been trying to make a mongodb function that updates a user in the database and since users can like the courses it has to update all courses too (courses have a field "usersLiked", which is an array and keep all user documents which have liked it).
The course structure is the following:
{
"_id" : ObjectId("5977662564aac9f6c8d48884"),
"title" : "Title",
"lecturer" : "Lecturer",
"length" : 30,
"coverPhoto" : "Photo",
"usersLiked": [
{
"_id" : ObjectId("597763e346a7a463cbb8f529"),
"fullname" : "Name",
"username" : "Username",
"password" : "Hash",
"city" : "City",
"street" : "Street",
"website" : "Website"
}
],
"lectures" : [
{
"title" : "Introduction",
"number" : 1,
"url" : "someURL"
}
]
}
And the user structure:
{
"_id" : ObjectId("597763e346a7a463cbb8f529"),
"fullname" : "Name",
"username" : "Username",
"password" : "Hash",
"enrolledCourses" : [
...
]
}
],
"city" : "City",
"street" : "Street",
"website" : "Website"
}
So now I am calling this function when I want to change. It changes the userCollection but in the courseCollection it does nothing, while it should get all courses and if some of them have an object with username(the user's username) in the "usersLiked" array it should modify the user there too.
const updateUser = (username, details) => {
usersCollection
.update({
username: username,
}, {
$set: {
fullname: details.fullname,
city: details.city,
street: details.street,
website: details.website,
},
});
coursesCollection
.updateMany(
{
usersLiked: {
$elemMatch: {
username: username,
},
},
},
{
$set: {
'usersLiked.username': details.username,
'usersLiked.city': details.city,
'usersLiked.street': details.street,
'usersLiked.website': details.website,
},
}
);
};
Your match on the course update looks valid but you are trying to set values into an array and according to the Mongo docs you need to provide an array indexer or a positional operator.
The following will allow the set command to operate on the first element within the usersLiked array which matches the given username.
coursesCollection.updateMany(
{
usersLiked: {
$elemMatch: {
username: username,
},
},
},
{
$set: {
'usersLiked.$.username': details.username,
'usersLiked.$.city': details.city,
'usersLiked.$.street': details.street,
'usersLiked.$.website': details.website
},
}
)
You could also choose which element in the usersLiked array to update e.g. usersLiked.1.username but I suspect each course only has one element in usersLiked for a given username in which case using the $ operator (which means: the first matching array element) should suffice.
I am trying to grab every project by the members within them -- I've created a sample datastructure though the actual structure is much larger (as in it would be difficult to restructure the database).
Here is my query:
var ref = new Firebase(FBURL + '/chat/meta/project');
var email = 'kerry#email.com';
ref
.orderByChild("email")
.equalTo(email)
.on("child_added", function(snapshot) {
console.log(snapshot.val());
}
);
It is important to note that if I remove the .equalTo(email) that it returns all of the "projects", when it should only return 2 of them.
Here is the data in Firebase:
{
"chat" : {
"meta" : {
"project" : {
"-KAgjWOxjk80HIbNr68M" : {
"name" : "Gman Branding",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:33:25.170Z",
"member" : {
"-KAgkD-2GVESwNwKP3fA" : {
"email" : "abc#gman.com"
},
"-KAgkP3M4nug9Bjn-vY6" : {
"email" : "def#gman.com"
},
"-KAgkP3OF0sUgc9x9p37" : {
"email" : "ghi#gman.com"
},
"-KAgkaMyEOiXft6o-HbO" : {
"email" : "kerry#email.com"
}
}
},
"-KAgl9xlPDU5T4072FgE" : {
"-KAglqH9pxkhgC84_kAl" : {
"name" : "YuDog",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z"
},
"name" : "billing test 1",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-02-25T23:18:55.626Z",
"dateNotifyUnread" : "2016-01-25T23:23:55.626Z",
"member" : {
"-KAglNsswyk66qUZNrTU" : {
"email" : "kerry#email.com"
}
}
},
"-KAgltmLk2oOYhEDfwRL" : {
"-KAgm1Jt5q53gzLm1GIh" : {
"name" : "YuDog",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z"
},
"name" : "YuDog",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z",
"member" : {
"-KAgm1Jvss9AMZa1qDb7" : {
"email" : "joe#yudog.com"
}
}
},
"-KAgluTcE_2dv00XDm1L" : {
"-KAgm6ENmkpDiDG2lqZ4" : {
"name" : "YuDog Landing Page",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z"
},
"-KAgmBptbeInutRzNinm" : {
"name" : "YuDog Landing Page",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z"
},
"name" : "YuDog Landing Page",
"url" : "http://localhost:3000/project/abc123fasd123cc/...",
"date" : "2015-10-10T21:41:31.172Z",
"member" : {
"-KAgm6EQcvQg3oP-OnIF" : {
"email" : "joe#yudog.com"
},
"-KAgmBpwoxPYGXS9fLZ9" : {
"email" : "joe#yudog.com"
}
}
}
}
}
}
}
I've looked at 8-10 other links on SO but haven't found any that solve this issue.
The solution is to create a data structure that matches your needs. In this case, you want to look up the projects for a user based on their email address. So we'll add a node that contains this mapping:
"projects_by_email": {
"kerry#email,com": {
"-KAgjWOxjk80HIbNr68M": true,
"-KAgl9xlPDU5T4072FgE": true
},
"abc#gman,com": {
"-KAgjWOxjk80HIbNr68M": true
}
...
}
This is called denormalizing your data, although I often think of them as inverted indexes. I would probably keep the projects by uid, but the structure would be the same.
With a structure like this, you can get the list of projects for an email with a simple direct look up:
var ref = new Firebase(FBURL);
var email = 'kerry#email.com';
ref.child('projects_by_email')
.child(email)
.on("child_added", function(snapshot) {
console.log(snapshot.key());
}
);
Or if you then also want to "join" the projects themselves:
var ref = new Firebase(FBURL);
var email = 'kerry#email.com';
ref.child('projects_by_email')
.child(email)
.on("child_added", function(snapshot) {
ref.child('project').child(snapshot.key()).once('value', function(projectSnapshot) {
console.log(projectSnapshot.val());
});
}
);
This type of denormalizing is a normal part of NoSQL data modeling. The duplication may feel wasteful, but it is part of why NoSQL solution scale so well: none of the code above asks the database to consider all projects/all users. It's all directly accessing the correct nodes, which scales really well. So we're sacrificing storage space to gain improved performance/scalability; a typical space vs time trade-off.