I am building a social media app where multiple users can edit the same CloudKit record at the same time. Should I implement a locking mechanism so that only one user can edit at a time (these edits may conflict with each other), or does CloudKit have a handy built-in way for dealing with this?
If I implement a locking mechanism, my plan would be to add a binary attribute to the editable records--this attribute would have a value of 1 if someone else is editing, and 0 if no one is currently editing. Does this sound like a reasonable way to do this?
Cloudkit has a mechanism for managing this called the change token. It's similar to a timestamp that's updated each time a record is changed. When you try to write, the last change token you have is passed to the server along with your new data. You can set policies that says how the server should handle collisions, such as the last writer always overwrites. Or that the last writer is rejected.
In the latter case, the second writer will receive a NSError. Embedded in the userInfo of that error are three versions of the record: the current on one the server, the version you tried to submit, and the common ancestor. This allows you compare the differences, merge the data as appropriate and re-save. Or you can re-fetch the record (which will update your version of the change token), and then save again.
I would recommend watching the WWDC cloudkit videos. I believe WWDC session 2014 session 231, "Advanced Cloudkit," and WWDC 2015 session 715 "Cloudkit Tips and Tricks," have the most helpful info.
Related
The CloudKit WWDC videos recommend implementing sync like this:
Track local changes
Send changes to the server
Resolve conflicts
Fetch server changes with CKFetchRecordChangesOperation
Apply server changes
Save server change token
I'm following this pattern in my app, but I'm running into a problem with deletion and parent-child relationships.
Let's say we have a list of books that are split up into categories. Every book has to belong to exactly one category.
I start with data like this:
SERVER
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: <empty>
As you can see, the final category is empty. Let's say I have two devices with exact copies of this data.
Now, on Device 1, the user adds a book CloudKit Sync to "Computer Programming":
DEVICE 1
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Computer Programming: "CloudKit Sync"
But on Device 2, the user completely deletes the "Computer Programming" category (it's empty, so this is fine from Device 2's point-of-view):
DEVICE 2
Thrillers: "Look Out!", "Secret Spy"
Non-Fiction: "Sailing the Seas", "Gardening Adventures"
Device 1 syncs first, so it creates a new Book entry with its parent field set to Computer Programming.
But now Device 2 starts its sync process. It applies its changes to the server, so it deletes the CKRecord corresponding to "Computer Programming". This is consistent with Device 2's worldview, where the category is empty and can be deleted.
However, when it deletes this category from the server, this doesn't make sense with respect to the worldview of Device 1 and the server itself. There's now an orphan book called CloudKit Sync that has a dangling pointer to its parent.
If I'm following Apple's recommendations from WWDC, how do I avoid this scenario? Depending on the order of the sync, I can easily arrive at an inconsistent state with an orphaned book and an invalid parent reference.
What I'd like to happen is for the Delete command from Device 2 to return an error telling me I'm going to orphan a book and prevent the action from occurring at all, so I can take some action to fix the situation.
Is that possible? Is there another way to approach this?
Yes, the behavior you want for Device 2 is possible. I see three aspects of cloudkit that will come into play in your scenario. Let's look at those first, then how they might be used in your scenario.
First, assuming that both (or all) devices have subscribed to changes to the appropriate records, each device would be notified that someone else added or removed something. The device receiving the alert would then have the opportunity to decide what to do about it. (remove it from it's local view, replace it on the server, etc)
Second, you can set the behavior for handling conflicts using the savePolicy on the CKModifyRecordOperation. You can specify whether the last change should overwrite older records, throw an error, etc. See https://developer.apple.com/documentation/cloudkit/ckrecordsavepolicy?language=objc for the three options. (I've only used this in the context of two users modifying a common record, but a deletion after another user updated the record should then throw a server record changed error).
Third, assuming you've configured the aforementioned savePolicy, is the server change token itself. I find it easiest to envision the change token as just a last-modified timestamp. "My copy of this record was last modified at 10:42pm" kind of thing. Depending on the overwrite options you've selected in the aforementioned savePolicy, the device will receive an NSError Server Record Changed alerting you that the version on the server is from, say, 10:56pm, and that your local version may no longer be valid.
The userInfo in the resulting NSError includes 3 versions of the record in question: the current version on the server, the version you tried to submit, and the common ancestor version. The guides from Apple say it's up to the developer to decide what how to merge this information. But in theory, you'd be able to diff the changes, decide which you want to keep, and then submit a new operation.
Regarding your specific scenario: Assuming you fully authorize and trust both dev1 and dev2 to delete records, then I would subscribe to creation and deletion events, and set the savePolicy to throw an error when attempting a conflicting change. In this case, Device 1 would add the record and Device 2 would receive the notification of the new record. If Device 2 simply attempts to delete the old record, it should fail with a server record changed error, which you could display to the user as
"Someone else modified this record, do you really want to delete it
(y/n)."
Device 2 would have to refresh the record (and receive the new record change token) before proceeding. After that, if Device 2 still wants to delete the new record, it could, but then Device 1 would be notified of the change via the aforementioned subscription. Device 1 would then download the new record to (or in this case remove the old record from) its local view. The subscription notification could alert user 1:
"Your record Foo was just deleted by Bar"
This will work even if the events happen practically simultaneously, because one of the changes will be applied on the server first and the other device's token will immediately become out-of-date. So, if Device 2 managed to delete the record first, Device 1's attempt to modify the record will fail with server record changed because Device 1's change token is now out of date. Device 1's error handler would have to decide whether to honor the deletion or to proceed with creating a new record based on your business rules. Maybe ask user 1 with something like:
"Computer Programming" has been removed from the server. Do you want to recreate
it?
At this point, user1 can send flame emails demanding other users stop deleting their newly created records, and user2 can demand that people stop recreating the records they just "cleaned up." :)
You could get a lot more complicated, maybe giving device 1 precedence over device 2, such that when device 1 is notified that the record is deleted, then device 1 re-writes the record to the server. If you have multiple users with deletion rights, you could determine an order of precedence and build out the appropriate error/notification handlers. However, this seems excruciating complicated and error prone. Loops that auto respond (create, delete, create, delete, create, delete) could occur. I include it only as a hypothetical example, not a recommendation!
Lastly, as a different example, my app has a different scenario. The records in my case are gaming sessions. All players need read access to the session data, but only the originator is given the option to delete the record altogether. So, you might consider whether you really authorize multiple users to delete shared records or not.
I see the entries in the API documentation for getting "CourseCompletion" objects. But do not see how these are entered in the Learning Environment. Can you explain what these objects are?
CourseCompletion records are essentially meta-data type notes that you can attach to a user/course-offering combination to make a record of a user having "completed" a course on such-and-such a date. The course completion record can also carry an expiry date for when the "completion" becomes out of date or no longer relevant. These features are not heavily used by D2L customers, and are not exposed through the Web UI.
I don't believe there is any automation within the back-end service around the creation or modification of these records (for example, there isn't an event in the system when a course completion record would get created: a client would need to manually create such a record when it wants one to exist).
I'm just starting to learn ASP.NET MVC and I'd like to know how I can retain model objects between subsequent requests to controller action methods?
For example say I'm creating a contact list web app. Users can create, update, rename, and delete contacts in their list. However, I also want users to be able to upload a contact list exported from other programs. Yet I don't want to just automatically add all the contacts in the uploaded file I want to give the user a secondary form where they can pick which uploaded contacts should be actualy added to their list.
So first I have a ContactController.Upload() method which shows an upload form. This submits to ContactController.Upload(HttpPostedFileBase file) which reads the file that was posted into a set of Contact model objects. Then I want to display a list of all the names of the contacts in the list and allow the user to select those that should be added to their contact list. This might be a long list that needs to be split up into multiple pages, and I might also want to allow the user to edit the details of the contacts before they are actually added to their contact list.
Where should I save the model objects between when a user uploads a file and when they finally submit the specific contacts they want? I'd rather not immediately load all the uploaded contacts into the back end database, as the user may end up only selecting a handful to actually add. Then the rest would need to be deleted. Also I would have to account for the case when a user uploads a file, but never actually completes the upload.
From what I understand an instance of a controller only lasts for one request. So should I create a static property on my Contact controller that contains all the latest uploaded contact model object collections? And then have some process that periodically checks the age of these collections and clears out any that are older then some specified expiration time?
A static property on the controller is trouble. First off, it won't work in a web farm and second it you'd have to deal with multiple requests from different users. If you really don't want to use your database you could use the ASP.NET Session.
No, you don't want a static property, as that would be static to all instances of the controller, even for other users.
Instead, you should create a table used to upload the data to. This table would be used as an intermediary between when the user uploads the data, and completes the process. Upon completion, you copy the contacts you want to keep into your permanent table, then delete the temporary data. You can then run a process every so often that purges incomplete data that is older than a specified time limit.
You could also use the HttpContext.Cache, which supports expiration (and sliding expiration) out-of-the box.
Alternatively, and perhaps even better (but more work) you could use cookies and have the user modify the data using javascript in her browser before finally posting it to you.
However, I'd strongly recommend to store the uploaded information in the database instead.
As you pointed out, it might be a lot of data and the user might want to edit it before clicking 'confirm'. What happens if the user's machine (or browser) crashes or she has to leave urgently?
Depending on the way you store the data the data in this scenario will probably be lost. Even if you used the user id as a cache key, a server restart, cache expiration or cache overflow would cause data loss.
The best solution is probably a combination of database and cookie storage where the DB keeps the information in a temporary collection. Every n minutes, or upon pagination, the modified data is sent to the server and updated in the DB.
The problem with storing the data in session or memory is what happens if the user uploads 50k contacts or more. You then have a very large data set in memory to deal with which depending on your platform may effect application performance.
If this is never going to be an issue and the size of the imported contacts list is manageable you can use either the session or cache to store the dataset for further modifications. Just remember to clear it when the user has committed the changes, you don't want a few heavy datasets hanging around in session.
If you store the dataset in session using your application controller then it will be available to all controllers while it is needed.
I'm developing a webapp that allows the editing of records. There is a possibility that two users could be working on the same screen at a time and I want to minimise the damage done, if they both click save.
If User1 requests the page and then makes changes to the Address, Telephone and Contact Details, but before he clicks Save, User2 requests the same page.
User1 then clicks save and the whole model is updated using TryUpdateModel(), if User2 simply appends some detail to the Notes field, when he saves, the TryUpdateModel() method will overwrite the new details User1 saved, with the old details.
I've considered storing the original values for all the model's properties in a hidden form field, and then writing a custom TryUpdateModel to only update the properties that have changed, but this feels a little too like the Viewstate we've all been more than happy to leave behind by moving to MVC.
Is there a pattern for dealing with this problem that I'm not aware of?
How would you handle it?
Update: In answer to the comments below, I'm using Entity Framework.
Anthony
Unless you have any particular requirements for what happens in this case (e.g. lock the record, which of course requires some functionality to undo the lock in the event that the user decides not to make a change) I'd suggest the normal approach is an optimistic lock:
Each update you perform should check that the record hasn't changed in the meantime.
So:
Put an integer "version" property or a guid / rowversion on the record.
Ensure this is contained in a hidden field in the html and is therefore returned with any submit;
When you perform the update, ensure that the (database) record's version/guid/rowversion still matches the value that was in the hidden field [and add 1 to the "version" integer when you do the update if you've decided to go with that manual approach.]
A similar approach is obviously to use a date/time stamp on the record, but don't do that because, to within the accuracy of your system clock, it's flawed.
[I suggest you'll find fuller explanations of the whole approach elsewhere. Certainly if you were to google for information on NHibernate's Version functionality...]
Locking modification of a page while one user is working on it is an option. This is done in some wiki software like dokuwiki. In that case it will usually use some javascript to free the lock after 5-10 minutes of inactivity so others can update it.
Another option might be storing all revisions in a database so when two users submit, both copies are saved and still exist. From there on, all you'd need to do is merge the two.
You usually don't handle this. If two users happen to edit a document at the same time and commit their updates, one of them wins and the other looses.
Resources lockout can be done with stateful desktop applications, but with web applications any lockout scheme you try to implement may only minimize the damage but not prevent it.
Don't try to write an absolutely perfect and secure application. It's already good as it is. Just use it, probably the situation won't come up at all.
If you use LINQ to SQL as your ORM it can handle the issues around changed values using the conflicts collection. However, essentially I'd agree with Mastermind's comment.
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?