Simpler Core Data with RubyMotion

23
Simpler Core Data with RubyMotion Stefán Hafliðason http://stefan.haflidason.com @styrmis

description

RubyMotion is great for quickly prototyping apps but it lacks the data modelling tools that Xcode provides. Luckily, using Core Data with RubyMotion can actually be easier and quicker with a little help from some 3rd party libraries.

Transcript of Simpler Core Data with RubyMotion

Page 1: Simpler Core Data with RubyMotion

Simpler Core Data with RubyMotion

Stefán Hafliðason ! http://stefan.haflidason.com

" @styrmis

Page 2: Simpler Core Data with RubyMotion

Why RubyMotion?• Promises increased developer productivity

• Brings the flexibility of Ruby to iOS and OSX development

• Bridges directly to Obj-C libraries: no intermediate glue code

• A REPL for working with your app live!

• Make tweaks quickly

• Build whole views programmatically on the fly

Page 3: Simpler Core Data with RubyMotion

Why Core Data?

• Optimised for low-memory/embedded (iOS) devices

• Mature data access/persistence framework

• Also available on OSX

• Works with iCloud—free cloud syncing for your app

Page 4: Simpler Core Data with RubyMotion

Core Data is Difficult• Provided boilerplate code unnecessarily complex

• An object graph that’s persisted to an SQLite database

• Suggests relational access, which is not quite the case

• Typical patterns for working with relational data are not optimal here

Page 5: Simpler Core Data with RubyMotion

RubyMotion is “Easy”

• Friendliness of Ruby

• An ARC equivalent is included

• Lots of work done to abstract complexity away

• More concepts similar to other OO languages

Page 6: Simpler Core Data with RubyMotion

Core Data and RubyMotion

• No equivalent of Xcode’s visual data modeller

• How do I define my data model?!

• What about versioning?!

• How will I handle migrations?

Page 7: Simpler Core Data with RubyMotion

What we need• Our data model (NSEntityDescriptions +

NSRelationshipDescriptions forming our NSManagedObject)

• A Core Data Stack (NSManagedObjectModel + NSPersistentStoreCoordinator + NSManagedObjectContext)

• A workflow for versioning and migrating between versions

Page 8: Simpler Core Data with RubyMotion

Defining Our Data Model• We would normally do this in Xcode

• Visual Editor for .xcdatamodel bundles

• Integrated handling of versioning and custom migration code

• Automatic lightweight (schema) migrations

• How do we achieve this with RubyMotion?

Page 9: Simpler Core Data with RubyMotion

Options for RubyMotion

• Handle everything programmatically (low level) #

• Use Xcode to work with .xcdatamodel files, copy in each time #

• Use a Ruby library for creating .xcdatamodel files $

Page 10: Simpler Core Data with RubyMotion

Handling Everything Programmatically

entity = NSEntityDescription.alloc.initentity.name = 'Task'entity.managedObjectClassName = 'Task'entity.properties = [ 'task_description', NSStringAttributeType, 'completed', NSBooleanAttributeType ].each_slice(2).map do |name, type| property = NSAttributeDescription.alloc.init property.name = name property.attributeType = type property.optional = false property end

Page 11: Simpler Core Data with RubyMotion

Handling Everything Programmatically

entity = NSEntityDescription.alloc.initentity.name = 'Task'entity.managedObjectClassName = 'Task'entity.properties = [ 'task_description', NSStringAttributeType, 'completed', NSBooleanAttributeType ].each_slice(2).map do |name, type| property = NSAttributeDescription.alloc.init property.name = name property.attributeType = type property.optional = false property end

Not all that bad, but we want to use .xcdatamodel files

Page 12: Simpler Core Data with RubyMotion

.xcdatamodel files are just XML

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>!<model name="" userDefinedModelVersionIdentifier="001" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="2061" systemVersion="12D78" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">! <entity name="Article" syncable="YES">! <attribute name="title" optional="YES" attributeType="String" syncable="YES"/>! <relationship name="author" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Author" inverseName="articles" inverseEntity="Article" syncable="YES"/>! </entity>! <entity name="Author" syncable="YES">! <attribute name="name" optional="YES" attributeType="String" syncable="YES"/>! <relationship name="articles" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Article" inverseName="author" inverseEntity="Author" syncable="YES"/>! </entity>!</model>

Page 13: Simpler Core Data with RubyMotion

Using a library to generate .xcdatamodel files (ruby-xcdm)

1 schema "001" do! 2 entity "Article" do! 3 string :body, optional: false! 4 integer32 :length! 5 boolean :published, default: false! 6 datetime :publishedAt, default: false! 7 string :title, optional: false! 8 ! 9 belongs_to :author!10 end!11 !12 entity "Author" do!13 float :fee!14 string :name, optional: false!15 has_many :articles!16 end!17 end

Page 14: Simpler Core Data with RubyMotion

