MacRuby notes series: taking the pain out of Core Data (part 1)

Continuing my “MacRuby notes series,” in the first post of which I discussed some observations of MacRuby from a Rails programmers perspective…let’s talk about data persistence using Core Data. 

Remember: this article series aims to help Rails programmers work with MacRuby. Read it in that light, not as an indictment of Core Data.

Core Data is the Cocoa mechanism for data persistence, much like ActiveRecord is for Rails. Like ActiveRecord, Core Data permits you to define your models and configure your persistence backend, but the similarities fade after that.

Functionally speaking, there are several major differences between the two.

  • Core Data, not being a true ORM, doesn’t directly map a database schema the way ActiveRecord does.
  • Core Data can use an actual database (SQLite) as its backend, but it can also use a binary format or even XML.
  • In Core Data there’s no clear way to pre-seed data.
  • In Core Data you cannot version your schema with migrations. Corrected; see below.
  • Core Data models cannot have functionality such as their own methods; they cannot obey the “fat model” programming technique you probably picked up as a Railsist. They are purely persistable data structures. Corrected; see below.
  • You will find that it isn’t nearly as well-documented nor as elegant in usage as ActiveRecord.

The last item is the most frustrating for new MacRuby developers, many of whom are used to (even spoiled by) ActiveRecord’s elegant, efficient and easy syntax.

Let’s examine these “Core Data facts of life”…

Core Data, not being a true ORM, doesn’t directly map a database schema the way ActiveRecord does.

In Rails with ActiveRecord you’re accustomed to being highly data-centric. You define your migrations to define the models; your models are the bedrock of your application. MacRuby applications using Core Data do not do this. MacRuby apps are UI-centric.

You’ve become accustomed to having a SQL schema that itself drives the model behavior. But in Core Data, the persistent storage itself has no role in defining the models; it’s just a place where data goes to live between runs. The models themselves inform the storage.

Core Data doesn’t necessarily use SQL.

In Core Data you are given the choice of a SQLite backend, but it can just as easily be XML or a closed binary format. (For what it’s worth, I recommend SQLite for this, as it’s fast, small, and handy for ad hoc queries.)

In Core Data there’s no clear way to pre-seed data.

In Rails you have “db/seeds.rb” to seed your initial application data. You can break with convention and use fixtures (either CSV or YML) to bulk load data; you could even import data directly into PostgreSQL using CSV. Core Data, being backend-agnostic, does not permit any of these…at least, not easily. I’ve hacked together a way to seed my SQLite database with data, but it’s far from pretty; it a future article in this series I’ll discuss that technique.

In Core Data you cannot version your schema with migrations.

You are trained as a Rails programmer to manage your schema with incremental alterations over time using migrations. These handily permit you to roll to any particular point on the timeline. In Core Data — although your actual codebase is of course in SCC — there is no such thing as data migrations. Your schema simply is what it is.

I was quite incorrect when I asserted that Core Data doesn’t support migrations; mea culpa. As the commenters below have pointed out, it can be done. Here’s how (based on Apple’s own docs).

To make a new version of your managed object model when you need to change the schema in an application you have already released:

  1. In the project navigator, select the managed object model.
  2. Choose Editor > Create Model Version.
  3. In the dialog, choose a name for the model, and if necessary choose the target for the model.
  4. Click Save.

To assign the current model version after you have created a new version of your managed object model:

  1. In the project navigator, select the managed object model.
  2. In the File inspector, locate the Versioned Data Model section.
  3. From the Current pop-up menu, choose the name of the current model version.

I will dig a bit deeper into the Core Data data migration mechanism and make a later post in the “MacRuby notes series.”

Core Data models cannot have functionality such as their own methods.

The prevailing “best practice” in Rails programming is to adopt the “fat model, skinny controller” technique. It facilitates good, DRY coding, easy unit testing, and overall code readability. Unfortunately, you’ll can’t do that with your models in MacRuby. Your persistent models are data storage devices and nothing more. You do, of course, get the expected persistence configuration items such as relationships and validations, but you cannot open the model up and add functionality.

I am grateful to the gracious commenters below for having demonstrated the proper way to do this. You indeed can create a class of your managed object, and add methods therein. The trick is to open the Inspector within the model definition and ensure that your Entity > Class parameter points at your class; by default it points to NSManagedObject. By doing this I was indeed able to implement the following as a proof-of-concept:

class Segment < NSManagedObject
  def to_s
    "Segment id #{self.object_id}"
  end
end

It does, in fact, work. I’m pleased to have been corrected on this matter.

You will find that it isn’t nearly as well-documented nor as elegant in usage as ActiveRecord.

