My iOS app downloads record from a 3rd party database, and stores them locally using CoreData. The user is able to edit some aspects of the stored records, eg user notes can be added in addition to notes from the database.
Occasionally, the database gets updated, and I provide a refresh function. Instead of checking what parts of the entries are different, I just brute-force remove the old one and replace it with a new one.
However, this also removes the user notes. I tried saving them before the refresh, and re-adding them after inserting the new entry, but once the original entry gets deleted, the user note is also deleted because of the "Cascade" delete rule. If I set the delete rule to "No Action" for notes, then all notes will not be deleted.
So I was thinking, is it possible to temporarily change the delete rule of the user note while updating so that it doesn't get deleted with the old entry?
Or maybe my approach is completely wrong, and there are better ways to handle this?
UPDATE: I have created a follow up question here: Change relationship of NSManagedObject to different context
You are not allowed to change model after it was instantiated, except versioning.
The way I think, you should create new entity, say, CustomNote and store some unique identifier to original "record". Then just retrieve this notes by id.
Although, it may be some more advanced approach with relationships, this is the simplest.
Related
After making a rough version of my app, I am now looking to make the finalised version which is better and has many improvements. This involves changing the attributes in the entity holding the timers.
Having put the app on TestFlight already, some of the downloads will have data stored already. I am going to delete the current entity and make a new one. Will this cause any problems if data is already stored for the attributes within the entity that is going to be deleted?
IMPORTANT: I do not want to keep any of the data in the entity to delete and I want to delete it all, so when users update all their data will be gone (this is fine as I distributed it only to people I know e.g. family/friends), who have given some feedback.
Any help is much appreciated. Thanks in advance.
CoreData is very pedantic. Once a store has been created based on a data model, you cannot use it with a different model unless you do some kind of migration. Different in this case means almost any change to the model. Certainly changing the attributes for an entity, or adding a new entity, will be sufficient for CoreData to throw an exception if you try to open the existing store with the new model.
If you want to preserve the data in the existing store, you would have to do a migration, but since you do not want to preserve that data, it will be easier to delete the store when the new version of your app is first run, and let CoreData create a new store using the new model.
I want to ask for a safe way to clear subEntities in coredata.
I have my a many-to-many relationship like this: Product *<->* Product. Therefore, I've got to create a subEntity to hold some special values between (sortPosition, groupName.....).
So it's like this: Product *<->1 ProductSubEntity 1<->*Product.
When I download products from server's API, the easiest way to update correctly correspond to the server's result is:
Remove all child relationship ([self removeProductSubEntities:self.subEntities]).
Add sub from server's result.
Result: There'd be a lot of subEntity in coredata (which won't hold relationship to any product), and this might take storage/memory/cpu when CRUD (I think?). But I can't actual delete the subEntity (in case it's being hold reference to as an viewController's Object somewhere, and it might cause crash: access to a deleted object).
QUESTION:
How can I clear those sub entities (might occur sometimes) if:
No relationship to any product.
No actual reference from anywhere (any viewControllers or objects)???
P/S: I'm thinking of implement a batch delete when terminate app. Could that be consider a safe solution?
I don't consider this to be a datastore issue, rather a UI update issue. You should delete the objects from the datastore when you don't need them any more and you should update the UI accordingly.
1 thing you didn't mention is re-use. It's possible that your download may be an update to an existing item, which you could find and update, then life is easy all round. Arguably everything below still applies in this case though as your UI might not update to reflect changes and you may need to refresh the managed object.
For the UI update it's generally wise to observe the datastore for changes, usually with an NSFetchedResultsController. If you're doing this then your UI would automatically update itself with the changes.
If you're explicitly passing entity instances around then you should have some way to trigger an update explicitly, and exactly how that works depends on your UI. Generally speaking you'd be doing something like posting a UINotification to tell the system that the datastore changed and they need to re-validate their data objects. For the UI you shouldn't be showing now-dead objects to the user, and in your question where you talk about not deleting to avoid crashes, it's probably worse to allow the user to update invalid objects and just quietly not telling them that their updates won't be saved. When the notification is received you may want to pop a (some) controller(s) off the stack, or re-query the datastore for the new data to be displayed.
If for some reason you don't want to do the above, then yes, you can query for all of the entities with a nil relationship and then batch delete them. This should be done on a background thread just like data loading and I'd recommend doing it on app load instead of close (because you won't have so many view controllers loaded and the ones that are should all have only valid references now...).
How does .DeleteSelf really work? Docs says:
When the reference object’s action is set to
CKReferenceActionDeleteSelf, the target of the reference—that is, the
record stored in the reference’s recordID property—becomes the owner
of the source record. Deleting the target (owner) record deletes all
its source records.
but my impression is that deleting a target will not always delete source. And it is quite annoying when it remains in the container, client downloads it, and expect that the reference point to somewhere, but target does not exist when building up slice of the server data store on client?
How do you treat this case? You ignore that sort of records? Or periodically you look up the CloudKit storage, searching for corrupt records to delete them?
Or instead of deleting a record is it better to set an attribute that it is in a deleted state, but keep it in the database?
I just struggled with this one for a while and I thought I would share my findings...
It is fundamentally a permission issue. The cascading delete will only work if the user deleting the records has 'write' permissions to all the records needing to be deleted.
So in the CloudKit Dashboard, the cascading delete will only work for the records created with the developer's iCloud account.
If you need to delete records that don't belong to the user deleting them, you can add 'write' permissions for a Record Type under Security.
If you are deleting via CloudKit Dashboard you have to wait before switching record types to check the other end of the reference. More than likely you switched before the delete actually happened. You can use Safari's Web Inspector on the Network tab to check when the delete has actually finished. It takes a very long time to delete multiple records.
I've got an iOS app which uses Core Data (SQLite on the backend). It only has one entity, 'Item'. There is a SQLite file bundled with the app, with hundreds of items pre-added, so when the user downloads the app from the App Store it already has the data.
The only entity has a BOOL favorite attribute which the user can alter, used -of course- to check if an item is among the user favorite items.
I'm planning to publish an update of the app with more items pre-built in the app bundle (a new SQLite file), but I want to keep the user favorites. As well, in this version my Core Data model will suffer a few modifications (I need some new properties in the 'Item' entity). The new set of items is a superset of the old items (an item in the old version of the app shall be in the new version, always).
I've been struggling with this a lot and I can't find a solution to this. I'm able to upgrade the data model introducing new properties into my entity while keeping the user favorites (performing a so-called lightweight migration, but then I'm not able to merge old and new items. On the other hand, I'm able to get the new pre-added items, but then the favorite-related data is discarded.
Any hint? Thank you all in advance
I finally managed to solve the problem.
I've got two NSPersistentStoreCoordinators, two NSManagedObjectContexts and two NSManagedObjectModels in my app delegate: one set to use in the application (the updated one) and another set pointing to the old store. In my app delegate didFinishLaunchingWithOptions: method I load all the user's favorites from the old store and save them into the new one. That's the only point in the app where I touch the old store.
Thank you all anyway!
I would suggest creating a second database with your new stuff in there, but without favorites. Then you pull favorites from your old database and insert them into your new one. Remove the old database and replace with the new one. That seems like the most straight-forward solution. There may be functionality built into Core Data for these situations, but chances are this is easier.
I am developing a gallery which allows users to post photos, comments, vote and do many other tasks.
Now I think that it is correct to allow users to unsubscribe and remove all their data if they want to. However it is difficult to allow such a thing because you run the risk to break your application (e.g. what should I do when a comment has many replies? what should I do with pages that have many revisions by different users?).
Photos can be easily removed, but for other data (i.e. comments, revisions...) I thought that there are three possibilities:
assign it to the admin
assign it to a user called "removed-user"
mantain the current associations (i.e. the user ID) and only rename user's data (e.g. assign a new username such as "removed-user-24" and a non-existent e-mail such as "noreply-removed-user-24#mysite.com"
What are the best practices to follow when we allow users to remove their accounts? How do you implement them (particularly in Rails)?
I've typically solved this type of problem by having an active flag on user, and simply setting active to false when the user is deleted. That way I maintain referential integrity throughout the system even if a user is "deleted". In the business layer I always validate a user is active before allowing them to perform operations. I also filter inactive users when retrieving data.
The usual thing to do is instead of deleting them from a database, add a boolean flag field and have it be true for valid users and false for invalid users. You will have to add code to filter on the flag. You should also remove all relevant data from the user that you can. The primary purpose of this flag is to keep the links intact. It is a variant of the renaming the user's data, but the flag will be easier to check.
Ideally in a system you would not want to "hard delete" data. The best way I know of and that we have implemented in past is "soft delete". Maintain a status column in all your data tables which ideally refers to the fact whether the row is active or not. Any row when created is "Active" by default; however as entries are deleted; they are made inactive.
All select queries which display data on screen filter results for only "active records". This way you get following advantages:
1. Data Recovery is possible.
2. You can have a scheduled task on database level, which can take care of hard deletes of once in a way; if really needed. (Like a SQL procedure or something)
3. You can have an admin screen to be able to decide which accounts, entries etc you'd really want to mark for deletion
4. A temperory disabling of account can also be implemented with same solution.
In prod environments where I have worked on, a hard delete is a strict No-No. Infact audits are maintained for deletes also. But if application is really small; it'd be upto user.
I would still suggest a "virtual delete" or a "soft delete" with periodic cleanup on db level; which will be faster efficient and optimized way of cleaning up.
I generally don't like to delete anything and instead opt to mark records as deleted/unpublished using states (with AASM i.e. acts as state machine).
I prefer states and events to just using flags as you can use events to update attributes and send emails etc. in one foul swoop. Then check states to decide what to do later on.
HTH.
I would recommend putting in a delete date field that contains the date/time the user unsubscribed - not only to the user record, but to all information related to that user. The app should check the field prior to displaying anything. You can then run a hard delete for all records 30 days (your choice of time) after the delete date. This will allow the information not to be shown (you will probably need to update the app in a few places), time to allow the user to re-subscribe (accidental or rethinking) and a scheduled process to delete old data. I would remove ALL information about the member and any related comments about the member or their prior published data (photos, etc.)
I am sure it changing lot since update with Data Protection and GDPR, etc.
the reason I found this page as I was looking for advice because of new Apply policy on account deletion requirements extended https://developer.apple.com/news/?id=i71db0mv
We are using Ruby on Rails right now. Your answers seem a little outdated? or not or still useful right now
I was thinking something like that
create a new table “old_user_table” with old user_id , First name, Second name, email, and booking slug.
It will allow keep all users who did previous booking. And deleted their user ID in the app. We need to keep all records for booking for audit purpose in the last 5 years in the app.
the user setup with this app, the user but never booking, then the user will not transfer to “old_user_table” cos the user never booking.
Does it make sense? something like that?