Using Core Data in iOS with RubyMotion

Learn how to use Core Data in a simple RubyMotion app. By Gavin Morrice.

Leave a rating/review
Save for later
Share

If you’re looking to add persistence to your iOS or OS X app, Core Data is great choice — and implementing it in RubyMotion is surprisingly simple.

In this tutorial, you’ll use Core Data to add persistence to the Pomotion timer I showed you in how to make in my RubyMotion Tutorial for Beginners series.

If you have a good understanding of RubyMotion, feel free to download the complete code from the GitHub repository and start from there.

Otherwise, I’d suggest working through those two tutorials before starting this one.

You’ll need the following to get the most out of this RubyMotion tutorial:

  • Experience developing for iOS in Objective-C
  • Basic understanding of Ruby
  • Basic knowledge of CSS
  • Basic experience using Terminal

Prior experience with Core Data or ActiveRecord would also be a huge plus, but neither is essential.

Getting Started

First, make sure you have the final project from the previous tutorial series or grab it from GitHub.

Open Terminal, switch to the root directory of the application, and make sure the app builds successfully by running the following command:

rake device_name="iPhone 4s"
Note: RubyMotion 3 includes a new option device_name for the rake command that lets you specify which device simulator the app will run on. I prefer to use the iPhone 4s simulator because it’s smaller and I can see everything on the screen.

If your screen looks like this, great — If it doesn’t, make sure you’ve followed the installation instructions and completed all of the steps in the previous Pomotion tutorials.

Your starting point app should look like this.

Your starting point app should look like this.

Once you have Pomotion running, you’re ready to begin.

Dependency Updates: If it has been a while since you last used RubyMotion, you may need to update some of your gems. Specifically, you may need to update the bundler gem. You can do so by running the following command in Terminal: gem update bundler.

Additionally, you may see an error something like Bundler::GemNotFound: Couldn’t find sass-3.4.2 in any of the sources. If you encounter this issue, you can explicitly install Sass v3.4.2 with the following: gem install sass -v 3.4.2

Adding the Tasks Button

You’re going to add a tasks list to the Pomotion app. Think of this as a user’s daily TODO list where users add and remove tasks, as well as select a task to work on.

Users access the tasks list by tapping a button in the navigation bar. So, you’ll start by adding that button.

Using your favorite text editor, open app/controllers/main_view_controller.rb and add the following lines under the timer_button method definition.

def tasks_button
  @tasks_button ||= UIBarButtonItem.alloc.initWithImage(tasks_image, 
    style: UIBarButtonItemStylePlain, target: self, action: nil)
end

def tasks_image
  @tasks_image ||= UIImage.imageNamed('todo.png')
end

Here you’ve defined two properties on the MainViewController class: a UIBarButtonItem named tasks_button, and a UIImage named tasks_image.

Before you run these changes, you’ll need to add the image file referenced in the tasks_image method above to your app’s resources.

Download this file and save it to your application’s resources directory as todo.png.

Still in app/controllers/main_view_controller.rb, add this method below the tasks_image method:

def viewDidLoad
  super
  self.title = "Pomotion"
  self.navigationItem.rightBarButtonItem = tasks_button
end

This adds the tasks_button to MainViewController’s navigation bar. Build and run your app the same way you did the last time to see the changes:

rake device_name="iPhone 4s"

Main Screen with blue tasks button

Looking good so far! However, the color scheme for this app is red, white and green. That blue tint on the tasks_button is a bit clashy, don’t you think? Good thing you can change that!

You might remember from the previous Pomotion tutorial that Pomotion uses PixateFreestyle to add CSS styles to your app’s views. This means that you’ll find style definition in a CSS file named resources/default.css.

Open resources/default.css and add the following line to the navigation-bar selector:

-ios-tint-color: white;

This changes the tint color of the navigation bar to white. To learn more, check out the Pixate reference.

Save the file then build and run the app again with rake device_name="iPhone 4s". The tasks button should now have a white tint instead of blue.

Main screen with white tasks button

Wasn’t that easy? :]

Try tapping the tasks_button. Nada. Why isn’t it responding? Oh, because you haven’t defined an action to be called when it’s tapped.

Adding the Tasks List Screen

In Terminal, create a new file in the app/controllers directory named tasks_view_controller.rb:

touch app/controllers/tasks_view_controller.rb

Open the file in your text editor.

Declare TasksViewController as a subclass of UITableViewController like the following:

class TasksViewController < UITableViewController

end

Save the file, and open main_view_controller.rb and add the following just after timer_button_tapped:.