It’s a sad fact, and I alluded to it in the first post of this series. MacRuby is at the bleeding edge, and unfortunately Core Data is not discussed with particular clarity yet. It’s clear that best practices and techniques are still being established.

Still with me?

So, let’s hope that this discussion has not overly depressed you. Now that you know some of the challenges facing a MacRuby Core Data programmer, let’s address the solutions to these problems in to Part 2…

Advertisements

8 thoughts on “MacRuby notes series: taking the pain out of Core Data (part 1)

  1. > In Core Data you cannot version your schema with migrations.

    In Xcode (3.2.x): Design menu -> Data Model -> Add Model Version

    > Core Data models cannot have functionality such as their own methods; they cannot obey the “fat model” programming technique you probably picked up as a Railsist. They are purely persistable data structures.

    NSManagedObject subclasses. Open your xcdatamodel, select an entity, File -> New File, Cocoa Touch class, Managed Object class.

    • Aaron, thank you for the Model Version hint — I’m excited to investigate that.

      While you can certainly easily subclass NSManagedObject in Objective-C, it does not appear to work in MacRuby — I’ve tried it. You can define a class such as “class Foobar < NSManagedObject" (where Foobar is, of course, already a defined model) but the methods etc. defined therein are not attached to your managed object.

      • I’ve been using MacRuby with CoreData and subclassing the model does work with “class Foobar < NSManagedObject" as well as further subclassing "class ChildBar < Foobar"

      • Just to expand on my comment that it does work, I wonder if the reason it’s not working for you is that you haven’t mapped the models to classes in the xcdatamodel file. You don’t just make a class with the same name, you have to tell xcode what the class is, and make sure that the .rb file where that class is defined is required.

  2. When you made your NSManagedObject subclass, did you also define the custom class in the model? When you select an entity in the data model, in the inspector it lists the entity name and then the class. You need to enter in the name of your subclass in order for CoreData to use it. I know CoreData does some dynamic jiggery pokery but I can’t see why MacRuby wouldn’t support it, assuming it supports all the runtime functionality.

    As for migrations, they do exist. As has been said you can add a new version of your data model. There are then various ways to migrate. CoreData can automatically migrate most minor changes. For bigger changes you can creating a mapping model, which lets you explicitly define the mappings between versions. This is largely done in a GUI, but you can customise the behaviour in places with certain hooks (eg setting default values for new fields).

    The one slightly annoying thing is that the framework doesn’t support progressive migration by default. It by default looks for a migration straight from the version it finds to the current version, so you’d need to have migrations for 1.0 -> 2.0, 1.1 -> 2.0, 1.2 -> 2.0 etc. Thankfully it isn’t too hard to write the code to handle progressive migration yourself.

  3. Aaron, Jeremy, Martin – thank you for correcting me on the matter of adding methods to NSManagedObjects. I altered the post above accordingly.

  4. I think your struggling with Core Data because you’ve misunderstood its basic function. This is a very common mistake among those with a high degree of knowledge about using some relational database API like ActiveRecord.

    ActiveRecord is an object wrapper/interface to a procedural database such as SQL. The entire point of the API is to efficiently move data in and out of the procedural database file and expose it to ruby’s object oriented environment. With ActiveRecord, if there is no database, then there is no need for ActiveRecord.

    Core Data is much different. Core Data does not require persistence and is largely agnostic about the means of persistence. Persistence is optional and in a lot of ways, an after thought to the API.

    Core Data’s true function is to manage a graph of live objects in memory. It is intended as an API to implement the model layer of a Model-View-Controller design application. It is intended to create the actual logical guts of an application. The others layers, the Views and the Controllers are just the interfaces for the model to the users and other apps. The model is where all the logical action is and Core Data makes it trivial to create, manage and use complex models.

    In other words, Core Data is primarily about simulating real-world objects, conditions or events instead of being primarily about storing and fetching data.

    Core Data seems obscure to people with backgrounds in database centric APIs because they assume that Core Data is just an object-oriented database wrapper. They assume that entities are really tables, attributes are really columns, relationships are really joins and instances are really rows. However, Core Data doesn’t start with the database, it starts with the objects and then provides the option of persisting those objects in the form of an sqlite db. There is no strong relationship between the live objects and the schema of the sqlite file. Heck, the schema for the sqlite persistent store is not even documented because developers have no need to actually know what goes on inside the file.

    It’s common for many experienced developers in other APIs to feel like Core Data is incompletely documented or otherwise obscure but I think that is largely because they are looking for documentation or explanations for functionality that is handled automatically and behind the scenes. In short people believe that, “That can’t all it takes to use it, I must be missing something.”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s