Seeding CoreData databases with ruby
January 26th, 2012
If you're writing an iOS app that uses Core Data then you may well want to ship it with an initial database (which potentially gets over the air updates later on).
On iOS, CoreData stores always use sqlite3 as their backend. You could create a sqlite database directly, but you'd have to reverse engineer the way apple uses sqlite, ensure that you use the same name manging for table and column names, generate the same meta data used for persistent store migration etc. Too brittle for my liking.
Luckily both RubyCocoa and MacRuby allow you to access the Core Data framework from a ruby script. The former is bundled with Mac OS X since 10.5 (you will need to use the system ruby). RubyCocoa has some clunkier syntax because it doesn't have the benefit of the extensions to ruby, however at the moment MacRuby doesn't quite work with Active Record, which is where I was getting my seed data from. If your data is coming from else where, this may not be a problem. Other than the slight syntax differences around the handling of Objective-C's sort-of-named-arguments the code is the same
The RubyCocoa version looks like this
require 'rubygems' require 'osx/cocoa' OSX.require_framework 'CoreData' class CoreDataStore def create_entity name, props={}, relationships={} entity = OSX::NSEntityDescription.insertNewObjectForEntityForName_inManagedObjectContext(name, context) props.each do |k,v| entity.setValue_forKey v, k end relationships.each do |k, objects| collection = entity.mutableSetValueForKey(k) objects.each {|o| collection.addObject o} end entity end def initialize(data_store_path, mom_path) @data_store_path = data_store_path @mom_path = mom_path end def context @context ||= OSX::NSManagedObjectContext.alloc.init.tap do |context| model = OSX::NSManagedObjectModel.alloc.initWithContentsOfURL( OSX::NSURL.fileURLWithPath(@mom_path)) coordinator = OSX::NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model) result, error = coordinator.addPersistentStoreWithType_configuration_URL_options_error( OSX::NSSQLiteStoreType, nil, OSX::NSURL.fileURLWithPath(@data_store_path), nil) if !result raise "Add persistent store failed: #{error.description}" end context.setPersistentStoreCoordinator coordinator end end def save res, error = context.save_ if !res raise "Save failed: #{error.description}" end res end end
Having done this, you use it like this
store = CoreDataStore.new('seedData.sqlite', 'yourmodel.mom') blog = store.create_entity 'Blog', 'title' => 'Hello world', 'body' => "it's a fine day" store.create_entity 'Comment', 'body' => 'I Agree', 'blog' => blog store.save
The too arguments are the path to the file you want to store the data in, and the path to your model file.
Your Mom's a data model
You may be wondering what a .mom file is. In XCode you work with a .xcdatamodel file. When you build your app, this is compiled down into a .mom file. You can also do it your self by running
/Developer/usr/bin/momc mymodel.xcdatamodel mymodel.mom
Error launching remote program: security policy error
November 7th, 2009
I created a new iPhone project today and got a rather vague error message from Xcode when I tried to run it on a device:
Error launching remote program: security policy error
After some digging it turns out that the problem was that I had two provisioning profiles on the device:
- a wildcarded one (which had expired) that I use for quick hacks that don't warrant going through the hassle of setting up new provision profiles
- one created specifically for this application
The wildcarded profile had expired, and for some reason the iPhone was trying to launch the app with the expired profile even though it had a valid profile that it could have used. Deleting the expired profile from the device did the trick. If only that error message had been a bit more explicit ...
My first Iphone app
October 12th, 2009
After much toiling kgb's first iPhone app is on sale! Get it now. Only in the US unfortunately.
For the record, we submitted two versions of the app: an earlier version that we didn't expect to release (we wanted to see what the approval process was like and if apple was OK with the basic idea of our app) and the one that is on sale today. Both were approved in about 10 days - no approval horror stories here!
Unit testing Core Data iphone apps
September 10th, 2009
This is probably obvious, but in the interest of saving someone else the few minutes I spent scratching my head on this one...
Your app probably has some code that looks like
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain]
If you pass nil then the main bundle is searched. When your an iphone app, that is the app itself - no surprises there. But when you are a unit test bundle, you are no longer the main bundle (the test rig application is). You need to tell CoreData to look inside your unit test bundle, with something along the lines of
managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles: [NSArray arrayWithObject:[NSBundle bundleWithIdentifier:@"com.yourcompany.unittests"]]] retain];
For this to work you also need your data model to be present in your unit test bundle. For some reason, unlike your .m files the inspector doesn't show you a list of targets the model should be included in - you need to drag the data model file into the compile source build phase of your unit test target.