def tasks_button_tapped(sender)
  tasks_controller = TasksViewController.alloc.initWithNibName(nil, bundle: nil)
  navigationController.pushViewController(tasks_controller, animated: true)  
end

This displays the tasks list when the user taps the tasks_button.

Now wire up this method to the action: parameter in tasks_button with this:

def tasks_button
  @tasks_button ||= UIBarButtonItem.alloc.initWithImage(tasks_image, 
    style: UIBarButtonItemStylePlain, target: self, action: 'tasks_button_tapped:')
end

Build and run again to launch the app in the simulator

rake device_name="iPhone 4s"

This time, when you tap the tasks_button you should see an empty table view that looks like this:

Pomotion tasks list with no tasks

Now you're making progress! The UI is mostly configured and now it's time to drill down into the inner workings and make it actually do something more than look pretty.

Populating the Tasks List with Real Data

Installing CDQ

Here comes the fun part...

To use Core Data in your app, you'll be using the Core Data Query gem (or cdq, for short). cdq makes it really easy to get started with Core Data in RubyMotion with just a few lines of code.

I showed you how to install gems to your project in RubyMotion Tutorial for Beginners: Part 1, but you can read the Pixate Freestyle section if you need a refresher.

Open your Gemfile and add the following line:

gem 'cdq'

Then make sure it's installed by running this command in Terminal:

$ bundle install

Now that cdq is installed, run the following command in Terminal to complete the installation:

cdq init

The cdq init command does a couple of things, and it's quite important that you understand them both.

First, it creates an initial schema file named 0001_initial.rb in a new directory named schemas. These schema files are where you define and manage your application's database structure.

Each time you need to make a change to one of the tables, you create a new schema file that reflects how the new database should look. As long as the versions are not too different from each other, Core Data automatically migrates your database and updates the structure without you having to do anything else.

The second thing cdq init does is that it adds another line to the bottom of your Rakefile, like this:

task :"build:simulator" => :"schema:build"

In short, this makes sure that each time you build the app to run in the simulator, the rake schema:build command also runs to keep the schema up to date.

This is extremely helpful, but there's a potential pitfall: If you're deploying to an iOS device instead of the simulator, this command doesn't run automatically — you'll have to run it manually from Terminal by running:

rake "schema:build"

Good to know!

Now, open app/app_delegate.rb and include CDQ by adding the following line just below AppDelegate:

  include CDQ

And finally, add the following line to the start of application:didFinishLaunchingWithOptions

  cdq.setup

And that's it for the installation. Next up is setting up your first data model.

Creating a Data Model

Open schemas/0001_initial.rb in your text editor, and you'll see some example code that's been commented out. Delete that and add the following in its place:

schema "0001 initial" do

  entity "Task" do
    string :name, optional: false
  end

end

This code defines a simple entity named Task. A Task has a name (defined as a string), which is required for a Task record to be valid (it's not optional).

There's one more step to finish adding your Task model, and that's to create a class for it in your application. In your text editor, create a file named task.rb in the app/models directory and define Task like so:

class Task < CDQManagedObject

end

The CDQManagedObject superclass is provided by CDQ, and it provides some extra methods for querying and saving data to your database.

Build and run the app in the simulator so you can try out CDQ in the console:

rake device_name="iPhone 4s"

Still in Terminal, run the following command once the app launches:

(main)> Task.count

It should return the number of Tasks currently in the database (which is currently 0):

(main)> Task.count
=> 0

Create a new Task by typing the following:

Task.create(name: "My first task")

The output should look similar to this:

(main)> Task.create(name: "My first task")
=> <Task: 0x11750b10> (entity: Task; id: 0x1174d940 <x-coredata:///Task/t2FA02CA9-05AF-4BD5-BAEB-B46F21637D0C2> ; data: {
   name = "My first task";
})

Now when you call Task.count, you should see it return 1.

(main)> Task.count
=> 1

Quit the simulator (by pressing ctrl + c), and then build and run the app once again.

rake device_name="iPhone 4s"

Fetch the number of Tasks once again by running Task.count, it should return 1.

(main)> Task.count
=> 0

...it should, but it didn't! What happened?!

Be the Boss of cdq

CDQ won't commit any data to the database unless you tell it to, and you do that by calling cdq.save in your code. This is an important gotcha to keep in mind.

Your application will not persist data unless you call this method. This is a deliberate feature of cdq; by only making commits to the database when you've defined all of the changes to be committed, you'll improve the memory usage and performance of your app.