Workflow• Create schema file in schemas directory, e.g. schemas/001_initial.rb

• Build the schema

• Add a new schema version, e.g. 002_add_new_fields.rb

• Rebuild the schema

• That’s it!

Page 15: Simpler Core Data with RubyMotion

Workflow$ echo "gem 'ruby-xcdm', '0.0.5'" >> Gemfile$ bundle install$ rake schema:buildGenerating Data Model learn-xcdm Loading schemas/001_initial.rb Writing resources/learn-xcdm.xcdatamodeld/1.xcdatamodel/contents$ rake # The default rake task is to run the app in the simulator(main)> mom = NSManagedObjectModel.mergedModelFromBundles(nil)=> #<NSManagedObjectModel:0x8fa7690>(main)> mom.entities.count=> 2(main)> mom.entities.first.name=> "Article"(main)> mom.entities.first.propertiesByName=> {"body"=>#<NSAttributeDescription:0x8e5db30>, "title"=>#<NSAttributeDescription:0x8ea4770>}

Page 16: Simpler Core Data with RubyMotion

Advantages of using ruby-xcdm

• No magic: generates XML from a schema

• Schema versions are fully text-based and readable, making them well-suited to version control

• Can compile our versions into .xcdatamodeld bundles, completely removing dependence on Xcode

Page 17: Simpler Core Data with RubyMotion

Basic Core Data Stack 1 model = NSManagedObjectModel.mergedModelFromBundles(nil) 2 3 store = NSPersistentStoreCoordinator.alloc.initWithManagedObjectModel(model) 4 store_path = File.join(NSHomeDirectory(), 'Documents', 'LearnXcdm.sqlite') 5 store_url = NSURL.fileURLWithPath(store_path) 6 7 options = { NSMigratePersistentStoresAutomaticallyOption => true, 8 NSInferMappingModelAutomaticallyOption => true } 9 10 error_ptr = Pointer.new(:object)11 12 unless store.addPersistentStoreWithType(NSSQLiteStoreType,13 configuration: nil,14 URL: store_url,15 options: options,16 error: error_ptr)17 raise "[ERROR] Failed to create persistent store: #{error_ptr[0].description}"18 end19 20 @context = NSManagedObjectContext.alloc.init21 @context.persistentStoreCoordinator = store

Page 18: Simpler Core Data with RubyMotion

Core Data Query

• From the developers of ruby-xcdm

• Abstracts away much of the complexity of Core Data

• All you need is your .xcdatamodeld bundle

Page 19: Simpler Core Data with RubyMotion

Core Data Query in Action# app/models/task.rbclass Task < CDQManagedObjectend!# app/app_delegate.rbclass AppDelegate include CDQ! def application(application, didFinishLaunchingWithOptions:launchOptions) cdq.setup true endend

Page 20: Simpler Core Data with RubyMotion

Core Data Query in Action(main)> Task.count=> 0(main)> t1 = Task.create(task_description: "Complete presentation")(main)> t2 = Task.create(task_description: "File tax return")(main)> cdq.save=> true(main)> exit$ rake...(main)> Task.count=> 2(main)> t1, t2 = Task.all.array(main)> t1.task_description=> "Complete chapter"(main)> t2.task_description=> "File tax return"(main)> t2.destroy=> #<NSManagedObjectContext:0x914cbe0>(main)> cdq.save=> true(main)> Task.count=> 1

Page 21: Simpler Core Data with RubyMotion

Core Data in ActionAuthor.where(:name).eq("Emily")Author.where(:name).not_equal("Emily")Author.limit(1)Author.offset(10)Author.where(:name).contains("A").offset(10).first!# ConjuctionsAuthor.where(:name).contains("Emily").and.contains("Dickinson")Author.where(:name).starts_with("E").or(:pub_count).eq(1)!# Nested ConjuctionsAuthor.where(:name).contains("Emily").and(cdq(:pub_count).gt(100).or.lt(10))!# RelationshipsAuthor.first.publications.offset(2).limit(1)cdq(emily_dickinson).publications.where(:type).eq('poetry')!class Author < CDQManagedObject scope :prolific, where(:pub_count).gt(50)end

Page 22: Simpler Core Data with RubyMotion

Takeaways

• Don’t be put off by the Xcode boilerplate: Core Data doesn’t have to be that hard

• With CDQ, Core Data is arguably easier to use with RubyMotion rather than harder

• XCDM, CDQ and RubyMotion Query (all by Infinitered) are all worth taking a look at

Page 23: Simpler Core Data with RubyMotion

Next Steps• In the coming weeks I’ll be researching and writing

about:

• How to best handle heavyweight/data migrations in RubyMotion

• Deconstructing the ‘magic’ in Core Data Query

• RubyMotion development best practices

Stefán Hafliðason ! http://stefan.haflidason.com

" @styrmis