I am trying to fetch a random record from my Swift Realm database.
I have previously used the sample function in mongoDB, so I thought there must be an equivalent in Realm (it's based on mongoDB, right?)
I can't find any documentation on such a function, and I've only found search results which suggest to fetch the entire collection then choose a random record [1, 2]. Obviously, this is inefficient.
Am I missing something obvious, is there a better way to do it?
See below for an example mongoDB query:
db.Words.aggregate([
{ $match: { gender: gender } },
{ $sample: { size: 1 } }
])
For clarity the code in the question is not part of the Realm Swift SDK for local or sync but it is a query directly to Atlas using app services, so it would be valid for non-sync'ing or non-local applications. (Use the Swift SDK if possible!)
If we're doing this using the SDK, you can actually leverage high-level Swift functions to return a result using .randomElement()
So given a PersonClass that has a name property
class PersonClass: Object {
#Persisted var name = ""
}
we can this use this code to return a random person from Realm and output their name to console
if let randomPerson = realm.objects(PersonClass.self).randomElement() {
print(randomPerson.name)
} else {
print("no data was returned)")
}
Related
Given the data structure below in firebase, i want to run a query to retrieve the blog 'efg'. I don't know the user id at this point.
{Users :
"1234567": {
name: 'Bob',
blogs: {
'abc':{..},
'zyx':{..}
}
},
"7654321": {
name: 'Frank',
blogs: {
'efg':{..},
'hij':{..}
}
}
}
The Firebase API only allows you to filter children one level deep (or with a known path) with its orderByChild and equalTo methods.
So without modifying/expanding your current data structure that just leaves the option to retrieve all data and filter it client-side:
var ref = firebase.database().ref('Users');
ref.once('value', function(snapshot) {
snapshot.forEach(function(userSnapshot) {
var blogs = userSnapshot.val().blogs;
var daBlog = blogs['efg'];
});
});
This is of course highly inefficient and won't scale when you have a non-trivial number of users/blogs.
So the common solution to that is to a so-called index to your tree that maps the key that you are looking for to the path where it resides:
{Blogs:
"abc": "1234567",
"zyx": "1234567",
"efg": "7654321",
"hij": "7654321"
}
Then you can quickly access the blog using:
var ref = firebase.database().ref();
ref.child('Blogs/efg').once('value', function(snapshot) {
var user = snapshot.val();
ref.child('Blogs/'+user+'/blogs').once('value', function(blogSnapshot) {
var daBlog = blogSnapshot.val();
});
});
You might also want to reconsider if you can restructure your data to better fit your use-case and Firebase's limitations. They have some good documentation on structuring your data, but the most important one for people new to NoSQL/hierarchical databases seems to be "avoid building nests".
Also see my answer on Firebase query if child of child contains a value for a good example. I'd also recommend reading about many-to-many relationships in Firebase, and this article on general NoSQL data modeling.
Given your current data structure you can retrieve the User that contains the blog post you are looking for.
const db = firebase.database()
const usersRef = db.ref('users')
const query = usersRef.orderByChild('blogs/efg').limitToLast(1)
query.once('value').then((ss) => {
console.log(ss.val()) //=> { '7654321': { blogs: {...}}}
})
You need to use limitToLast since Objects are sorted last when using orderByChild docs.
It's actually super easy - just use foreslash:
db.ref('Users').child("userid/name")
db.ref('Users').child("userid/blogs")
db.ref('Users').child("userid/blogs/abc")
No need of loops or anything more.
I have an iOS app using Cloud Firestore and have problems with updating the data. My goal is to add urls to a dictionary one by one, but all I get is rewritten one value. How should I use setData and updateData? Tried it different ways
storageRef.child("users/" + currentUser.value!.documentID + "/" + faceRef.documentID + ".jpg")
.putData(data!).observe(.success) { (snapshot) in
guard let downloadURL = snapshot.metadata?.downloadURL()?.absoluteString else { return }
let db = self.fsReference.document(self.currentUser.value!.documentID)
var dict = ["faces": ["": ""]]
dict["faces"] = ["newvalue\(downloadURL.hashValue)": downloadURL]
db.updateData(dict)
completion?()
Here's what I tried. Any advice would be nice, thanks in advance!
UPD: Tried to move my dictionary to subcollection, but after .collection("newCollection").document("newdocument") collection does not appear. What might be the problem?
So what I am seeing is you are using cloud storage to save profile pictures and you want to save each one of the urls those pictures. You need to understand that both setValue() and updateValue() do just about the same thing. A note with updateValue() is it will create that document if it doesn't already exist. So, when updating values in Firestore understand that it sets the value to what you give it, which can be misleading at first.
1st When updating any document start by getting the document first. If people are constantly updating different document you may want to consider using Firestore transactions: https://firebase.google.com/docs/firestore/manage-data/transactions#transactions
This will make sure that your data is updated correctly.
2nd Append the URL to the to the array, I am not how you set it up, but I would setup the firestore to look something like this
"users" = [
"unique_id = "{
"firstname": "John",
"lastname": "Doe",
"unique_id": "document_id_here"
"faces": [ {key: value} ]
}
]
When you serialize that object your faces object should be this [[String: Any]]
3rd, last step would be to get the document and update just that value
// Get the value in the completion with the data use this code
// Drill down to the property you want to update using the completion data ex.
var faces = completedData.faces
faces.append("[key: value]")
// Update the data back to firestore
let path = Firestore.firestore().collection("users").document("unique_user_id")
// Merging is so important. otherwise it will override your document
path.setData(["facesKey: faces"], merge: true) {(error in
if let error = error {
// good error handling here
}
// Successfully updated document
)}
I Am building an express JS application with graphql, and mongodb (mongoose). I am using facebooks Dataloader to batch and cache requests.
Its working perfectly fine except for this use case.
I have a database filled with users posts. Each post contains the users ID for reference. When i make a call to return all the posts in the database. The posts are returned fine but if i try to get the user in each post. Users with multiple posts will only return a single user because the key for the second user is cached. So 2 posts(keys) from user "x" will only return 1 user object "x".
However Dataloader has to return the same amount of promises as keys that it recieves.
It has a option to specify cache as false so each key will make a request. But this doesnt seem to work for my use case.
Sorry if i havn't explained this very well.
this is my graphql request
query {
getAllPosts {
_id // This is returned fine
user {
_id
}
}
}
Returned error:
DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys.
are you trying to batch post keys [1, 2, 3] and expecting to get user results [{ user1 }, {user2}, { user1 }]?
or are you trying to batch user keys [1, 2] and expecting to get post results [{ post1}, {post3}] and [{ post2 }]?
seems like only in the second case will you run into a situation where you have length of keys differing from length of results array.
to solve the second, you could do something like this in sql:
const loader = new Dataloader(userIDs => {
const promises = userIDs.map(id => {
return db('user_posts')
.where('user_id', id);
});
return Promise.all(promises);
})
loader.load(1)
loader.load(2)
so you return [[{ post1}, {post3}], [{ post2 }]] which dataloader can unwrap.
if you had done this instead:
const loader = new Dataloader(userIDs => {
return db('user_posts')
.where('user_id', [userIDs]);
})
loader.load(1)
loader.load(2)
you will instead get [{ post1}, {post3}, { post2 }] and hence the error: the function did not return a Promise of an Array of the same length as the Array of keys
not sure if the above is relevant / helpful. i can revise if you can provide a snippet of your batch load function
You need to map the data returned from the database to the Array of keys.
Dataloader: The Array of values must be the same length as the Array of keys
This issue is well explained in this YouTube Video - Dataloader - Highly recommended
In the firebase docs for firebase database there is info on flattening a database and a message app is used as an example.
https://firebase.google.com/docs/database/web/structure-data
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
Under "members"/"one" each key is the name of a participating member in the chat. I'm pretty sure in firebase database there is a getKey method to get each key.
I have set up my database (using firestore instead of firebase) in a similar way but where each chat is a unique identifier and the document contains the members of the chat where the key is their firebase auth id eg.
var docsref: DocumentReference?
docsref = self.defaultStore?.colleection("chats").document(chatID)
docsref?.getDocument { (document, error) in
if let _ = document {
print("document!.data()")
} else {
print("Document does not exist")
}
}
And when I output:
print(document!.data())
I get ["2mHuccccxxxxxxxxxxk4wkIAt33": 1] which is the firebase auth code of one of the participating chat members and a boolean.
I would like to get that key but wasn't sure how.
Thanks.
I'm not sure if it's the exact Syntax but self.defaultStore?.colleection might be a typo with the double E. It could also possibly be that you're missing a step. You're getting inside the chatID and you're expecting to output the keys inside members without actually accessing it. I'd access members after .document(chatID)
You can get it as follows.
to get value document.data()
to get key document.documentID
I'm using the request library to make calls from one sails app to another one which exposes the default blueprint endpoints. It works fine when I query by non-id fields, but I need to run some queries by passing id arrays. The problem is that the moment you provide an id, only the first id is considered, effectively not allowing this kind of query.
Is there a way to get around this? I could switch over to another attribute if all else fails but I need to know if there is a proper way around this.
Here's how I'm querying:
var idArr = [];//array of ids
var queryParams = { id: idArr };
var options: {
//headers, method and url here
json: queryParams
};
request(options, function(err, response, body){
if (err) return next(err);
return next(null, body);
});
Thanks in advance.
Sails blueprint APIs allow you to use the same waterline query langauge that you would otherwise use in code.
You can directly pass the array of id's in the get call to receive the objects as follows
GET /city?where={"id":[1, 2]}
Refer here for more.
Have fun!
Alright, I switched to a hacky solution to get moving.
For all models that needed querying by id arrays, I added a secondary attribute to the model. Let's call it code. Then, in afterCreate(), I updated code and set it equal to the id. This incurs an additional database call, but it's fine since it's called just once - when the object is created.
Here's the code.
module.exports = {
attributes: {
code: {
type: 'string'//the secondary attribute
},
// other attributes
},
afterCreate: function (newObj, next) {
Model.update({ id: newObj.id }, { code: newObj.id }, next);
}
}
Note that newObj isn't a Model object as even I was led to believe. So we cannot simply update its code and call newObj.save().
After this, in the queries having id arrays, substituting id with code makes them work as expected!