It's easy to forget about this extra step – especially if you're coming to iOS from Ruby on Rails.

So start again from the top by create a new Task:

(main)> Task.create(name: "This is my first task")
=> <Task: 0xb49aec0> (entity: Task; id: 0xb49aef0 <x-coredata:///Task/t41D7C6A6-806D-4490-90A6-19F1E61ED6C12> ; data: {
    name = "This is my first task";
})

But this time, run the following command afterwards:

(main)> Task.save
=> true

Now quit your application and then re-launch. Call Task.count, and you'll see it returns 1.

(main)> Task.count
=> 1

CDQ Helper Methods

CDQ offers a few other helpful methods to save and retrieve persisted data. Run the following commands in the console to see their results.

Fetch the first row:

(main)> Task.first
=> <Task: 0xb49aec0> (entity: Task; id: 0xb47a830 <x-coredata://B0AEB5CD-2B77-43BA-B78B-93BA98325BA0/Task/p1> ; data: {
    name = "This is my first task";
})

Check if there are any records:

(main)> Task.any?
=> true

Find rows that match given constraints:

(main)> Task.where(name: "This is my first task").any?
=> true    

(main)> Task.where(name: "This is my second task").any?
=> false

Delete a row:

(main)> Task.first.destroy
=> #<NSManagedObjectContext:0xad53ed0>

And most importantly, commit changes to the database:

(main)> Task.save
=> true

You'll use these methods again soon -- when you complete the tasks list feature.

Loading Tasks from the Database

The Tasks screen should show each of today's Tasks that are in the database. First, though, you should prepare for the initial condition when there are no tasks.

Add the following implementation In tasks_view_controller.rb:

class TasksViewController < UITableViewController  
  # 1
  # = UITableViewDelegate =

  def tableView(table_view, heightForRowAtIndexPath: index_path)
    todays_tasks.any? ? 75.0 : tableView.frame.size.height
  end
  
  # 2
  # = UITableViewDataSource =

  def tableView(table_view, cellForRowAtIndexPath: index_path)
    table_view.dequeueReusableCellWithIdentifier(EmptyCell.name)
  end

  # 3
  def tableView(table_view, numberOfRowsInSection: section)
    [1, todays_tasks.count].max
  end

  # 4
  private

  def todays_tasks
    Task.all
  end

end

