Updated June 12, 2015.
I always approach posts like this one with a fear that it’s “too basic” or “too easy” and no one will find value in it. Then I find myself looking for good solid introductory information on topics unfamiliar to me, and discover that good intro posts are hard to find. So here goes.
If you want to use Core Data in your RubyMotion project, then you should probably start with the RubyMotionSamples project on Github. Although most of the samples are pretty basic, they all have valuable lessons to teach us. With the recent addition of OSX application support in RubyMotion, the samples have been subdivided into ios and osx subdirectories. The sample project you will want is under ios, and is called “Locations”.
](http://rubymotion.com) RubyMotion Samples
The user interface on Locations consists of a fairly basic
UITableViewController
, so we won’t spend much time there. The interesting stuff is all found in the other 2 files. We have location.rb and locations_store.rb.
The Location model in
location.rb starts off by subclassing
NSManagedObject
. This is the basic model type of Core Data.
class Location < NSManagedObject
end
Now if you were using Xcode, you’d be defining your models in a xcdatamodel file, using the fancy graphical designer. But we aren’t using Xcode, so how is that done in code?
Fortunately, it’s not that difficult. We simply need to create and populate an NSEntityDescription
def self.entity
@entity ||= begin
entity = NSEntityDescription.alloc.init
entity.name = 'Location'
entity.managedObjectClassName = 'Location'
entity.properties =
['creation_date', NSDateAttributeType,
'latitude', NSDoubleAttributeType,
'longitude', NSDoubleAttributeType].each_slice(2).map do |name, type|
property = NSAttributeDescription.alloc.init
property.name = name
property.attributeType = type
property.optional = false
property
end
entity
end
end
This code creates (and caches) the entity (which represents a database table), and adds properties (the table’s columns) to it. Pretty simple.
I would suggest, if you are going to be writing code like this, that you invest in
Dash sooner rather than later, because it will make answering questions like “What other
NSAttributeType
’s are available?” so much easier.
(https://kapeli.com/dash)
The
iOS Programming: The Big Nerd Ranch Guide book describes the
Model-View-Controller-Store or MVCS pattern, which introduces the Store object. Turns out this is a really useful pattern when dealing with external sources of data, like Core Data, and that’s what our Locations sample is doing with
locations_store.rb
.
class LocationsStore
def self.shared
# Our store is a singleton object.
Dispatch.once { @instance ||= new }
@instance
end
end
Our store follows the Singleton pattern. In order to ensure that we only ever have one store object, the initialize method is declared private,
private
def initialize
model = NSManagedObjectModel.alloc.init
model.entities = [Location.entity]
store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)
store_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), 'Documents', 'Locations.sqlite'))
error_ptr = Pointer.new(:object)
unless store.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:store_url, options:nil, error:error_ptr)
raise "Can't add persistent SQLite store: #{error_ptr[5].description}"
end
context = NSManagedObjectContext.alloc.init
context.persistentStoreCoordinator = store
@context = context
end
There’s a lot going on here, so let’s take it one step at a time. First, we need to create our
NSManagedObjectModel
(MOM), and tell it about the entities we are going to be storing.
model = NSManagedObjectModel.alloc.init
model.entities = [Location.entity]
Next, we create an
NSPersistentStoreCoordinator
(PSC), initializing it with our MOM.
store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model)
Then we define the location of our Core Data store, which in this case is a sqlite database.
store_url = NSURL.fileURLWithPath(File.join(NSHomeDirectory(), 'Documents', 'Locations.sqlite'))
Now we tell the PSC where to keep our locations.
error_ptr = Pointer.new(:object)
unless store.addPersistentStoreWithType(NSSQLiteStoreType, configuration:nil, URL:store_url, options:nil, error:error_ptr)
raise "Can't add persistent SQLite store: #{error_ptr[5].description}"
end
Lastly, we create the
NSManagedObjectContext
(MOC), which we will be using to access our store as data is added, removed, and fetched.
context = NSManagedObjectContext.alloc.init
context.persistentStoreCoordinator = store
@context = context
I will point out here, that if you were programming this in Objective C, all the code from this initialize method would still need to be written, with the exception of about one line.
The code to add and remove Locations is fairly trivial, so I will leave it as an exercise to the reader. I do, however, want to help decipher one last bit of code. The NSFetchRequest which is used to pull data back out of our Core Data store.
def locations
@locations ||= begin
# Fetch all locations from the model, sorting by the creation date.
request = NSFetchRequest.alloc.init
request.entity = NSEntityDescription.entityForName('Location', inManagedObjectContext:@context)
request.sortDescriptors = [NSSortDescriptor.alloc.initWithKey('creation_date', ascending:false)]
error_ptr = Pointer.new(:object)
data = @context.executeFetchRequest(request, error:error_ptr)
if data == nil
raise "Error when fetching data: #{error_ptr[5].description}"
end
data
end
end
The
NSFetchRequest
is created, and we tell it what entity we are looking for by pulling it out of our MOC by name. Then we define the sort order of our query. Finally, we ask the context to execute the query, and it should return all our Locations
, sorted by
creation_date.
I hope this has demystified the basics of using Core Data in RubyMotion. It’s not nearly as difficult as I had imagined when I started.
Next up, we discuss our first complication… How to define multiple, related models. Until then…
If you found this post interesting, you will find the ebook I wrote on these topics (and more) will save you hours, if not days, of research and frustration.