Magical Record import (next step) - ios

I've put next step in the title as this is not the same problem as my previous question with almost the exact same title.
I have a Person entity.
Person
--------
name - mappedKeyName: FullName
email - mappedKeyName: EmailAddress
personID - mappedKeyName: Id
--------
photos
And a Photo entity.
Photo
--------
image
createDate - mappedKeyName: Date
photoID - mappedKeyName: Id
--------
owner (type Person) - mappedKeyName: UserId - relatedByAttribute: personID
There are other objects that relate to Person too and the JSON for these comes as so...
{
ObjectId : blah,
Owner : {
Id : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
With this JSON my setup works with the import. Any person records that don't exist (with the Id) are created. And any that do exist are updated.
However, the photos JSON object comes like this...
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
When the objects come down like this the Magical record import stops when it gets to the person import.
The code crashes at...
- (id) MR_relatedValueForRelationship:(NSRelationshipDescription *)relationshipInfo
{
NSString *lookupKey = [self MR_lookupKeyForRelationship:relationshipInfo];
return lookupKey ? [self valueForKeyPath:lookupKey] : nil; // it stops here.
}
The value of lookupKey is #"personID".
Printing out relationshipInfo at the breakpoint gives...
$6 = 0x1fd695e0 (<NSRelationshipDescription: 0x1fd695e0>),
name owner,
isOptional 0,
isTransient 0,
entity Photo,
renamingIdentifier owner,
validation predicates (),
warnings (),
versionHashModifier (null)
userInfo {
mappedKeyName = UserId;
relatedByAttribute = personID;
},
destination entity Person,
inverseRelationship photos,
minCount 1,
maxCount 1,
isOrdered 0,
deleteRule 1
I really have no idea why this isn't working. I don't get any sensible errors to report.

MagicalRecord cannot map the relationship automatically with this JSON format:
{
Id : thisIsThePhotoID,
Date : today,
UserId : 12345asdfg
}
In order for MagicalRecord to map the relationship to a Person object, it would have to be an object in the JSON as well, for example:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg
}
}
This way MagicalRecord knows it's an object and it will do the appropriate lookup in your existing database for the Person record with the above ID and map the relationship.
So there are two issues with this, though. If you cannot change the JSON output you have to create a category class on Photo where you manually map the relationship yourself. I'll get to that after the second issue.
The second issue is that the above JSON format assumes you already have parsed the users and stored the records in your database. If you have not MagicalRecord will create a new Person record with the above ID, but since no other attributes exist on that object (notice the UserId key is the only attribute in the dictionary) it will be fairly empty and not include the name and email address. You can always extend your JSON (if you have that possibility) to include those attributes as well in the Person dictionary inside the Photo dictionary:
{
Id : thisIsThePhotoID,
Date : today,
User : {
UserId : 12345asdfg,
FullName : Oliver,
EmailAddress : oliver#oliver.com
}
}
The JSON payload is quite small so it doesn't hurt to do that if you can. Plus it will only create a new Person record if one doesn't exist in the database already.
And then for the manual mapping. If you cannot change the JSON to the above format you have to manually override the relationship mapping as the JSON is not prepared the way MagicalRecord does mapping.
Create a category class for Photo called Photo+Mapping.h/.m. I like to stick with +Mapping for these. Then the class should be Photo (Mapping) in the header and implementation file and you're good to go.
MagicalRecord has a number of instance methods available to override (see the latter part of this article on MagicalRecord importing written by the author of MagicalRecord), among them are import<;attributeName>;: and import<;relationshipName>;:. There are also a willImport:, didImport: and shouldImport: methods on the class itself which allows you to override any mapping.
For your case you can use import<;relationshipName>;: or shouldImport:. I took these two because one has a bit of a benefit depending on whether you have already mapped all your Person objects and they're available for relationship mapping on the Photo object.
Here are the examples of what you can do (you can choose to combine a few of them if you wish, it doesn't hurt to do so). A note here: ALWAYS use the current NSManagedObjectContext when overriding mapping (easily accessible with MagicalRecord through self.managedObjectContext) otherwise you will end up with context issues.
Be sure to import Person:
#import "Photo+Mapping.h"
#import "Person.h"
// Assuming you only want to import the Photo object if you already have a Person stored this is a great method to tell MagicalRecord whether to continue with importing or not
-(BOOL)shouldImport:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// no Person object exists so don't import the Photo object - again this is up to you since you might want to create the record if not
return NO;
}
// you can set the relationship here (you might as well) or use the importPerson: method below (doing a second lookup, which is unnecessary at this point)
[self setPerson:person];
return YES;
}
// If you use this method you're doing the lookup to check whether a record exist when MagicalRecord is trying to map the Person relationship
-(void)importPerson:(id)data {
Person *person = [Person findFirstByAttribute:data[#"UserId"] value:#"personID" inContext:self.managedObjectContext];
if (!person) {
// if no Person record exists for the associated UserId, you should create one (or not - if you choose not to, it's wise to throw away this Photo object)
person = [Person createInContext:self.managedObjectContext];
[person setPersonID:data[#"UserId"]];
}
// set the relationship
[self setPerson:person];
}
// finally you can also use the following method when MagicalRecord is done mapping and get rid of the Photo object if the Person relationship is nil:
-(void)didImport:(id)data {
if (!self.person) {
[self deleteInContext:self.managedObjectContext];
}
}
Hope this helps! Let me know if you have any questions.

Related

How to fetch relationship entities when performing a core data migration?

I am performing a core data migration. I have this subclass for my migration:
class TagtoSkillMigrationPolicyV1toV2: NSEntityMigrationPolicy {
override func createDestinationInstances( forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// 1 - create destination instance of skill and skillmetadata
let skillEntityDescription = NSEntityDescription.entity( forEntityName: "Skill",in: manager.destinationContext)
let newSkill = Skill(entity: skillEntityDescription!, insertInto: manager.destinationContext)
let skillMetaDataDescription = NSEntityDescription.entity(forEntityName: "SkillMetaData", in: manager.destinationContext)
newSkill.metaData = SkillMetaData(entity: skillMetaDataDescription!, insertInto: manager.destinationContext)
var posts = sInstance.value(forKey: "posts")
posts.sort {
($0.timeStamp! as Date) < ($1.timeStamp! as Date)
}
if let mostRecentThumbnail = posts.first?.postImage {
let thumbnail = Utils.makeThusmbnail(url: URL(string: mostRecentThumbnail)!, for: 150.0)
newSkill.metaData?.thumbnail = thumbnail?.pngData()
}
if let name = sInstance.value(forKey: "name"){
newSkill.setValue(name, forKey: "name")
}
manager.associate(sourceInstance: sInstance, withDestinationInstance: newSkill, for: mapping)
}
}
This custom migration is from my old Tag entity to my new Skill entity. For each Tag, it creates a skill entity and a skillmetaData entity that it attaches to it. There are no entries in the .xcmappingmodel file, as I am doing everything manually - please let me know if I still need entries there.
Right now, when it runs, it gives a signal SIGABRT:
Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
2020-04-14 15:55:40.108927-0700 SweatNetOffline[28046:3645007] Could not cast value of type '_NSFaultingMutableOrderedSet' (0x7fff87c44e80) to 'NSArray' (0x7fff87c51fb0).
When I set a breakpoint and inspect sInstance.value(forKey: "posts"), I get
▿ Optional<Any>
- some : Relationship 'posts' fault on managed object (0x6000010dcf00) <NSManagedObject: 0x6000010dcf00> (entity: Tag; id: 0xdf4b05c2e82b2c4e <x-coredata://A6586C18-60C3-40E7-B469-293A50EB5728/Tag/p1>; data: {
latestUpdate = "2011-03-13 00:17:25 +0000";
name = flowers;
posts = "<relationship fault: 0x600002607f40 'posts'>";
uuid = "4509C1B3-7D0F-4BBD-AA0B-7C7BC848DA80";
})
so its not entirely loading those posts - its giving that fault there.
The goal is to get the thumbnail from the latest of those posts. How can I access this relationship item?
I found a way to do it using
let lastThumbnail = sInstance.mutableOrderedSetValue(forKeyPath: "posts.postImage").lastObject
Reading this helped a lot. My understanding is at this point in the migration, we are dealing with NSManagedObjects and never the actual Model which would have attributes like "posts" that it usually does in core data.
In Objective-C especially and swift to some extent, NSObjects are accessed with key-value coding. They are accessed indirectly - meaning instead of reading the object itself, you are reading what the NSMutableOrderedSet wants to show you for that key - so it can do stuff to it before presenting it. Thus you can't for instance, fetch just "forKey: posts" and then expect posts to have the attributes your post usually does. Anyone is welcome to flesh out this explanation if they know more :).
One thing I was trying to do is to fetch by that key-path in a sortable way - so that I can find the post that has the most recent timestamp and then use that associated thumbnail. Instead I am using .lastObject which works because this is an NSMutableOrderedSet and not an NSMutableSet because my original model specified this relationship to be an ordered relationship. This means that it will pick the last inserted item which is ok for me for now. Still interested in a way to filter that result though if anyone has advice.

Will an array of a superclass contain subclasses as well?

I created a class Person. Person contain properties like name and email. Both are from type String.
Beside the Person class, I have a subclass Student that inherited from superclass Person. Subclass Student contain properties like student number (String) and isGraduated (Boolean).
I have an empty array of persons from the class Person, like:
var persons: [Person]()
After I created both Person and Student objects inside the array persons, I read them out using a UITableView. Both models will be print in the cells. But when I want to check the value of isGraduated from the selected row, auto-completion doesn't give me the value of the property: persons.isGraduated.
The first thought of this problem is, will my persons array contain also the subclass Student? My second thought would be, that I think I should not check the value isGraduated inside the TableView. My wish of this function is that it will do something, like call the native camera if the person is graduated.
Looking forward to the solution.
Tree options here
A: Change the array type to Student
var students: [Student]()
B: Cast each element from the persons array to a Student. Ideally inside a for each person.
guard let student = person as? Student else {
return // or continue if inside a for loop
}
Then u will have access to that student variable.
C: Or if they might be students and persons at the same time in the array then
for person in persons {
switch person{
case let student as Student: //student case
//do something
default: //person case
//do something
}
}
If you want to check what is the type of your person : Person or Student you can use is keyword like this :
if persons[0] is Person {
//if a person
} else if persons[0] is Student {
//if a student
}
If you want to use a function member for your specific type, you need to downcast the type by using the keyword as like this :
if let person = persons[0] as? Person {
//person is now a Person type
} else if let student = persons[0] as? Student {
//student is now a Student type
}

Realm query Object property field by its property

I'm developing an application for iOS using Swift and chose Realm as a database solution for it. I asked one question about Realm and now I have another.
Suppose we have a schema like this:
class Person: Object {
dynamic var id: String = NSUUID().UUIDString
dynamic var name: String?
dynamic var cars: Car?
class Car: Object {
dynamic var name: String?
I have one class (Person) that contains any number of objects of another class (Car). Car that are "linked" with the Person has some properties in context of that Person (and they can be different for same Car for different Persons or for two similar Cars for one Person). Using List<...>() we can not store such properties for each Item, am I right?
If we use Car only for one Person and only once we can create another class that includes only additional properties for Cars and links them with ID of Person plus ID of Car. But it does't work if we have two similar Cars with different additional properties.
So, how I see the solution. Have one table (class) stores ID of Person, ID of one Car and additional properties for this Car. For another Car for the same Person it has the same Person ID, Car ID (same or not) and another additional properties for this instance of a Car.
There is a problem (and a question that I mean). Having that structure I want to query all Cars from that table with their additional properties that have Person ID equals to some_id. How should I do this? Or maybe what another structure (maybe using List<...>) I should have to achieve such kind of behavior?
What is FastList exactly ?
If you want Items to have a property of Lists collection.
You have to redefine your Realm model. something like this.
class Car:Object{
dynamic var createDate: NSDate = NSDate()
}
class Person:Object{
let cars = List<Car>()
}
and query by predicate like this
let realm = Realm()
var ownedCarsFilterByDate = realm.objects(Person).filter("ANY cars.createDate = '\(date)'")
Edited to updated question
Your solution is to create table class, which has 'Person' , 'Car' and 'Context Attribute'.
Your model would be like this
class PersonAndCarRelation:Object{
dynamic var person: Person?
dynamic var car: Car?
dynamic var contextAttribute = ""
}
and you can query all cars associated with person
let personID = "123456789"
let personAndCarArray = realm.objects(PersonAndCarRelation).filter("person.id == \(personID)")
for personAndCar in personAndCarArray{
let personName = personAndCar.person.name
let carName = personAndCar.car.name
let context = personAndCar.contextAttribute
println("I am \(personName). I have a \(carName) with \(context)")
}

Convert ABRecordID to NSNumber always -1

I am trying to save an ABRecordID of an existing contact from the Address Book into Core Data. I have an Int32 defined in my model, but in order to save my object there I need to convert the ABRecordID (which is an Int32) into an NSNumber
Here is my code that converts it:
func peoplePickerNavigationController(peoplePicker: ABPeoplePickerNavigationController!, didSelectPerson person: ABRecord!, property: ABPropertyID, identifier: ABMultiValueIdentifier) {
let multiValue: ABMultiValueRef = ABRecordCopyValue(person, property).takeRetainedValue()
let index = ABMultiValueGetIndexForIdentifier(multiValue, identifier)
let pickedVal = ABMultiValueCopyValueAtIndex(multiValue, index).takeRetainedValue() as! String
let theRecord = recordIdFromPersonRecord(person)
println(ABRecordGetRecordID(person!))
func recordIdFromPersonRecord (personRecord: ABRecord) -> NSNumber {
let recordId = ABRecordGetRecordID(personRecord)
println(personRecord)
return (NSNumber(int: recordId))
}
This code always prints -1 in the console, no matter what record I select in my address book. I'm a newbie programmer and I'm totally stuck.
Thanks for your help!
EDIT:
Commenter below indicated that this is a value reserved for records that do not exist in the address book database...
That is confusing. I am just browsing the AB to get the name value and the ID out of existing records... how can existing records not have a record saved to the address book database? Here is what the definition from the docs is: Records with this ID have not been saved to the Address Book database. Am I misunderstanding?
In iOS 8, you can browse the contacts without it ever requesting permission to the address book (because you might, for example, allow the user to perform the default action with ever returning actual data to the app). But to actually get the particulars on a contact, you have to explicitly request permission to do so.
It would appear that you must have used AddressBookUI.framework to present the contact UI, but never asked for permission to access the particulars about the contact.

Data disappearing from the database?

I am running a grails application and I am receiving the weirdest error I've probably ever encountered. One "field" in a model got data that just disappears for no reason.
I have two Model or a Domain class in my project with the following set up:
class Insertion {
String title
Date insertDate
static hasMany = Dataholder
static constraints = {
title(unique: true)
}
}
class Dataholder {
String product
int somenumber
int somenumber2
int somenumber3
Date startDate
Date endDate
List<String> somedatalist
Insertion insertions
static belongsTo = Insertion
static constraints = {
}
}
The "insertion" class is representing every time a user might input a bunch of dataholders. The dataholder represents all the data for that specific product. Important to know is that the data that disappears is contained in the Dataholder model and the ONLY data that disappears is the somedatalist.
This is the magic which is completely confusing, when I insert the data and saves it. It all goes well:
if (!errors) {
dataholderValidator.each {
it.insertion = insertion
it.save()
}
def results = Dataholder.findAllByInsertion(insertion)
I do some validating and apply data to every Dataholder and then if everything goes well, if(!errors) I add the insertion to each object. After that is done I save each objec, saving the data to the database. You may think it's going wrong here but just wait and be amazed.
After saving I get all the Dataholders from the database (since I want to be sure that the data was saved before printing it out to the user) by using the insertion. This is where the strange part begin, what I get back is the correct data:
results.each {
it.somedatalist.each { it2 ->
if(!weekdays.contains(it2))
weekdays.add(it2)
}
}
Populate an array with all the unique items from the datalist. Then printing it out to the view and voila:
Now, we just wait for the users confirm of all the data in the view and when he or she is clicking on a confirm button the insertion title is sent with post to function which would retrieve the data and to my surprise the somedatalist is null.
This is the functionality that retrieves the data:
def result = Insertion.findByTitle(insertionTitle)
def results = Dataholder.findAllByInsertions(result)
When putting a breaking point after results I can for sure confirm that every Dataholder contains the correct the right data except that somedatalist which is equal to null.
I've tried to get the data above by using the Insertion Title instead of just using the object and it works well. I can't understand why the data is populated in the database in one second and how something can just disappear?
Test:
void testSaveDataholder() {
Insertions insertion = new Insertion(title: 'adadad', insertDate: new Date())
insertion.save()
assert Insertion.all.size() == 1
Dataholder ed = new Dataholder(product: 'abc123', somenumber: 123, somenumber2: 13, startDate: new Date(), endDate: new Date(), somedatalist: ['TI'], insertions: insertion)
ed.save()
assert Dataholder.all.size() == 1
assert Dataholder.all.first().somedatalist.size() == 1
assert Dataholder.all.first().insertions.title == 'adadad'
assert Insertion.findAllByTitle('adadad').size() == 1
assert Dataholder.findAllByInsertions(Insertion.all.first()).size() == 1
}
This test all returns true, I am using Grails 2.1.
EDIT: I am using the in-memory database with "update" as configuration. So I can't really view the data. But it should be there.
Please help me with your sage advice and better wisdom.
It just has come to my mind. Persisting a collection of objects into single column breaks the 1st normal form, so it is not the correct way to do it. I have immediately googled an issue in JIRA:
http://jira.grails.org/browse/GRAILS-1023
The correct way is to create a new domain class with single String attibute and use standard one-to-many relation.

Resources