Most of this code should seem pretty familiar to you by now, except for the last method, but I'll walk you through each of them.

  1. First, you define tableView:heightForRowAtIndexPath from the UITableViewDelegate delegate. If there are any tasks in the database, then the height of each cell should be 75 points, otherwise, a cell's height should be the entire height of the UITableView.
  2. Next, in tableView:cellForRowAtIndexPath: you return an instance of EmptyCell -- you'll define this class in a moment.
  3. The tableView:numberOfRowsInSection: method should return 1 if there are no tasks in the database, or the number of tasks if there are any. This ensures that if the list of tasks is empty, the app still displays one cell.
  4. Finally, todays_tasks is a helper method that loads all of the tasks from the database.

    Although you could easily just call Task.all throughout the various methods in TasksViewController, it's good practice to define a method like this to access each of the objects or collections you load from the database.

    Having this single point of access is much DRY-er (Don't Repeat Yourself), and it means you only have to change the code once if you need to add extra constraints later.

Now add this:

def viewDidLoad
  super
  tableView.registerClass(EmptyCell, forCellReuseIdentifier: EmptyCell.name)
end

You've just registered the EmptyCell view class with TasksViewController's tableView by defining viewDidLoad.

Before you build the app again, you'll also need to create the actual class for the empty table view cell, so create a new file in the app/views directory named empty_cell.rb, like this:

touch app/views/empty_cell.rb

Now add this…

class EmptyCell < UITableViewCell

end

to define the EmptyCell class as a subclass of UITableViewCell.

Build and run the app in your simulator once again with this command:

rake device_name="iPhone 4s"

Now when you tap the tasks button, you'll see a screen like this

Pomotion tasks controller with large empty cell

If you see multiple cells or an error message, make sure that you've deleted all of the Tasks from the database by calling Task.destroy_all followed by Task.save in the app console.

Wouldn't this be more user friendly if it had a title? Of course it would. In tasks_view_controller.rb, update the viewDidLoad method like so:

def viewDidLoad
  super
  self.title = "Tasks"
  tableView.registerClass(EmptyCell, forCellReuseIdentifier: EmptyCell.name)
end

This time, when you build and run the app you should see the title Tasks in the navigation bar on the tasks screen.

Now you need to give the users a means of adding new tasks to the list. First, create an "Add" button with the following method:

def add_button
  @add_button ||= UIBarButtonItem.alloc.
    initWithBarButtonSystemItem(UIBarButtonSystemItemAdd, target: self, action: nil)
end

Then, add the following line at the bottom of viewDidLoad:

navigationItem.rightBarButtonItem = add_button

This, of course, adds the button to the right of your navigation bar. Build and run the app again, and you'll see a new + button that makes it possible to add new tasks.

Pomotion - Tasks list with "+" button

Most users will understand that the plus button is how you add tasks, but just to be on the safe side leave a hint that helps the user see how to add tasks.

To do this, simply add a message to the empty cell that tells the user how to add a task. Add the following method to empty_cell.rb:

def initWithStyle(style, reuseIdentifier: reuseIdentifier)
  super.tap do
    self.styleClass = 'empty_cell'
    textLabel.text = "Click '+' to add your first task"
  end
end  

Of course, the label needs a little styling before it's complete, otherwise, it'll be ugly. So, add the following CSS rules to resources/default.css

.empty_cell label {
  top: 200px;  
  left: 0;  
  width: 320px;
  height: 30px;
  font-size: 18px;
  color: #AAAAAA;
  text-align: center;
}

Build and run the app again with rake device_name="iPhone 4s". The tasks screen should now look like this:

Tasks screen with empty cell plus prompt styled

Looking good!

Creating New Tasks

Okay, so far you've got the framework mostly there, but the only way to add tasks is a tad complex for the average user.

So, in tasks_view_controller.rb add a UIAlertView property under add_button to prompt the user to enter the new task's name:

def task_alert_view
  @task_alert_view ||= UIAlertView.alloc.initWithTitle("Add A Task", 
    message: "Insert the name of the task below",
    delegate: self, cancelButtonTitle: "Add", otherButtonTitles: nil).tap do |alert|
      alert.alertViewStyle = UIAlertViewStylePlainTextInput
  end
end

By setting the alertViewStyle property to UIAlertViewStylePlainTextInput, you make it so the alert view contains a text field that the user can populate and submit.

All you need to do now is define an action to show this alert view, so add the following method:

def add_button_tapped(sender)
  task_alert_view.show
end

Then, update the action: parameter in add_button to call add_button_tapped::

def add_button
  @add_button ||= UIBarButtonItem.alloc.
    initWithBarButtonSystemItem(UIBarButtonSystemItemAdd, target: self, action: 'add_button_tapped:')
end

Before this will work as expected, you'll have to implement the UIAlertViewDelegate method alertView:clickedButtonAtIndex in TasksViewController.

Add the following at the bottom of the implementation:

# = UIAlertViewDelegate =

def alertView(alert_view, clickedButtonAtIndex: index_path)
  text_field = alert_view.textFieldAtIndex(0)
  if !text_field.text.to_s.empty?
    create_new_task(name: text_field.text)
    tableView.reloadData      
    text_field.text = ''
  end
end

private

def create_new_task(attributes)
  Task.create(attributes)
  Task.save
end

This method first assigns the text field from the alert view to an instance variable and checks if it's text property has a value.

If it does, the controller creates a new Task record by calling create_new_task, a private method that creates a new record and commits the changes to the database.

Then, you reload the tableView to show the newly added task in the list, and reset the text_field's text property back to an empty string.

Build and run the app once again, and add a new task to the tasks list:

Tasks screen with new task alert

But what happens when you add a task? Where does it go?

The tasks in your database won't display on screen yet, because the tableView cells haven't been configured to display them. To do that, create a new file in app/views named task_cell.rb.

touch app/views/task_cell.rb

Just like EmptyCell, TaskCell should be a subclass of UITableViewCell. Open task_cell.rb and add the following:

class TaskCell < UITableViewCell

end

And also add the following lines to TaskCell

def initWithStyle(style, reuseIdentifier: reuseIdentifier)
  super.tap do
    self.styleClass = 'task_cell'
  end
end

def configure_for_task(task)
  textLabel.text = task.name
end

def prepareForReuse
  super
  textLabel.text = ''
end 

Here you redefine initWithStyle:reuseIdentifier and set a styleClass for the cell, just like you did in EmptyCell. configure_for_task lets you set the value of the cell's textLabel from TasksViewController.

prepareForReuse should be familiar to you. In this case, you're setting the text property of the textLabel inherited from UITableViewCell back to an empty string, so the cell may be reused again by the table view. In this case, it's not strictly necessary, although it's always good practice.

Go back to tasks_view_controller.rb and tell your tableView about TaskCell the same way you did with the EmptyCell class, by updating viewDidLoad to the following:

def viewDidLoad
  super
  tableView.registerClass(EmptyCell, forCellReuseIdentifier: EmptyCell.name)
  tableView.registerClass(TaskCell,  forCellReuseIdentifier: TaskCell.name)
  navigationItem.rightBarButtonItem = add_button
end

Finally, update tableView:cellForRowAtIndexPath: to return a TaskCell if there are tasks present.

def tableView(table_view, cellForRowAtIndexPath: index_path)
  if todays_tasks.any?
    task = todays_tasks[index_path.row]
    table_view.dequeueReusableCellWithIdentifier(TaskCell.name).tap do |cell|
      cell.configure_for_task(task)
    end
  else
    table_view.dequeueReusableCellWithIdentifier(EmptyCell.name)
  end
end

Build and run the app again. Now the task list will update with new tasks as you add them.

 Tasks list populated with a couple of tasks

Things are really coming together.
Celebrate all the tasks!

Selecting the Current Task

So as of now, the app functions, but there's always more you can do to kick it up a notch. In this case, you could display the name of the current task on the timer screen. That would certainly help the user stay focused on the current task.

To get this working, you'll first need to add a column to the tasks table to identify which task is the current one. Update the table by defining the new schema in the schemas directory.

Create a new file named 0002_add_current_to_tasks.rb

touch schemas/0002_add_current_to_tasks.rb

Edit this file in your text editor so it looks like this:

schema "0002 add current to tasks" do
  entity "Task" do
    string :name, optional: false
    boolean :current, default: false, optional: false
  end
end

This file is very similar to the previous schema file you created.

In fact, there are only two differences: The name of the schema 0002 add current to tasks and the additional line where the current column is defined. In this case, it's a boolean column with a default value of false, which must be set for the Task record to be valid.

That's all you need to do!

The next time you run rake device_name="iPhone 4s" your app should automatically update the tasks table to match the new schema version.

Add a label to the main screen to display the name of the current task. In main_view.rb add this new property:

  def task_name_label
    @task_name_label ||= UILabel.alloc.initWithFrame(CGRectZero).tap do |label|
      label.styleClass = 'task_name_label'
      label.text = "n/a"
    end
  end

Again, nothing too crazy here. task_name_label returns a UILabel where text is pre-populated with n/a (not available, as there may not be a current task set when the screen is loaded). The label is given a style class task_name_label, so that you can easily define how it looks using CSS.

Update initWithFrame to add the task_name_label as a subview:

def initWithFrame(frame)
  super.tap do 
    self.styleId = 'main_view'
    addSubview(timer_label)
    addSubview(timer_button)
    addSubview(task_name_label)
  end
end

Then you need to open resources/default.css and add some new CSS:

.task_name_label {
  top: 400px;
  left: 60px;
  width: 200px;
  height: 30px;
  font-size: 16px;
  color: #333333;
  text-align: center;
}

Build and run the app once again:

rake device_name="iPhone 4s"

Now you should see a label with n/a showing underneath the main timer:

Main screen with current task name label

The text for this label needs to be populated with the name of the current task whenever this screen displays. That way, it'll update when the current task changes.

In your text editor, open app/models/task.rb and add the following class methods:

# 1
def self.current=(task)  	
  self.current.current = false if self.current
  # 2
  task.current = true
  # 3
  self.save
  # 4
  @current = task
end
  
# 5  
def self.current
  @current ||= where(current: true).first
end

# 6
def self.reset_current
  @current = nil
end

Let's review this section by section:

  1. Task::current= is a setter method that sets the current Task. First, it checks for any existing current tasks and sets their current property to false.
  2. This sets current on the given task to true.
  3. This saves the changes -- remember, CDQ won't commit any changes to the database unless you call the save method.
  4. Finally, Task::current= sets an instance variable to store the new current Task.
  5. Task::current returns the value of the @current instance variable if it's set. If not, it queries the database to find the first Task where the current column is set to true, and then it returns that.
  6. Lastly, Task::reset_current sets the value of the @current back to nil, ensuring that the new current task reloads the next time Task::current is called.

Go back to the tasks_view_controller.rb and implement the UITableViewDelegate method tableView:didSelectRowAtIndexPath, just beneath tableView:heightForRowAtIndexPath:

def tableView(table_view, didSelectRowAtIndexPath: index_path)
  Task.current = Task.all[index_path.row]
  navigationController.popViewControllerAnimated(true)
end

This block makes it so that when a user taps on a task cell in the tasks screen, it sets the current task, updates that record in the database, and then the app navigates back to the main screen.

There's one small problem here: Currently the user could select the EmptyCell. You'll prevent it from being selected by implementing the following:

def tableView(table_view, shouldHighlightRowAtIndexPath: index_path)
  todays_tasks.any?
end

Give this a try; build and run the app in the simulator.

rake device_name="iPhone 4s"

You won't see any change on the screen yet, and that's because you still need to add that. But if you type the following in Terminal while the simulator is running, you should see that it's working behind the scenes.

(main)> Task.current
=> <Task: 0xb6a9a60> (entity: Task; id: 0xb63bfa0 <x-coredata://B0AEB5CD-2B77-43BA-B78B-93BA98325BA0/Task/p5> ; data: {
    current = 1;
    name = "Write RubyMotion tutorial";
})

