Skip to content
This repository was archived by the owner on Mar 23, 2021. It is now read-only.

Conversation

jdjennin
Copy link

I created an NSRailsManagedObject object by copying/pasting the code from NSRailsModel. NSRailsManagedObject, however, inherits from NSManagedObject rather than NSObject. When the CRUD and fetch operations succeed, the corresponding objects in Core Data (uniquely identified by their primary key attribute) are updated, and the Core Data managed object context is saved.

The primary key attribute is assumed to be classname_id in the Core Data model. So for a class named "User", the primary key would be "user_id".

Setup requires that the entities which should inherit from NSRailsManagedObject have their class set in the right pane of Xcode's data model editor (in the *.xcdatamodeld file) to the entity name. Otherwise, Core Data assumes that the entity's class inherits from NSManagedObject directly, and the application will crash.

remoteCreate will create the object locally and send a request to the server to create the object.

remoteUpdate will attempt to update the server's record, and if the request succeeds, the object will be saved locally. Otherwise, changes will be rolled back if you have implemented an undo manager for the core data managed object context.

remoteDestroy will send a request to the server to destroy the remote object. If successful, the local object will be deleted from Core Data.

remoteFetch will update the attributes of the object in Core Data and save it, if successful.

@dingbat
Copy link
Owner

dingbat commented May 26, 2012

This looks fantastic. Thanks so much, this is really appreciated. Of course there've been a bunch of changes since your fork, so it may take a bit of time to merge.

In the meantime, do you think you could start adding some tests?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - what was the reason for this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the toy project I was using to implement this, the camelize function was turning location_id into locationId, if I remember correctly. I suppose I could have adjusted the property names on my iOS models. The shop I work at doesn't really standardize its naming conventions. Some of us use camel case and others use snake case. Most of us snake it. Feel free to change it back, though!

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. And by the way, what's wrong with using the NSRails-given remoteID property as the primary key? It's there exactly so you don't have to do location_id :)

@jdjennin
Copy link
Author

And definitely yes on the tests. It'll probably be Tuesday before I can start that. Camping trip this weekend :-)

On May 25, 2012, at 10:25 PM, dan hassinreply@reply.github.com wrote:

This looks fantastic. Thanks so much, this is really appreciated. Of course there've been a bunch of changes since your fork, so it may take a bit of time to merge.

In the meantime, do you think you could start adding some tests?


Reply to this email directly or view it on GitHub:
#5 (comment)

@dingbat
Copy link
Owner

dingbat commented May 27, 2012

Enjoy!

I guess this can wait until you get back then, but I had a few questions:

  • You mentioned that remoteCreate will create the object locally, but I don't see the code to do the insert. I'm guessing it's assumed that if the object exists it was already inserted in the context?
  • You mentioned that remoteUpdate will rollback if not successful, but it looks like your PR just saves the context only if it was successful. (Which I think is good.) Wouldn't this make the rollback unnecessary, if the changes weren't saved anyway? And I couldn't find any mention of rollbacks in your source - I've never implemented an undo manager, so how does this work? Is this purely client-side?

@jdjennin
Copy link
Author

  1. remoteCreate is an instance method, so the object must exist locally before it can be created remotely. So the user of NSRailsManagedObject would create the object with the static new method (which is overridden to insert the object into Core Data), populate its properties, then remoteCreate it. Sorry for the lack of clarity there. The insertion happens in the new method.

  2. Yes, I left the rollback to the user's implementation. I suppose it could be included in the NSRailsManagedObject implementation, but it's really two lines:

[[managedObjectContext setUndoManager:[[NSUndoManager alloc] init]] before anything is manipulated in Core Data, then

[managedObjectContext undo] to return to the state of the last save of the receiving context.

It's thread-specific though, so it's something the user should handle.

The real problem is that even though the changes aren't saved, they are still there and will be saved the next time the altered object's managed object context is saved. Undoing will remove the changes so that the object is in its last saved state before being changed.

  1. I guess I could have used remoteID as the primary key, but I'm not sure how Core Data would deal with entities which inherit this property from NSRailsManagedObject but which don't declare the property in the Core Data model. Explicitly filtering on _id made more sense to me at the time.

As long as there is a concise way to isolate the appropriate objects from the Core Data store for CRUD operations, it doesn't really matter to me how it's done :)

