MetaSkills.net

Synchronizing Core Data With Rails (3.0.0.pre)

Posted On: February 11th, 2010 by kencollins

This is my presentation to our local @757rb/@757objc users group this past Tuesday. Hope some find it useful. Lessons learned from building HomeMarks native iPhone application to synchronize Core Data with a RESTful backend built using rails 3.0.0.pre. This covers a previous design methodology called the AJAX head pattern which decouples rails applications from the views they present which allowed an easy API foundation for the iPhone application and data sync methods.

Resources

Ettore

  HOMEPAGE  | February 12th, 2010 at 08:40 AM
Ettore Thanks for sharing

Daniel Lopes

  HOMEPAGE  | February 20th, 2010 at 10:36 AM
Daniel Lopes Very cool content... Have you considered the idea to record a screencast with your presentation?

Ken Collins

  HOMEPAGE  | February 20th, 2010 at 08:31 PM
Ken Collins

I have. The local user groups will soon have the ability to record our talks. I will also be in Richmond on March 8th doing this talk for the local CocoaHeads.

Shayne Sweeney

  HOMEPAGE  | February 22nd, 2010 at 09:31 PM
Shayne Sweeney I want to start off telling you how awesome these slides are, even without audio I've been able to learn and use so much in building out my current project. Much Appreciated! I do, have a few questions if you wouldn't mind answering! :) The annotation at the top of slide 66 (Customize To Your API) reads: // Assumes MyRequest & MyFormRequest subclasses of // ASIHTTPRequest and ASIHTTPFormRequest (minimal use). I assume that ASIHTTPFormRequest is meant to be ASIFormDataRequest. I've created my subclasses, but they remain pretty empty as of now; simply namespacing ASI's classes. Considering the note "minimal use", my question is: What kind of behavior have you added to these subclasses? Later on slide 69 (Import On Launch) you implement the method "getUserDataFor:" in the ASIHTTPRequest category (MyAdditions). The next slide shows the success delegate method implementation of "didGetUserData:"; you check if the request response "isNotModified", conditionally modify an Etag and call import. Questions are: 1) You mention that this is implemented in "The Controller" - is this referring to a subclass of UIViewController or are you using "other controllers" here? 2) You pass off the request to the application's delegate, regardless of the "isNotModified" condition - what does this method look like and what is it's purpose? 3) Would very much like to see an example of your import method implementation, where we actually "sync" with CoreData. I'm a bit fuzzy on how I'd implement maintaining the managed objects (existing, vs new). Again, thanks so much for this and I apologize ahead of time as I understand these questions may have been answered during your live presentation.

Ken Collins

  HOMEPAGE  | February 23rd, 2010 at 10:48 AM
Ken Collins

@Shayne Sweeney, thanks for the kudos. I'll see if I can answer your questions in order. First, yes, that was supposed to be ASIFormDataRequest and as of yet, I have no behavior in those classes, most everything is in ASIHTTPRequestAdditions. That said, if I had time to refactor, I could move over a lot of my category extensions to sub class implementations. In fact most can, this includes the new designated initializer, class utility and factory methods as well as instance extensions like - (BOOL)isSuccess and - (BOOL)isNotModified. In actuality, I am having a hard time recalling why I went with a category vs subclasses. In my app I always use MyRequest class factories, and let the verb make a call on the correct low level implementation of weather to instantiate a MyRequest or MyFormRequest, which the utility verb helpers do.

I always sub class controller and most of my requests very rarely have a controller as a delegate. Hence why my designated initializer has the default catch all of letting the app delegate handle delegation. The main reason is just to log, but you could take the "oh shit" approach of alerts, refreshing entire data with popping back to root view if something really bad happened. Most requests just send and forget, only new item requests specify a model NSManagedObject as the delegate and even in that case it reports back to the app delegate afterward for logging and "oh shit" handling :)

My import is not a real big amount of code and it's simple for one reason. It kills everything in the DB and just reimports again. There is no such thing in my app as an object is here and not there. An average large user of HomeMarks has a JSON payload when gzipped of only 5K or so, getting all user data is really small when your web server and iPhone connections are set up right. I love that ASIHTTPRequest has the gzip function built in.

Shayne Sweeney

  HOMEPAGE  | February 23rd, 2010 at 06:51 PM
Shayne Sweeney Thanks! I really appreciate your response. (What's the newline secret? I apologize for the lack of formatting, but the newlines didn't translate over well.)

Ken Collins

  HOMEPAGE  | February 23rd, 2010 at 10:10 PM
Ken Collins

The magic of hand coded <p> tags. I dont think this poor old mephisto blog has automatic comment formatting support.

Shayne Sweeney

  HOMEPAGE  | February 24th, 2010 at 05:57 PM
Shayne Sweeney

Ken, Thanks for that! I'll be sure to keep that in mind.

I've been hard at work implementing Core Data into app, keeping a close eye on your presentation throughout. One of the improvisations I've made in importing data has me thinking "there has to be a better way".

In dealing with managed objects with relationships how do you manage staying true to the @property definition, or do you through it out and write your own implementation.

Let's say I have a MyPlace managed object that has a relationship to a MyAddress managed object. Each place has one address.

The JSON from the server returns places with an "address" : { ... } element. The [MyPlace newObject:attributes] method takes the once JSON, now NSDictionary and loops over the keys setting the appropriate values.

How do you handle setting the nested objects? Is it possible to maintain @properties within your managed objects. I suppose you could put conditions in the newObject: method but seems like a maintenance tradeoff.

Thanks! PS - I hope to write a response to your slides based on my experience to increase the available information on this topic.

Ken Collins

  HOMEPAGE  | February 24th, 2010 at 06:50 PM
Ken Collins

I basically have one class method at the top of each managed object class that does all the work. So in my case if I import a column object (has_many :boxes) and there is a boxes value in the JSON, then i just pushes it further down to the HMBoxes managed object class method and so on down the chain to bookmarks. This keeps things nice and tidy and let's a top level class champion all of its associations. Easy to do in my case.

One thing that might not have been apparent in my slides -- each of my managed object models has a remoteID property that stores the PK from the web app's DB. Obviously this is needed for remote actions. Another thing that was interesting was in my rails model, bookmarks are polymorphic owned. Meaning a Inbox, Trashbox, or Box of any id can have_many :bookmarks and when a bookmark moves that polymorphic association changes it's scope as well as position. Core Data is not like a DB we rails developers are used to, even tho it is SQLite on the backend. We have to let go and let it manage our relationships and keys. To map my polymorphic bookmarks in Core Data, I had to use a technique called FETCHED_PROPERTIES, which let's me reflect on the calling object to define properties as predicates definitions. Even tho Core Data is iPhone OS, 3.0, Apple only fixed the FETCHED_PROPERTIES technique in OS 3.1 (thanks to my bug report).

Yea there is a lot of info to share in this area. I would love to see your slides and I'll link to them too if you let me know.