Updating the label to show the new task is really simple. In main_view_controller.rb define a wrapper method that returns the task_name_label for the MainView. Add this just below tasks_image:

def task_name_label
  view.task_name_label
end

Then, insert this private method at the bottom of the MainViewController implementation:

def set_task_name_label_from_current_task
  if Task.current
    task_name_label_text = Task.current.name
  else
    task_name_label_text = "n/a"
  end
  task_name_label.text = task_name_label_text
end

Finally, implement viewDidAppear: for MainViewController and call set_task_name_label_from_current_task inside there. Remember to call super inside this method first with this:

def viewDidAppear(animated)
  super
  set_task_name_label_from_current_task
end

Build and run the app once again to see your changes take effect. This time, when you select a new task, the task_name_label updates with the name of your chosen task.

Main screen with task name label populated

Editing the Tasks List

You're almost done; there's just one more thing to add, and this feature is complete!

The user should also be able to remove tasks from the list once they're complete. After all, there's nothing more satisfying than cleaning up your TODO list once your work is complete.

Add this by implementing the UITableViewDelegate methods: tableView:canEditRowAtIndexPath: and tableView:commitEditingStyle:forRowAtIndexPath: in tasks_view_controller.rb like so:

# 1
def tableView(table_view, canEditRowAtIndexPath: index_path)
  todays_tasks.any?
