Suppose we have two types of documents. One - stores meta data, many others - have complicated processing, depending on the state of the first document. We do not want to duplicate the state in all documents of the second type because lots of them. How to develop MAP function to receive data from the first-type document.
{ "_id":"123",
"public":true
}
{
"_id":"321",
"owner_id":"123"
"data":"..."
}
function(doc) {
if (doc._id=="321"){
// How do get another document like in python, for example
var doc2 = db[doc.owner_id];
if (doc2.public) {
emit(doc._id, null);
}
}
}
You can do this;
function(doc) {
emit(doc._id, {"_id":doc.owner_id});
}
Then when you do ?key=321&include_docs=true the included doc will be the one with id of doc.owner_id not 321.
More here:
http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents
Related
I have been getting this problem now a few times when I'm coding and I think I just don't understand the way SwiftUI execute the order of the code.
I have a method in my context model that gets data from Firebase that I call in .onAppear. But the method doesn't execute the last line in the method after running the whole for loop.
And when I set breakpoints on different places it seems that the code first is just run through without making the for loop and then it returns to the method again and then does one run of the for loop and then it jumps to some other strange place and then back to the method again...
I guess I just don't get it?
Has it something to do with main/background thread? Can you help me?
Here is my code.
Part of my UI-view that calls the method getTeachersAndCoursesInSchool
VStack {
//Title
Text("Settings")
.font(.title)
Spacer()
NavigationView {
VStack {
NavigationLink {
ManageCourses()
.onAppear {
model.getTeachersAndCoursesInSchool()
}
} label: {
ZStack {
// ...
}
}
}
}
}
Here is the for-loop of my method:
//Get a reference to the teacher list of the school
let teachersInSchool = schoolColl.document("TeacherList")
//Get teacherlist document data
teachersInSchool.getDocument { docSnapshot, docError in
if docError == nil && docSnapshot != nil {
//Create temporary modelArr to append teachermodel to
var tempTeacherAndCoursesInSchoolArr = [TeacherModel]()
//Loop through all FB teachers collections in local array and get their teacherData
for name in teachersInSchoolArr {
//Get reference to each teachers data document and get the document data
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument {
teacherDataSnapshot, teacherDataError in
//check for error in getting snapshot
if teacherDataError == nil {
//Load teacher data from FB
//check for snapshot is not nil
if let teacherDataSnapshot = teacherDataSnapshot {
do {
//Set local variable to teacher data
let teacherData: TeacherModel = try teacherDataSnapshot.data(as: TeacherModel.self)
//Append teacher to total contentmodel array of teacherdata
tempTeacherAndCoursesInSchoolArr.append(teacherData)
} catch {
//Handle error
}
}
} else {
//TODO: Error in loading data, handle error
}
}
}
//Assign all teacher and their courses to contentmodel data
self.teacherAndCoursesInSchool = tempTeacherAndCoursesInSchoolArr
} else {
//TODO: handle error in fetching teacher Data
}
}
The method assigns data correctly to the tempTeacherAndCoursesInSchoolArr but the method doesn't assign the tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool in the last line. Why doesn't it do that?
Most of Firebase's API calls are asynchronous: when you ask Firestore to fetch a document for you, it needs to communicate with the backend, and - even on a fast connection - that will take some time.
To deal with this, you can use two approaches: callbacks and async/await. Both work fine, but you might find that async/await is easier to read. If you're interested in the details, check out my blog post Calling asynchronous Firebase APIs from Swift - Callbacks, Combine, and async/await | Peter Friese.
In your code snippet, you use a completion handler for handling the documents that getDocuments returns once the asynchronous call returns:
schoolColl.document("Teachers").collection(name).document("Teacher data").getDocument { teacherDataSnapshot, teacherDataError in
// ...
}
However, the code for assigning tempTeacherAndCoursesInSchoolArr to self.teacherAndCoursesInSchool is outside of the completion handler, so it will be called before the completion handler is even called.
You can fix this in a couple of ways:
Use Swift's async/await for fetching the data, and then use a Task group (see Paul's excellent article about how they work) to fetch all the teachers' data in parallel, and aggregate them once all the data has been received.
You might also want to consider using a collection group query - it seems like your data is structure in a way that should make this possible.
Generally, iterating over the elements of a collection and performing Firestore queries for each of the elements is considered a bad practice as is drags down the performance of your app, since it will perform N+1 network requests when instead it could just send one single network request (using a collection group query).
I need to update a collection in values like this :
{
"email" : "x#gmail.com",
"fullName" : "Mehr",
"locations" : ["sss","dsds","adsdsd"]
}
Locations needs to be an array. in firebase how can I do that ... and also it should check duplicated.
I did like this :
const locations=[]
locations.push(id)
firebase.database().ref(`/users/ + ${userId}`).push({ locations })
Since you need to check for duplicates, you'll need to first read the value of the array, and then update it. In the Firebase Realtime Database that combination can is done through a transaction. You can run the transaction on the locations node itself here:
var locationsRef = firebase.database().ref(`/users/${userId}/locations`);
var newLocation = "xyz";
locationsRef.transaction(function(locations) {
if (locations) {
if (locations.indexOf(newLocation) === -1) {
locations.push(newLocation);
}
}
return locations;
});
As you can see, this loads the locations, ensures the new location is present once, and then writes it back to the database.
Note that Firebase recommends using arrays for set-like data structures such as this. Consider using the more direct mapping of a mathematical set to JavaScript:
"locations" : {
"sss": true,
"dsds": true,
"adsdsd": true
}
One advantage of this structure is that adding a new value is an idempotent operation. Say that we have a location "sss". We add that to the location with:
locations["sss"] = true;
Now there are two options:
"sss" was not yet in the node, in which case this operation adds it.
"sss" was already in the node, in which case this operation does nothing.
For more on this, see best practices for arrays in Firebase.
you can simply push the items in a loop:
if(locations.length > 0) {
var ref = firebase.database().ref(`/users/ + ${userId}`).child('locations');
for(i=0; i < locations.length; i++) {
ref.push(locations[i]);
}
}
this also creates unique keys for the items, instead of a numerical index (which tends to change).
You can use update rather than push method. It would much easier for you. Try it like below
var locationsObj={};
if(locations.length > 0) {
for(i=0; i < locations.length; i++) {
var key= firebase.database().ref(`/users/ + ${userId}`).child('locations').push().key;
locationsObj[`/users/ + ${userId}` +'/locations/' + key] =locations[i];
}
firebase.database().ref().update(locationsObj).then(function(){// which return the promise.
console.log("successfully updated");
})
}
Note : update method is used to update multiple paths at a same time. which will be helpful in this case, but if you use push in the loop then you have to wait for the all the push to return the promises. In the update method it will take care of the all promises and returns at once. Either you get success or error.
Hi i have a noSql db in firebase.
I want to get the object where userId is 288
i'v tried many combinations but i cant figure out how its done.
This is my code so far :
var refTest= database.ref('conversation')
var query = refTest
.orderByChild('messages');
query.on('value', function(data) {
var a = data.val();
console.log(a.messages.userId);
console.log(data.val());
});
This is a image of my "schema"
I'm obviously a noob when it comes to NoSQL. I do understand SQL
All help is appreciated
You can order/filter on a nested value like this:
var refTest= database.ref('conversation')
var query = refTest.orderByChild('messages/userId').equalTo("288");
query.on('value', function(snapshot) {
snapshot.forEach(function(child) {
console.log(child.key);
console.log(child.val());
});
});
The forEach is needed, since there may be multiple child nodes with messages/userId equal to 288.
The key named "messages" doesn't make sense in your schema. Because if you want to have another message under that conversation, then you wouldn't be able to add it with the same key name and you also couldn't add it under "messages" because it would overwrite the other one. My suggestion is to use the push() method for adding a new message. This way you uniquely identify each message.
Regarding your question, an easy to understand way of parsing your schema is this: you loop through each message of each conversation for finding the messages with userID.
refTest.on('value', function(data) {
var conversations = data.val();
for (conversation in conversations){
for (message in conversation) {
if (message.userId == 288) {
// do whatever you need
// and eventually return something to break the loops
}
}
}
}
Of course, you can adapt it based on your needs
I'm implementing an orbit.js adapter for firebase, orbit-firebase.
I'm looking for an efficient way to query for multiple records so that I can resolve relationships between objects e.g. course.participants
{
course: {
'c1': {
participants: ['p1', 'p2']
}
},
participant: {
'p1': {
name: "Jim"
},
'p2': {
name: "Mark"
}
}
}
Given I have the ids 'p1' and 'p2' what's an efficient way to query for both of them?
I can't use a query because I'm using security rules with the participants i.e. the user that's trying to resolve course.participants doesn't have access to all of the participants (bear in mind this is a contrived example).
I'd recommend that you move away from arrays in your JSON structures. These are nothing but pain in real-time, distributed data and don't work particularly well with security rules and situations like this.
Given this structure:
course: {
'c1': {
participants: {
'p1': true, 'p2': true
}
}
}
I could join these fairly easily. You can get a normalized ref that behaves just like a Firebase ref by using Firebase.util's NormalizedCollection:
var ref = new Firebase(...);
var coll = new Firebase.util.NormalizedCollection(
ref.child('course/c1/participants'),
ref.child('participant')
).select('participant.name').ref();
coll.on('child_added', function(snap) {
console.log('participant ' + snap.key(), snap.val());
});
Note that this data structure (sans the array) will also make it simpler to enforce read rules on participant data and the like by allowing you to directly reference the user ids under $courseid/participants/, since they are now keys that can match a $ variable.
I have made a collection
var Words = new Meteor.Collection("words");
and published it:
Meteor.publish("words", function() {
return Words.find();
});
so that I can access it on the client. Problem is, this collection is going to get very large and I just want to publish a transform of it. For example, let's say I want to publish a summary called "num words by length", which is an array of ints, where the index is the length of a word and the item is the number of words of that length. So
wordsByLength[5] = 12;
means that there are 12 words of length 5. In SQL terms, it's a simple GROUP BY/COUNT over the original data set. I'm trying to make a template on the client that will say something like
You have N words of length X
for each length. My question boils down to "I have my data in form A, and I want to publish a transformed version, B".
UPDATE You can transform a collection on the server like this:
Words = new Mongo.Collection("collection_name");
Meteor.publish("yourRecordSet", function() {
//Transform function
var transform = function(doc) {
doc.date = new Date();
return doc;
}
var self = this;
var observer = Words.find().observe({
added: function (document) {
self.added('collection_name', document._id, transform(document));
},
changed: function (newDocument, oldDocument) {
self.changed('collection_name', oldDocument._id, transform(newDocument));
},
removed: function (oldDocument) {
self.removed('collection_name', oldDocument._id);
}
});
self.onStop(function () {
observer.stop();
});
self.ready();
});
To wrap transformations mentioned in other answers, you could use the package I developed, meteor-middleware. It provides a nice pluggable API for this. So instead of just providing a transform, you can stack them one on another. This allows for code reuse, permissions checks (like removing or aggregating fields based on permissions), etc. So you could create a class which allows you to aggregate documents in the way you want.
But for your particular case you might want to look into MongoDB aggregation pipeline. If there is really a lot of words you probably do not want to transfer all of them from the MongoDB server to the Meteor server side. On the other hand, aggregation pipeline lacks the reactivity you might want to have. So that published documents change counts as words come in and go.
To address that you could use another package I developed, PeerDB. It allows you to specify triggers which would be reactively called as data changes, and stored in the database. Then you could simply use normal publishing to send counts to the client. The downside is that all users should be interested in the same collection. It works globally, not per user. But if you are interested in counts of words per whole collection, you could do something like (in CoffeesScript):
class WordCounts extends Document
#Meta
name: 'WordCounts'
class Words extends Document
#Meta
name: 'Words'
triggers: =>
countWords: #Trigger ['word'], (newDocument, oldDocument) ->
# Document has been removed.
if not newDocument._id
WordCounts.update
length: oldDocument.word.length
,
$inc:
count: -1
# Document has been added.
else if not oldDocument._id
WordCounts.update
length: newDocument.word.length
,
$inc:
count: 1
# Word length has changed.
else if newDocument.word.length isnt oldDocument.word.length
WordCounts.update
length: oldDocument.word.length
,
$inc:
count: -1
WordCounts.update
length: newDocument.word.length
,
$inc:
count: 1
And then you could simply publish WordCounts documents:
Meteor.publish 'counts', ->
WordCounts.documents.find()
You could assemble the counts by going through each document in Words, (cursor for each)
var countingCursor = Words.find({});
var wordCounts = {};
countingCursor.forEach(function (word) {
wordCounts[word.length].count += 1;
wordCounts[word.length].words = wordCounts[word.length].words || []
wordCounts[word.length].words.push(word);
});
create a local collection,
var counts = new Meteor.Collection('local-counts-collection', {connection: null});
and insert your answers
var key, value;
for (key in wordCounts) {
value = object[key];
counts.insert({
length: key,
count: value.count,
members: value.words
});
}
Counts is now a collection, just not stored in Mongo.
Not tested!