How can I filter out the RealmFilter.objectIds that has a given Int?
func delete(ids: [Int]) {
let filterResultsToDelete = realm.objects(CRMRealmFilterResult.self).filter("ANY objectIds IN %#",ids)
//Crashes
}
class RealmFilterResult : Object {
#objc dynamic var filterId: Int = 0
let objectIds = List<Int>()
override static func primaryKey() -> String {
return "filterId"
}
}
This may not be at all what you want but it was a good exercise. Maybe this will help.
Let me re-state what I think you're asking: You've got a series of objects that each have a List property of Int's and you want to be able to query for all objects that have a particular int in their list
Using a more real-world example, suppose we have a list of teams and we keep a list of game scores (a list) within each team
class TeamObject: Object {
#objc dynamic var object_id = NSUUID().uuidString
let scoreList = List<ScoreObject>()
override static func primaryKey() -> String? {
return "object_id"
}
}
and we have a score object that stores a score as an Int (and maybe other details like who they played or the date)
class ScoreObject: Object {
#objc dynamic var score = 0
let teamsWithScores = LinkingObjects(fromType: TeamObject.self, property: "scoreList")
}
For simplicity, let's create three scores and two teams and give each team two scores in their list.
let score1 = ScoreObject()
score1.score = 1
let score2 = ScoreObject()
score2.score = 2
let score3 = ScoreObject()
score3.score = 3
let t1 = TeamObject()
t1.scoreList.append(score1)
t1.scoreList.append(score3)
let t2 = TeamObject()
t2.scoreList.append(score2)
t2.scoreList.append(score3)
and write them to realm
try! realm.write {
realm.add(t1)
realm.add(t2)
}
from there, we can get any team that has a score of 1, which solves the question of getting the objects that have a list that contain a given int.
let results = realm.objects(ScoreObject.self).filter("score IN %#", [1])
if results.count > 0 {
for aScore in results {
let teamsWithThisScore = aScore.teamsWithScores
for team in teamsWithThisScore {
print("score: \(aScore.score)")
print(" id: \(team.object_id)")
}
}
} else {
print("no teams with those scores")
}
you can expand on this to get teams (object) that have several scores (ints)
let results = realm.objects(ScoreObject.self).filter("score IN %#", [1,3])
As I said, it may be off base but it does provide a solution in a more object oriented way.
Querying List of primitives (i.e. non Object subclasses, like Int in your case) is not yet supported by Realm.
You can follow the status of this on this GitHub issue.
Related
These are my realm classes and functions:
class RealmItems: Object {
#objc dynamic var header = String()
#objc dynamic var specification = String()
#objc dynamic var day = Int()
#objc dynamic var month = Int()
#objc dynamic var deadline = String()
#objc dynamic var status = String()
}
class RealmModel {
static let shared = RealmModel()
private let realm = try! Realm()
func addTask(headerTask: String, specificationTask: String, dayTask: Int, monthTask: Int, deadlineTask: String, statusTask: String) {
let new = RealmItems()
new.header = headerTask
new.specification = specificationTask
new.day = dayTask
new.month = monthTask
new.deadline = deadlineTask
new.status = statusTask
try! realm.write {
realm.add(new)
}
}
func deleteTask(name: RealmItems) {
try! realm.write {
realm.delete(name)
}
}
func getTasks() -> [RealmItems] {
var arrayTasks: [RealmItems] = []
for task in realm.objects(RealmItems.self) {
arrayTasks.append(task)
}
return arrayTasks.sorted{$0.day > $1.day}
}
}
function getTasks() doesn't work the way i want it works. Now collection shows oldest cells higher than newest - that's wrong. I want to newest were higher than oldest
A few things to be aware of.
First, read Realm Objects Are Lazily Loaded e.g. don't cast them to an array or sort them using Swift functions. Well, you can but it can lead to other issues so best to avoid that unless there's a specific use case that requires it.
Secondly, Realm Results do not have a guaranteed order unless you specify that order. On the other hand, a List object maintains it's order.
Ordering a Results collection is simple, and actually keeps the objects in the results in order as object properties change, for example
let orderedResults = realm.objects(RealmItems.self).sorted(byKeyPath: "day", ascending: false)
Will load all of the RealmItems objects sorted by day, descending (e.g. 20th will be at the top and the 1st will be at the bottom)and the results will maintain their order. e.g. if a day changes, it will auto sort within the list. Pretty magical, huh?
in realm i given id = 0 and it is as primary key and it will be auto increment, but problem is while updating it is saving in the index path 0 as declare as id : Int = 0.
Where ever i update also it is only updating in 0th index only.
i want to update as per selected object.
What to do?
Program :-
class Discount: Object {
#objc dynamic var id : Int = 0
#objc dynamic var offerName : String = ""
#objc dynamic var percentage: Float = 0.00
#objc dynamic var segmentIndex : Int = 0
#objc dynamic var dateWise: Date?
override class func primaryKey() -> String? {
return "id"
}
//Incrementa ID
func IncrementaID() -> Int{
let realm = try! Realm()
if let retNext = realm.objects(Discount.self).sorted(byKeyPath: "id").last?.id {
return retNext + 1
}else{
return 1
}
}
}
Generally speaking, auto-incrementing primary keys are challenging to deal with and can cause headaches long term.
What's generally most important is ensuring primary keys are unique and using UUID strings is ideally suited for that.
class Discount: Object {
#objc dynamic var discount_id = UUID().uuidString
override static func primaryKey() -> String? {
return "discount_id"
}
}
There may be concern about ordering and often times that managed by either adding a class var to determine ordering; like a timestamp for example or if you want to preserve ordering, objects can be added to a List, which keeps the order, like an array.
To answer your specific question, the code in your question is not complete (it was partially pulled from another question). The reason is that for each object that's created, it must be written to realm first, then the next object's primary key is based on the prior object.
Here's an example.
#objcMembers class User: Object {
dynamic var uid: Int = 0
dynamic var username: String?
func getNextUid() -> Int {
let realm = try! Realm()
if let lastObject = realm.objects(User.self).sorted(byKeyPath: "uid").first {
let lastUid = lastObject.uid
let nextUid = lastUid + 1
return nextUid
}
return 1
}
override static func primaryKey() -> String? {
return "uid"
}
}
now the sequence to use this is as follows
let u0 = User()
u0.uid = u0.getNextUid()
u0.username = "User 0"
let realm = try! Realm()
try! realm.write {
realm.add(u0)
}
let u1 = User()
u1.uid = u1.getNextUid()
u1.username = "User 1"
try! realm.write {
realm.add(u1)
}
as you can see, each object needs to be written to realm in order to the next object to be queried to get the prior objects primary key.
It's a whole lot of potentially unnecessary work and code.
My advice: Stick with the UUID().uuidString for primary keys.
Below is my custom object class.
class UserGroups: NSObject {
let groupName: String
let users: [CheckIn]?
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn]{
return jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})
}
}
I've an array of above custom objects. How can I combine 2 or more custom objects into a single object by merging users of every object having same groupName.
Below is my CheckIn Model:
class CheckIn: NSObject {
let id: String
let firstName: String
let lastName: String
let latitude: String
let longitude: String
let hint: String
init(json: JSON) {
self.id = json[Constants.Models.CheckIn.id].stringValue
self.firstName = json[Constants.Models.CheckIn.firstName].stringValue
self.lastName = json[Constants.Models.CheckIn.lastName].stringValue
self.hint = json[Constants.Models.CheckIn.hint].stringValue
self.latitude = json["location"][Constants.Models.CheckIn.latitude].stringValue
self.longitude = json["location"][Constants.Models.CheckIn.longitude].stringValue
}
}
id field is not unique in CheckIn.
Here's a slightly simplified example that shows how to combine groups that have the same group name.
Here is the UserGroup class. users is now a variable (var) because we will be adding elements to groups to combine them.
class UserGroups: NSObject {
let groupName: String
var users: [String]?
init(groupName: String, users: [String]?) {
self.groupName = groupName
self.users = users
}
}
Here are three groups, two of the share the same group name, Blues.
let group1 = UserGroups(groupName: "Blues", users: ["Tom", "Huck", "Jim"])
let group2 = UserGroups(groupName: "Reds", users: ["Jo", "Ben", "Tommy"])
let group3 = UserGroups(groupName: "Blues", users: ["Polly", "Watson", "Douglas"])
Next, we'll put all the groups in an array.
let allGroups = [group1, group2, group3]
Here, we use Swift's reduce function to allow us to reduce the array to only groups with unique group names.
let compacted = allGroups.reduce([UserGroups](), { partialResult, group in
var dupe = partialResult.filter {$0.groupName == group.groupName }.first
if let dupeGroup = dupe {
dupeGroup.users?.append(contentsOf: group.users ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
The array is now reduced to unique groups, we print out all the groups and their users with the help of Swift's map function.
print(compacted.map { $0.users })
// Prints [
Optional(["Tom", "Huck", "Jim", "Polly", "Watson", "Douglas"]),
Optional(["Jo", "Ben", "Tommy"])
]
The Solution
You did not include the CheckIn model, but I will assume that it has some sort of an id field unique to each user. We will use this to make the object Hashable:
// Add this to your file outside of the UserGroups class
extension CheckIn: Hashable {
var hashValue: Int { return self.id }
}
Making it Hashable allows you to convert the Array to a Set, which does not allow duplicates and will remove them in a very efficient way.
// Change getUserGroupsList as follows
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})))
}
Optional Considerations
As an aside, in case you're coming from another language, Swift gives you nice type inference and default names for closure arguments ($0 is the first argument). You can probably make the code a little less verbose, but it's a matter of taste which is preferred.
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap { CheckIn(json: $0) }))
}
Also consider whether you really want the return value to be an array. If you want the list to always have unique users, it is a bit more efficient to use a Set as your return type and forgo the conversion back to an Array like this:
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
Finally, consider whether you really need the users property to be optional. With sequence types, it is often sufficient to use an empty sequence to denote absence of a value. Depending on your situation, this may simplify your code. The final version looks like this:
class UserGroups: NSObject {
let groupName: String
let users: Set<CheckIn>
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
}
Maintaining Order
The caveat is that Set does not maintain the order of the items. If the order of the groups does matter, we can use this solution instead:
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
var encountered: Set<CheckIn> = []
return jsonArray.flatMap { CheckIn(json: $0) }.filter { encountered.update(with: $0) == nil }
}
In this version, we still use a set, but only to maintain a set of items we've encountered already. The update method on a set returns the same value if it's already in the set or returns nil if it's being inserted for the first time. We use that to filter our array to those items being encountered for the first time while adding them to the set of encountered items to filter them out when they are subsequently encountered again.
I think this is a simple solution, but i´m stuck with the best approach.
My Realm Database is made with the objects POI and CATEGORY. Where a POI can have one or multiple object of CATEGORY
class POI: Object {
dynamic var id:String = ""
dynamic var name:String = ""
dynamic var visited:Bool = false;
let categories = List<CATEGORY>()
override static func primaryKey() -> String?
{
return "id";
}
}
Later I need to show the number of POI that have a particular CATEGORY, and the number of POI that have a given CATEGORY with the boolean visited has true.
Something like this:
func getAllVisitedPointsWithCategory(idCategory:String) -> Results<POI> {
}
func getAllPointsWithCategory(idCategory:String) -> Results<POI>{
}
Any suggestion?
This should work:
func getAllVisitedPointsWithCategory(idCategory:String) -> [POI] {
let containingPOI = getAllPointsWithCategory(idCategory)
return containingPOI.filter({ (poi) -> Bool in
return poi.visited
})
}
func getAllPointsWithCategory(idCategory:String) -> [POI] {
let realm = try! Realm()
let containingPOI = realm.objects(POI).filter({ (poi) -> Bool in
return poi.categories.contains({ (cat) -> Bool in
return idCategory == cat.id
})
})
return containingPOI
}
It's basically just querying data and filtering the result.
This can be done more concisely and faster by using Realm's query engine rather than filtering the objects individually in Swift:
func getAllVisitedPointsWithCategory(idCategory:String) -> Results<POI> {
return getAllPointsWithCategory(idCategory).filter("visited = true")
}
func getAllPointsWithCategory(idCategory:String) -> Results<POI>{
let realm = try! Realm()
return realm.objects(POI.self).filter("ANY categories.id = %#", idCategory)
}
On my API I've got a relationship between Stands and Products. In which stands have products, but products can be found in different stands aswell. I'm trying to replicate this relationship on my iOS-application, using Realm but I can't seem to get it working.
The goal of having this relationship is being able to search for Stands that sell particular products.
My model:
class Stand: Object {
dynamic var id : Int = 0
dynamic var name : String = ""
dynamic var latitude : Double = 0.0
dynamic var longitude : Double = 0.0
let products = List<Product>()
override static func primaryKey() -> String? {
return "id"
}
}
class Product: Object {
dynamic var id : Int = 0
dynamic var name : String = ""
let stands = List<Stand>()
override static func primaryKey() -> String? {
return "id"
}
}
When doing my API request for Stands I retrieve the associated Products with it aswell. When I append these to the Stands it works fine for my Stands model as the products are just normally added in the List().
But all of the products are individually created, without having any Stands attached to them.
Is there a way to directly assign these Stands to the Products upon creation of the Products? Just like it's happening the other way around?
My current solution is this..
func retrieveAndCacheStands(clearDatabase clearDatabase: Bool?) {
backend.retrievePath(endpoint.StandsIndex, completion: { (response) -> () in
let listOfProducts : List<(Product)> = List<(Product)>()
func addProducts(stand: Stand, products: List<(Product)>?) {
for product in products! {
print(product.name)
let newProduct = Product()
newProduct.id = product.id
newProduct.name = product.name
newProduct.stands.append(stand)
try! self.realm.write({ () -> Void in
self.realm.create(Product.self, value: newProduct, update: true)
})
}
listOfProducts.removeAll()
}
for (_, value) in response {
let stand = Stand()
stand.id = value["id"].intValue
stand.name = value["name"].string!
stand.latitude = value["latitude"].double!
stand.longitude = value["longitude"].double!
for (_, products) in value["products"] {
let product = Product()
product.id = products["id"].intValue
product.name = products["name"].string!
stand.products.append(product)
listOfProducts.append(product)
}
try! self.realm.write({ () -> Void in
self.realm.create(Stand.self, value: stand, update: true)
})
addProducts(stand, products: listOfProducts)
}
print(Realm.Configuration.defaultConfiguration.path!)
}) { (error) -> () in
print(error)
}
}
This stores the Stands and adds Products to them. It also creates all the products and adds 1 Stand per 10 ish Products (?).
I can't seem to figure out how to make this work. Does anyone else know how to solve this? Or a better solution?
Instead of doing the double-bookkeeping necessary to maintain inverse relationships manually, you should use Realm's inverse relationships mechanism, which provides you with all of the objects pointing to another object using a given property:
class Product: Object {
dynamic var id: Int = 0
dynamic var name: String = ""
// Realm doesn't persist this property because it is of type `LinkingObjects`
// Define "stands" as the inverse relationship to Stand.products
let stands = LinkingObjects(fromType: Stand.self, property: "products")
override static func primaryKey() -> String? {
return "id"
}
}
See Realm's docs on Inverse Relationships for more information.