Understanding equality in RealmSwift - swift

I have some items which I fetch from Realm:
let items = realm.objects(ShoppingListItem.self)
print("\(items.count) items") // 10 items
Each item has a subdepartment, and each subdepartment has a department:
let departments = items.flatMap({ $0.product?.subdepartment?.department })
print("\(departments.count) departments") // 10 departments
My goal is to find the unique Department objects from this list. The expected result is 4. My standard approach here is to use Set:
let uniqueDepartments1 = Set(departments)
print("\(uniqueDepartments1.count) unique departments via Set") // 9 unique departments via Set - but changes every time!?
I figure there must be something I'm missing related to the way Realm treats equality. But to check, I also attempt to get the unique departments via enumeration:
var uniqueDepartments2 = [Department]()
for department in departments {
if uniqueDepartments2.contains(department) {
continue
}
uniqueDepartments2.append(department)
}
print("\(uniqueDepartments2.count) unique departments via enumeration") // 4 unique departments via enumeration
This is indeed the expected result.
Why doesn't Set work here as I expected it to? And why is the count changing each time I run it?
Edit 2/27
Here are the models in play:
class ShoppingListItem: Object {
dynamic var product: Product?
convenience init(ingredient: Ingredient) {
self.init()
self.product = ingredient.product
}
}
class Product: Object {
dynamic var productID, subdepartmentID, title: String?
dynamic var subdepartment: Subdepartment?
}
class Department: Object {
dynamic var departmentID, title: String?
}
class Subdepartment: Object {
dynamic var subdepartmentID, departmentID, title: String?
dynamic var department: Department?
}