@dingbat
Copy link
Owner

dingbat commented May 27, 2012

Re: 2) Cool, I see. I'll add that to the remoteUpdate docs.

Re: 3) Yes, remoteID would have to be defined in each CoreData model. This makes sense to me though - in your case you'd have to define class_id anyway.

@dingbat
Copy link
Owner

dingbat commented May 27, 2012

I think everything works. Don't worry about adding tests, I'll write them.

@dingbat
Copy link
Owner

dingbat commented May 29, 2012

Two last questions on saveContext:

  1. Why did you make it perform asynchronously?
  2. Why does it broadcast a notification? Doesn't save: on NSManagedObjectContext do that with a generic CoreData notification already?

@jdjennin
Copy link
Author

  1. It's asynchronous, but it fires on the main thread. This way, the UI isn't blocked by Core Data (since saving the context is one of the more intensive tasks for Core Data), but the context is still saved on the main thread. It doesn't make much difference when the user has implemented a multi-threaded environment since the contexts must be saved on a per-thread basis.

  2. I broadcast a notification simply because _context can be nil if it isn't set, and if it is nil, no notification will be sent. Also, in the case of a multi-threaded application, it allows for each thread to save its context so all changes are committed to the persistent store. It's just a safety net.

@dingbat
Copy link
Owner

dingbat commented May 29, 2012

Alright, released NSRails 1.1, which included a lot of changes inspired by your work.

A few differences from your commits, if you're interested:

  • Consolidated both classes with an #ifdef. NSRRemoteObject now inherits from NSManagedObject if NSR_USE_COREDATA is defined, otherwise inherits from NSObject
  • No need for "_id" style primary key - since you'll be interfacing with these objects in the context of a server (Rails), we can directly use the NSRRemoteObject-given remoteID
  • Moved _context to NSRConfig instead of a static variable in NSRRemoteObject to allow different contexts for different configs
  • Removed "global" saveContext since I don't think it is that useful (and leaving it to the client would allow custom handling of errors).
  • Made the internal context saving synchronous. I did this because the changes between internal saveContexts were very small - it saves after each object is inserted, whenever incremental changes are made on a per-object basis, etc. I don't think it should eat too much of the main thread, but if it does, the requests should be made asynchronously anyway.
  • Removed the NSRailsSaveCoreDataNotification notification broadcast because the _context static variable and the method to save it are now gone, meaning that any context saving NSRails does is on an object-basis, for which a context is required regardless, meaning that the client can use the standard CoreData context saved notification.

That should be it. Let me know if you have any more suggestions or PRs, you should file them in separate issues. Thanks for your efforts!

@dingbat dingbat closed this May 29, 2012
@marklarr
Copy link
Contributor

@jdjennin @dingbat
Does this support has-many relationships in core data? It seems to blow up for me in NSRRemoteObject -- decodeRemoteValue:(id)railsObject forRemoteKey:(NSString *)remoteKey at the very end, when setting the value for key. My guess is that it should be using the CoreDataGeneratedAccessors, like add, remove, addObject, removeObject? Am I doing something wrong?

(PS Daniel, I started using CoreData. Thanks for the shove in the right direction.)

@dingbat
Copy link
Owner

dingbat commented Jun 20, 2013

@marklarr Glad I could convert you :)

Please open a new issue, and could you provide some more details? For what key is it crashing, are you setting an object's collection or an object's parent/container?

@marklarr
Copy link
Contributor

@dingbat it looks like I may have had my core data model set up incorrectly. I just got home and it's working all of a sudden! As always, thanks for the quick response.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants