My data is structured like this
firstName: String
lastName: String
username: String
userID: String
school: String
****EDIT: This is my front end code if that helps, maybe the problem lies here.
let index = client.index(withName: "Users")
index.setSettings([
"searchableAttributes": [
"firstName",
"lastName",
"username"
]
])
let query = Query(query: searchBar.text)
// query.facets = ["firstName", "username", "lastName"]
index.search(query, completionHandler: { (content, error) -> Void in
// completion handler//print results
})
When I index this, I only want to get results with the firstName, lastName, and username fields. I DO NOT want algolia to look through the userID and school fields for this particular search
I've tried changing the facets and the searchable attributes but nothing seems to change.
My results have been searching through every single field, not just the ones I specified. Maybe I just don't have a great understanding of how algolia queries work.
While indexing you should set which attributes are searchable.
index.setSettings([
"searchableAttributes": [
"firstName",
"lastName",
"username"
]
])
See this link for more details.
Although setting the searchableAttributes should work, you can also restrict your attributes
query.restrictSearchableAttributes = [
"firstName",
"lastName",
"username"
]
See this link for restricting your attributes for a query
Related
I have a value which needs to be compared with an array of values. Basically a user needs to check if it has the same item as another user. But I am struggling to solve this as how do I get the item of an array of users? Normally when you observe a value you do something like this:
Database.database().reference(withPath: "users/\(userID)/itemList").observeSingleEvent...
However, when it comes to an array of users itemList how can this be achieved if there are multiple ID's? I'd like compare a user item or items with other users item and check if they match in order to sort an array of users that have a match.
If there is already an example for this please help direct me there.
Update
This is how my data structure looks:
{
"users": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": {
"firstName": "Friederike",
"itemList": [
2: true,
3: true,
0: true
]
},
"C076OYmVTzOiHgnq4wPQtY2XpED2": {
"firstName": "Ian",
"itemList": [
0: true,
1: true,
3: true
]
},
"Juoiuf0N6qNmkm32jrtu6X6UK62": {
"itemList": [
0: true
],
"firstName": "Jack"
}
}
}
Update 2.0
With the answer below I am able to query through the items table but still unable to query the keys that match as there can be multiple arrays and therefore I cannot use it to filter or anything.
Ok so with your current data structure you'd have to query all nodes under users and then compare the arrays, which is very inefficient. There isn't a straight forward or easy way to do that without modifying your structure. So I suggest you modify your data structure so that each item has a list of all users that have it. Something like this:
{
"items": {
"0": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": "Friederike",
"C076OYmVTzOiHgnq4wPQtY2XpED2": "Ian",
"Juoiuf0N6qNmkm32jrtu6X6UK62": "Jack"
},
"1": {
"C076OYmVTzOiHgnq4wPQtY2XpED2": "Ian"
},
"2": {
"EWJGFYmVTzOiHgnq42ebhrg2fj": "Friederike"
}
//....
}
}
Depending on what you want to display you might want to store more information than just the users UID and username. You can query all the users that have the same items as you using a query like this:
let ref = Database.database().reference()
// assuming you already have the current users items stored in an array
for item in items {
ref.child("items").child(String(item)).observeSingleEvent(of: .value, with: { snap in
for child in snap.children {
let child = child as? DataSnapshot
if let key = child?.key, let name = child?.value as? String {
// do something with this data
}
}
})
}
Firebase database is noSQL, so data is meant to be denormalized or duplicated so that queries can be optimized. Firebase actually recommends that you avoid nesting data. Take a look at this question for more information.
Hope that helps
Code related to question asked in comments
Assuming you are storing the UID's or names of users with the same items in a string array you can prevent duplicates using .contains()
var namesWithMatchingItems = [String]()
if !namesWithMatchingItems.contains(nameYouJustFetched) {
namesWithMatchingItems.append(nameYouJustFetched)
}
I have an array of User ID Strings and I want to query data in Firebase Realtime Database to see if each User ID has a specific gender.
var nearbyUserArray = [String]()
Below is an example of the data I have in Firebase. The second level deep is the User ID (0UFhIgGAwIY1btnBeNaNkJruYKJ3). What I am trying to do is loop through my User ID array and for each User ID check if that specific user is Male. If the user is Female, remove that User ID from the array.
"profiles" : {
"0UFhIgGAwIY1btnBeNaNkJruYKJ3" : {
"dateOfBirth" : "May 11, 1987",
"gender" : "Female",
"higherAgeRange" : 36,
"interestedIn" : "Male",
"lowerAgeRange" : 29,
"name" : "Sally"
},
"6vNjngZqoTbd4oGudip3NX78pu32" : {
"dateOfBirth" : "December 23, 1990",
"gender" : "Male",
"higherAgeRange" : 39,
"interestedIn" : "Female",
"lowerAgeRange" : 25,
"name" : "Bob"
}
How would you recommend I accomplish this? I want to minimize the amount of data I have to download and manipulate on the client side. Below is what I am attempting to do but I cannot get it to work. Any ideas why this always returns Null?
func getUsersWithinGenderCriteria() {
for user in nearbyUserArray {
DatabaseService.shared.profilesRef.child(user).queryOrdered(byChild: "gender").queryEqual(toValue: "Female").observeSingleEvent(of: .value) { [weak self] (snapshot) in
print("snap: \(snapshot)")
// The idea is if snapshot is Female, remove the user ID from the array. If the snapshot is Null, keep it in the array. However, this is not working as all snapshots come back as null.
}
}
}
Also, here are the security rules I set up.
"profiles": {
"$userId": {
".indexOn": ["gender"]
}
}
When you perform a query, Firebase compares a property of each child node under the location you query to the value you pass in.
I assume that profilesRef points to /profiles in your database. In that case profilesRef.child(user) in your code points to the profile for a specific user already. When you then run a query, you are querying each of the properties of that specific user.
What you like want to do is to get all users with gender equal to Female. In that case you should query the user list:
DatabaseService.shared.profilesRef.queryOrdered(byChild: "gender").queryEqual(toValue: "Female")...
Note that you also should define the index on the level where the query is run, so on /users:
"profiles": {
".indexOn": ["gender"]
}
Another alternative (the might scale better) is to not use a query altogether, but instead load the gender property of each individual user in your array. In (approximate) code:
for user in nearbyUserArray {
DatabaseService.shared.profilesRef.child(user).child( "gender").observeSingleEvent(of: .value) { [weak self] (snapshot) in
if snapshot.value == "Female" {
print("snap: \(snapshot)")
}
}
}
I'm using the third-party library SwiftAddressBook to work with Contacts in my app. I want to iterate through contacts in my device and extract a few specific values. Namely the twitter and facebook usernames if they exist.
SwiftAddressBook has an object called SwiftAddressBookPerson which represents a single contact. And SwiftAddressBookPerson object has a property called socialProfiles which contains an array of dictionaries. Each dictionary contains info about available social media account such as the username, url etc. Printing the value of socialProfiles looks like this.
[
SwiftAddressBook.MultivalueEntry<Swift.Dictionary<SwiftAddressBook.SwiftAddressBookSocialProfileProperty, Swift.String>>(value: [
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.url: "http://twitter.com/isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.username: "isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.service: "twitter"],
label: nil, id: 0),
SwiftAddressBook.MultivalueEntry<Swift.Dictionary<SwiftAddressBook.SwiftAddressBookSocialProfileProperty, Swift.String>>(value: [
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.url: "http://www.facebook.com/isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.username: "isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.service: "facebook"],
label: Optional("facebook"), id: 1)
]
I cleaned it up a little by doing this socialProfiles.map { $0.map { $0.value } } which outputs the following.
[
[
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.url: "http://twitter.com/isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.username: "isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.service: "twitter"
],
[
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.url: "http://www.facebook.com/isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.username: "isuru",
SwiftAddressBook.SwiftAddressBookSocialProfileProperty.service: "facebook"
]
]
What I want now is given the service's name (twitter, facebook), retrieve the username used for that service.
How do I parse through the dictionary and get the value of a different field?
Given that this is an array, I'd use a first { $0.service == serviceName }.
Alternatively, you could loop over the entire array ahead of time to create a dictionary with key as serviceName, and value as the profile property. The advantage of this approach is subsequent lookups would be much faster.
I have named your array of swift address book dictionaries, profiles. The forced unwrapping in the flatMap call is safe because both key and value are tested for non-nil in the previous filter call.
The result is a single dictionary containing username values for the services listed in your interestingServices array.
let service = SwiftAddressBook.SwiftAddressBookSocialProfileProperty.service
let username = SwiftAddressBook.SwiftAddressBookSocialProfileProperty.username
let interestingServices = ["facebook", "twitter"]
let usernames = profiles
.filter {
guard
let service = $0[service],
$0[username] != nil
else {
return false
}
return interestingServices.contains(service)
}
.flatMap { profile in
return [profile[service]!, profile[username]!]
}
I am working on a group chat application, and I'm running into trouble trying to make a specific query to Firebase DB.
I need to get all of the profile pictures of users in a room. But only the rooms that the current user is a part of.
private func observeRooms() {
let databaseRef = FIRDatabase.database().reference()
let groupRef = databaseRef.child("groups")
guard let uid = FIRAuth.auth()?.currentUser?.uid else {return}
let queryRef = groupRef.queryOrdered(byChild: "participants/\(uid)").queryEqual(toValue: nil)
queryRef.observe(.childAdded, with: { snapshot in
let roomDict = snapshot.value as! [String: AnyObject]
print("roomDict: \(roomDict)")
let id = snapshot.key
let avatar = roomDict["profilePicture"] as! String
})
}
Obviously setting queryEqual(toValue: nil) returns all the rooms that the current user is NOT a part of - the opposite of what I need. I know the inverse will be to check queryEqual(toValue: true), but the : true is not showing up in the database.
This is how I add a node to "groups" in Firebase:
// Start group node for new room
let groupRef: FIRDatabaseReference = FIRDatabase.database().reference().child("groups")
let roomID = newRoomRef.key
let groupRoomRef = groupRef.child(roomID)
let groupDict = ["roomName" : roomName, "participants": [uid : true]] as [String : Any]
groupRoomRef.setValue(groupDict)
And this is how the data structure ends up:
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"gender" : "male",
"handle" : "Testing",
"name" : "Test User",
"profilePicture" : "https://graph.facebook.com/*removed*/picture?type=large&return_ssl_resources=1",
"status" : "F8B016"
}
},
"roomName" : "Test Room"
},
How can I properly the profilePicture values for all users in a room, only for the groups that the current user's UID is a participant of?
You're on the right track. When using queryEqual your query needs to match the value actually present in the database. Unfortunately that gets complicated when the values you're querying against are complex objects. In fact I'm not sure the function will work even if you did match the entire object value. The issue here is that you're trying to look up objects by the presence of a child key, instead of actually sorting by or matching their values. That doesn't quite fit the use case of any of the orderBy methods.
There are a few ways to work around this, ordered by effort.
First, you could take advantage of the sort order to get only those who have some object with the name of the UID. Since Firebase sorts nulls first, then primitives, and finally objects, you should be able to order by child and start at true (or any primitive) to get all object instances with that key (assuming, of course, you're not storing UID: true anywhere).
groupRef.queryOrdered(byChild: "participants/\(uid)").queryStartingAtValue(true)
Second, duplicate the UID as a key inside the object with the value true so that you can sort on it directly. Notice the extra step in the query.
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true,
...
groupRef.queryOrdered(byChild: "participants/\(uid)/\(uid)").queryEqual(toValue: true)
Finally, and this is what I recommend, reconsider your data structure. You're nesting a lot of data inside this collection of collections that is going to make your queries and updates unwieldy. Firebase tends to work better when you denormalize your data, preferring flat/shallow structures with some duplication of simple data. In your case, instead of storing the user's profile data inside of their group membership, you might have a collection of groups and another collection of users, with each group containing only a list of UIDs to its members and vice versa. The example in the documentation is a very similar case to yours involving membership in chat conversations. At that point your UIDs (and group IDs, if you're querying on users instead) will have a value of true so you can again use a simple value query.
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : {
"participants" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : true
...
"users" : {
"tzfHgGKWLEPzPU9GvkO4XE1QKy53" : {
"groups" : {
"-KkGzZ7frbnXmImt0JpV" : true
...
groupRef.queryOrdered(byChild: "participants/\(uid)").queryEqual(toValue: true)
I have REST for finding users by name, which for some search term returns users that has that term in first name or last name.
GET /search/joe
returns json array:
[
{"id": 1, "firstName": "Joe", "lastName": "Doe"},
{"id": 2, "firstName": "Alex", "lastName": "Joelson"}
]
How to test this REST with restassured and verify that given search term is contained in either first or last name of every row, case insensitive?
given()
.when()
.get("/user/search")
.then()
.statusCode(200)
.body(?)
Without a User object:
Response response = given().when().get("/user/search");
response.then().statusCode(200);
JSONArray users = new JSONArray(response.asString());
for (int i = 0; i < users.length(); i++) {
JSONObject user = users.getJSONObject(i);
String firstName = user.getString("firstName").toLowercase();
String lastName = user.getString("lastName").toLowercase();
Assert.assertTrue(firstName.contains("joe") || lastName.contains("joe"));
}
If you have a User object, look into using Jackson or JsonPath to make the validation simpler. You can also look into using Hamcrest to do the validations.