In short: in order for an Object subclass to be correctly hashable you must currently declare a property as the primary key.
Set is built on top of a hash table. This means it checks for the existence of a specific object by first computing the hash value of the object, mapping that hash value to a bucket within the hash table, and then checking each entry in that bucket for equality with the specified object.
The nature of this implementation means that for Set to work correctly on objects of a given type, both the hashValue property and == operator must obey specific rules. In particular, any two objects for which == returns true must return the same value from their hashValue property (the reverse is not required; it's completely valid for two unequal objects to have the same hashValue). Realm's implementation's of hashValue and == don't currently meet this criteria unless your class declares a property as the primary key. When no primary key is declared, Object's default hashValue calculation defers to -[NSObject hash], which simply returns the object's address in memory. Since Object.== allows two objects with different addresses to compare equal, this violates the relationship between hashValue and == I outlined above, leading to incorrect behavior when used with Set or as the key of a Dictionary. I've filed a bug against Realm requesting that the behavior of == be fixed to be compatible with the value returned by the hashValue property.

Related

Why Swift Set fucntion Why firstIndex(of: ) Apply?

I understand Set Collection is key-value and Keys are not duplicated.
In the example below, I thought fruits were the key.
however .firstIndex(of: ) is exist why?
So can a second index exist?
Am I misunderstanding the set?
var favoriteFruits: Set = ["Banana", "Apple", "Orange", "Orange"]
favoriteFruits.insert("WaterMelon")
print(favoriteFruits)
favoriteFruits.remove("Banana")
print(favoriteFruits)
if favoriteFruits.contains("Tomato") {
print("Tomato is my favorite Fruits")
} else {
print("Tomato is not my favorite Fruits")
}
print(favoriteFruits.firstIndex(of: "Orange"))
It would be of great help if you leave a comment.
If you check firstIndexOf method's explanition you will see a defition :
Available when Element conforms to Equatable.
Sets conforms to Equatable.You can also test this while checking the equality of the two sets.Below code doesn't give any error.
if set1 == set2{
//....
}
You ask .firstIndex(of: ) is exist why?
So there is no obstacle for the collection type of Set to use the firstIndexOf method
If you ask why set is equatable.Set uses the hash table to check the elements inside. So the Set must be hashable.An object that conforms to Hashable must also be Equatable too
This is a consequence of Set<String> conforming to Collection<String>. The Collection protocol requires this method:
func firstIndex(of element: Self.Element) -> Self.Index?
The associated types Element and Index are also required by the Collection protocol. Even if Set is not index-based, Set does declare a nested struct called Index. See its implementation here.
It is true that there can be no "second" index for a particular element, since elements in a set are unique. firstIndex(of:) and lastIndex(of:) for any element for a set would return the same thing.
However, the Collection protocol does not care. After all, functions that can take any Collection does not know what kind of collection they are working with, e.g.
func someGenericFunction<C: Collection>(collection: C) {
// ...
}
so they need to specify whether they want the first, last, or the nth index of a particular element, so that their code works for all kinds of collections. If Set did not have these methods, then Set is not a Collection, and cannot be passed to these functions.

Advantages of using Virtuals in Mongoose

i would like to kmow the advantages of using virtuals in mongoose while establishing relationship. Will it result in faster retrival of information from DB
Virtuals are additional fields for a given model. Their values can be set manually or automatically with defined functionality. A common virtual property is the full name of a person, composed of user’s first and last name.
virtual properties don’t get persisted in the database. They only exist logically and are not written to the document’s collection.
Example
Mongoose Schema
The user schema has two properties indicating the user’s first and last name: first and last.
// define user schema
var userSchema = new Schema({
first: String,
last: String
});
// compile our model
var User = mongoose.model('User', userSchema);
// create a document
var mentalist = new User({
first: 'Patrick',
last: 'Jane'
});
Assume we want to get the full name of a mentalist, we can do this manually appending the first to last property:
console.log(mentalist.first + ' ' + mentalist.last); // Patrick Jane
Define a Virtual Property
Actually, there is a better way of getting the full name of a user: virtual fields. With virtuals, you benefit of writing the name concatenation mess only once.
Mongoose splits the definiton of virtual fields into GET and SET methods.
Get Method
The virtuals get method is a function returning a the virtual value. You can do complex processing or just concatenate single document field values.
userSchema.virtual('fullname').get(function() {
return this.first + ' ' + this.last;
});
The code example above just concatenates the first and last property values. With that, the virtual fullname property now will print the same output as above:
console.log(mentalist.fullname); // Patrick Jane
Set Method
setter methods are useful to split strings or do other operations. Define a virtual setter by passing a proper function and execute your desired processing. The example below splits the passed name variable at any whitespace.
userSchema.virtual('fullname').set(function (name) {
var split = name.split(' ');
this.first = split[0];
this.last = split[1];
});
The first part of name is assigned to the first and the second part to the last property. This set method will override the previous model values and assign the ones we pass as fullname property.
var humor = new User({
first: '',
last: ''
});
humor.fullname = 'Kimball Cho';
console.log(humor.first); // Kimball
console.log(humor.last); // Cho
Queries and Field Selection
Virtuals are NOT available for document queries or field selection. Only non-virtual properties work for queries and field selections.
As you see, virtual properties aren’t static model properties. They
are additional model functions returning values based on the default
schema fields.

Core Data Cross Reference Table (Swift 4)

Imagine I have an app with two Core Data entities: ingredients and cookies.
Ingredients have a title, a type, an image, and a few other properties.
Cookies have a title, a type, an image and a few other properties.
A cookie is comprised of several ingredients. Ingredients can be used in multiple different cookies. The Cookie entity and the Ingredient entity have an inverse relationship which allows me to see which cookies have which ingredients and which ingredients are used in which cookies.
All of this is working fine.
Here is my problem:
In addition to several other ingredients of varying quantities, chocolate chip cookies require one egg but oatmeal raisin cookies require two eggs.
If I was designing a MySQL database to model all this, I'd have a Cookie table (similar to my Cookie Core Data entity), an Ingredient table (similar to my Ingredient Core Data entity), and a cross-reference table CookieIngredient, which would include a cookieID, an ingredientID, and a quantity.
I've tried to do this in Core Data, but I can't seem to get the entity for the cross-reference table created correctly. I create an Entity named CookieIngredient with Integer 16 fields of cookieID, ingredientID, and quantity, but then when I attempt to create an NSManagedObject Subclass, the class which is created -- unlike the other two classes -- displays numerous errors telling me "extensions cannot contain stored properties", which doesn't seem to have anything to do with anything else in the universe.
None of the many "recipe" Swift tutorials I've found online use Core Data, and the ones that do don't use a cross-reference table.
Any suggestions appreciated. (Am I doing this the wrong way?)
do{
let fetchRequest : NSFetchRequest<Ingredient> = Ingredient.createFetchRequest()
fetchRequest.predicate = NSPredicate(format: "title == %#", title)
let fetchedResults = try the_context.fetch(fetchRequest)
if let anIngredient = fetchedResults.first {
let the_recipe = CookieIngredient(context: the_context)
the_recipe.quantity = amount
the_recipe.unit = unit
the_recipe.mutableSetValue(forKey: "recipeIngredient").add(anIngredient)
the_recipe.mutableSetValue(forKey: "recipeCookie").add(Cookie)
}
}
catch{
print( "unable to update ingredients" )
}
Assuming I have a Cookie object, I should be able to do this. But the error I'm getting is:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSManagedObjects of entity 'CookieIngredient' do not support -mutableSetValueForKey: for the property 'recipeIngredient''
Is mutableSetValue not the correct way to create the relationship?
Note: mutableSetValue was not the correct way to do it.
extension CookieIngredient {
#nonobjc public class func createfetchRequest() -> NSFetchRequest<CookieIngredient> {
return NSFetchRequest<CookieIngredient>(entityName: "CookieIngredient")
}
#NSManaged public var quantity: String
#NSManaged public var unit: String
#NSManaged public var recipeCookie: Cookie
#NSManaged public var recipeIngredient: Ingredient
}