end  

# 2
def tableView(table_view, commitEditingStyle:editing_style, forRowAtIndexPath: index_path)
  case editing_style
  when UITableViewCellEditingStyleDelete
    delete_task_at_index(index_path.row)
    if todays_tasks.any?
      tableView.deleteRowsAtIndexPaths([index_path], 
          withRowAnimation: UITableViewRowAnimationFade)
    else
      tableView.reloadRowsAtIndexPaths([index_path], 
          withRowAnimation: UITableViewRowAnimationFade)      
    end
  end
end

What's going on here?

  1. tableView:canEditRowAtIndexPath: simply returns true if there are Tasks available to tell the controller that the tasks may be edited.
  2. tableView:commitEditingStyle:forRowAtIndexPath is more involved, and I'll talk you through it:
  • First, it checks the value of editing_style. Here you're only dealing with the value UITableViewCellEditingStyleDelete. If that's the case, the task is deleted by calling delete_task_at_index (it's not defined yet).
  • If there are still other tasks in the database, then this row is deleted from the table view with a nice UITableViewRowAnimationFade animation, otherwise, the table reloads to show one EmptyCell with a UITableViewRowAnimationFade animation.

Now define delete_task_at_index at the bottom of TasksViewController, just before the closing end:

def delete_task_at_index(index)
  task = todays_tasks[index]
  task.destroy
  Task.save
  Task.reset_current
end

This private method fetches the task to be destroyed by its index in todays_tasks, marks it for deletion from the database, and then commits the changes to the database. The last line calls the reset_current method you defined in Task to clear the current task.

Build and run your app one more time and try adding and removing some tasks

rake device_name="iPhone 4s"

Tasks screen with deleted task

Where To Go From Here?

That's it. You've successfully implemented Core Data with your RubyMotion app. Hopefully you found it refreshingly straightforward.

Of course, there's a lot more you can achieve with Core Data and RubyMotion than what you've covered here. I recommend you check out the fully documented source code, as well as CDQ's README and documentation.

I'd love to hear about how you've implemented Core Data with RubyMotion, anything you discover while you work through this tutorial and any questions that bubble up as you play around around. Leave your notes, comments and questions below!