f# Insert on MongoDB using Records

I've been trying for a while to insert on MongoDB using only records with no success.
My problem is that I want to create a simple insert function which I send a generic type and it is inserted into the database.
Like so.
let insert (value: 'a) =
let collection = MongoClient().GetDatabase("db").GetCollection<'a> "col"
collection.InsertOne value
From this function, I tried inserting the following records.
// Error that it can't set the Id
type t1 = {
Id: ObjectId
Text: string
}
// Creates the record perfectly but doesn't generate a new Id
type t2 = {
Id: string
Text: string
}
// Creates the record and autogenerates the Id but doesn't insert the Text, and there are two Ids (_id, Id#)
type t3 = {
mutable Id: ObjectId
Text: string
}
// Creates the record and autogenerates the Id but for every property it generates two on MongoDB (_id, Id#, Text, Text#)
type t4 = {
mutable Id: ObjectId
mutable Text: string
}
So does anyone can think of a solution for this or am I stuck having to use a class.
// Works!!!
type t5() =
member val Id = ObjectId.Empty with get, set
member val Name = "" with get, set
Also, does anyone has any Idea of why when the C# MongoDB library translates the mutable he gets the property with the # at the end?
I would be fine with having all my properties set as mutable, although this wouldn't be my first choice, having he create multiple properties on the DB is quite bad.
You could try annotating your records with CLIMutable (and no mutable fields).
The #s end up in the DB because MongoDB using reflection and F# implementing mutable with backing fields fieldName#

EF4 inheritance and Stored procedures

I implemented inheritance with a discriminator field so all my records are in the same table. My basetype is Person (also the name of the table) and Driver and Passenger inherit from it. I receive instances of the correct type (Driver and Passenger) when I perform a query on the object context to Person. example:
var q = from d in ctx.Person
select d;
But I also create a function that calls a stored procedure and mapped the output of the function to the type Person. But now I get a list of Person and not Drivers or Passengers when I execute this method.
Anybody an idea how to solve this or is this a bug in EF4?
AFAIK, you can't use discriminator mapping (e.g TPH) when dealing with stored procedure mappings.
The stored procedure must be mapped to a complex type or custom entity (e.g POCO), the mapping cannot be conditional.
What you could do is map it to a regular POCO, but then project that result set into the relevant derived type (manual discrimination).
E.g:
public ICollection<Person> GetPeople()
{
var results = ExecuteFunction<Person>(); // result is ObjectResult<Person>
ICollection<Person> people = new List<Person>();
foreach (var result in results)
{
if (result.FieldWhichIsYourDiscriminator == discriminatorForDriver)
{
people.Add((Driver)result);
}
// other discriminators
}
}
If your always expecting a collection of one type (e.g only Drivers), then you wouldn't need the foreach loop, you could just add the range. The above is in case you are expecting a mixed bag of different people types.
Would be interested to see other answers, and if there is a better way though - but the above should work.