iOS Development in Ruby with RubyMotion, Part III: Backend as a Service

Brian Sam-Bodden
  • June 2014
  • iOS
  • RubyMotion

In the first two installments of this series I introduced RubyMotion, a product that enables the development of iOS (and OS X) native applications using the Ruby programming language. We set out to show that native mobile development could be approached in a modern, agile way by borrowing techniques (such as Test-Driven Development) from the web development community, in particular those techniques and practices that I've learned in almost a decade working with the Ruby on Rails Web Development Framework.

In this installment I aim to continue the development of the sample ToDo application we built and have aptly renamed Okonawa (行わ) which translates roughly to Done. In the next few pages we’ll work on connecting our simple iOS application to a Parse backend in order to provide a server-side component to our mobile offering.

Where We've Been So Far

Figure 1 and Figure 2 show the state of the ToDo application as we left it at the end of Part II.

The application provided the user the ability to create a list of items with a due date and to mark those items as "Done".

List Todos

Figure 1. ToDo App - List Todos

Create/Edit a Todo

Figure 2. ToDo App - Create/Edit a Todo

In that incarnation of the application all the data for the items was stored locally and there was no concept of a user or account.

Parse: Backend as a Service

Traditionally, if we wanted to provide a way for users to store their data outside the confines of their phones or tablets we would have to build a service to maintain that data. Most likely we would use a RESTful backend returning JSON. Although we could quickly create a basic version of such an application, it would be far from being production ready. The deeper you dig into the development effort the quicker you realize that it is anything but trivial. In the early stages of a mobile application's life the endeavor of building a robust backend might derail the application from ever reaching an audience.

To quote Yukihiro "Matz" Matsumoto, the creator of the Ruby programming language:

If you have a good interface on your system, and a budget of money and time, you can work on your system. If your system has bugs or is too slow, you can improve it. It won't matter if it is a work of the highest craftsmanship on the inside. If your system has a bad interface, no one will use it. So the interface or surface of the system, whether to users or other machines, is very important.

- Yukihiro Matsumoto, Matz on Craftsmanship, A Conversation with Yukihiro Matsumoto

Backend as a Service (BaaS), also known as Mobile Backend as a Service (MBaaS), provides the basic infrastructure and application services to allow you to concentrate on your applications' user interfaces.

One such service, and the focus of this article, is Parse. Parse provides a core set of services including user accounts, model storage, authentication with OAuth, server-side custom code, push notifications, social integration, analytics and simple web presence.

Parse provides mobile applications access to their services via a REST API, or natively via SDKs for Android, iOS/OS X, JavaScript, .Net, and Unity.

Creating a Parse.com Application

To get started with Parse we must first create a free account at https://parse.com after which you’ll be directed to the Parse dashboard. Under the Parse dashboard you can manage your parse applications and get an overview of certain metrics like number of requests, amount of file storage used and push notifications created.

Let’s create a new Parse application. Click the Create New App button as shown next:

Creating a Parse App

Figure 3. Creating a Parse App

Once your new App has been created you'll be presented with your App's keys. Save the Application ID and Client Key values, as we'll use them later to Parse-enable our RubyMotion application.

Parse App Keys

Figure 4. Parse App Keys

Downloading and Installing the Parse iOS SDK

To Parse-enable our RubyMotion application we start by downloading the Parse SDK, which can be found at https://www.parse.com/docs/downloads. At the time of this writing the version available is 1.2.19, and is contained in the file parse-library-1.2.19.zip. Unzip the file and copy the contents to your RubyMotion project directory under a /vendor directory (/vendor/Parse.framework).

Adding a 3rd-party library such as the Parse framework is referred to as vendoring. (Sansonetti, chap. 2, sec. 6) To make the dependency available to our project we use the vendor_project method available in the scope of the application setup block as shown in Listing 1:

Vendoring the Parse Framework

Motion::Project::App.setup do |app|
  # ...
  
  app.vendor_project('vendor/Parse.framework', :static, :products => ['Parse']) (1) (2) (3)
  
end
  1. The first argument is the path to the Parse.framework directory we’ve created.
  2. The second is a Ruby symbol representing the project type (which can be :xcode or :static). The Parse.framework is a static dependency.
  3. The third argument is a hash containing the additional configuration parameters. In our case :products which is an array of static library names.

The Parse SDK has some core dependencies that must be satisfied. We add those to the frameworks property as shown in Listing 2:

Parse Framework Dependencies

Motion::Project::App.setup do |app|
        
  # ...
        
  app.frameworks += [
    'AudioToolbox',
    'CFNetwork',
    'CoreGraphics',
    'CoreLocation',
    'MobileCoreServices',
    'QuartzCore',
    'Security',
    'StoreKit',
    'SystemConfiguration'
  ]
end

The next step is to initialize the Parse framework within our App (see Listing 3). We do this in the didFinishLaunchingWithOptions callback within our app_delegate.rb file by invoking the setApplicationId method passing the Application ID and Client Key values (the ones we saved when we created the Parse application):

Initializing the Parse Framework

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    Parse.setApplicationId("parse_app_id", clientKey: "parse_client_key")
        
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
        
    @todos_controller = TodosController.alloc.initWithNibName(nil, bundle:nil)
        
    @window.rootViewController =
      UINavigationController.alloc.initWithRootViewController(@todos_controller)
        
    @window.makeKeyAndVisible
        
    true
  end
end

Implementing Sign up and Log in

The next step is to use one of Parse’s most useful features: its login and signup views. Parse provides two controllers, PFLogInViewController and PFSignUpViewController, which provide ready made ways to log in and register using Parse’s PFUser class under the covers.

On the viewDidAppear callback in the TodosController (todos_controller.rb) we’ll invoke the display_login method if there is no user currently logged in and we are not running in test mode.

Using Parse’s View Controllers

def viewDidAppear(animated)
  display_login unless (PFUser.currentUser || RUBYMOTION_ENV == 'test')
end
        
def display_login
  @login = PFLogInViewController.new
  @login.fields = PFLogInFieldsUsernameAndPassword | PFLogInFieldsLogInButton |
                  PFLogInFieldsSignUpButton
  @login.delegate = self
  @login.signUpController.delegate = self
  self.presentModalViewController(@login, animated:true)
end

In iOS we have the ability to present view controllers in order to interrupt the current workflow and display an alternate interface. The display_login method breaks the flow by instantiating a PFLogInViewController. The view is configured to display username/password fields, a login button and a sign up button. The delegate properties of both the PFLogInViewController and its contained PFSignUpViewController (signUpController) are set to the TodosController, which will allow us to handle events from both controllers in the TodosController.

Handling a Successful Log In

def logInViewController(logIn, didLogInUser:user)
  @login.dismissModalViewControllerAnimated(true)
  load_todos
end

Upon successful login we dismiss the login view and invoke the load_todos method. We implemented the load_todos method previously using the MotionModel gem. The load_todos loads all the Todo objects created previously (in the seed method implemented in the AppDelegate class). We also removed the call to load_todos in the viewDidLoad handler and replaced it with a single line to initialize the @todos array (@todos = []) in order to prevent the Todos from being loaded before we have successfully logged in.

load_todos Method Implemented with MotionModel

def load_todos
  @todos = Todo.all
end

Launching the application reveals the ‘PFLogInViewController’ as shown in Figure 5:

Parse’s PFLogInViewController

Figure 5. Parse’s PFLogInViewController

We can further customize the login dialog by adding PFLogInFieldsPasswordForgotten value to the fields property in the display_login method as shown in Listing 7:

Customizing the Login Dialog

@login.fields = PFLogInFieldsUsernameAndPassword | PFLogInFieldsLogInButton |
          PFLogInFieldsSignUpButton | PFLogInFieldsPasswordForgotten

The result of adding PFLogInFieldsPasswordForgotten flag is shown in Figure 6.

Adding PFLogInFieldsPasswordForgotten

Figure 6. Adding PFLogInFieldsPasswordForgotten

On the UI the change is almost imperceptible, we get a small “Forgot?” tab next to the login and password fields. It is the amount of work we are saving ourselves in terms of server-side development that is remarkable. Recall the all too familiar user story, “As a User I Should be able to reset my password.”

From the Parse Dashboard (https://www.parse.com/apps) select the created application and navigate to the Data Browser tab (https://www.parse.com/apps/okonawa/collections):

Okonawa Data Browser

Figure 7. Okonawa Data Browser

From the running application let’s register a new user as show in Figure 8:

Register a New User

Figure 8. Register a New User

After registering the user we should see that a new User has been created. We should also be able to log in to the application with the newly created user’s credentials.

Newly Registered User

Figure 9. Newly Registered User

Implementing Log Out

Once a user is logged in we need to give them the choice to log out. We’ll provide our users with a btn:[Logout] button on the left of the main navigation bar as shown in Listing 8:

Logout Button

class TodosController < UITableViewController
        
  def viewDidLoad
    super
    self.title = "Okonawa"
        
    @todos = []
        
    add_todo_button = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemAdd, target:self, action:'add_todo')
    log_out_button = UIBarButtonItem.alloc.initWithTitle("Logout", style: UIBarButtonItemStyleBordered, target:self, action:'logout')
        
    self.navigationItem.rightBarButtonItem = add_todo_button
    self.navigationItem.leftBarButtonItem = log_out_button
  end
        
  # ...
end

Listing 9 shows the logout method which is triggered by the logout button. The logout method invokes Parse’s PFUser logOut class method, resets the array of Todo objects (@todos), refreshes the table of todos (to properly clear it) and redisplays the login view.

Logout and refresh_display

class TodosController < UITableViewController
  # ...
        
  def logout
    PFUser.logOut
    @todos = []
    refresh_display
    display_login
  end
        
  def refresh_display
    self.tableView.reloadData
  end
        
end

Implementing Facebook OAuth Sign Up and Log In

Parse also covers another popular feature when it comes to modern mobile applications: social media integration. Let’s provide Okonawa users with a way to register and log in to the application using their Facebook credentials.

To start using Facebook with Parse, you need to:

  • Register for a Facebook developer account at https://developers.facebook.com
  • Create a Facebook application for the Okonawa application
  • Integrate the Facebook SDK with your RubyMotion application

Integrating the Facebook SDK with CocoaPods

Previously when we added the Parse SDK as a dependency to our RubyMotion application we followed the vendoring procedure. Until recently this was the only way to maintain 3rd-party dependencies in a RubyMotion iOS application (or an XCode one); to download, manually copy and upgrade by following the same manual procedure.

In most modern development environments we have grown accustomed to declarative, version-based dependency management tools. In the Ruby world we use Bundler which allows us to declare Gem dependencies.

Fortunately for us, a new project has been created just for the purpose of managing XCode and RubyMotion project dependencies: CocoaPods. CocoaPods is a Ruby-based tool that can manage library dependencies by declaring them in a text file called a Podfile.

To use CocoaPods within a RubyMotion application you use the motion-cocoapods gem located at https://github.com/HipByte/motion-cocoapods. It can be installed with Bundler like any other Ruby Gem. Let’s add it to our project’s Gemfile and bundle the application.

Adding motion-cocoa to Okonawa’s Gemfile

gem 'motion-cocoapods'

We also need to require the Gem in the project’s Rakefile (unless you are already calling Bundler.require).

Requiring motion-cocoa in Okonawa’s Rakefile

require 'motion-cocoapods'

In order to add the Facebook SDK to our RubyMotion project we first need to find out if the library is maintained under CocoaPods. The easiest way to do this to search directly on the CocoaPods site (paste http://cocoapods.org/?q=Facebook in your browser). At the time of this writing 56 matches are returned, the third one being Facebook-iOS-SDK (v3.13.0). By the way, as of this writing, the Parse library is not available via CocoaPods, which is why we used the manual vendoring procedure.

CocoaPods Search

Figure 10. CocoaPods Search

Using motion-cocoapods we can declare our Pods directly in the Rakefile as shown in Listing 12:

Declaring Pods Dependencies in Rakefile

Motion::Project::App.setup do |app|
  # ...
  app.pods do
    pod 'Facebook-iOS-SDK', '~> 3.13.0'
  end
end

Like many dependency management systems, CocoaPods configures a master repository locally to avoid duplication of libraries (which can potentially be very large). After bundling the application, you might need to run the pod setup command:

Setting Up CocoaPods Master Repository

/> pod setup
Setting up CocoaPods master repo
Setup completed (read-only access)

The final step is to install the dependencies into our RubyMotion project. The motion-pods Gem makes a Rake task available to the project just for this purpose, rake pod:install:

Installing Facebook-iOS-SDK

/> rake pod:install
Updating spec repo `master`
Already up-to-date.
Resolving dependencies of
Comparing resolved specification to the sandbox manifest
Analyzing dependencies
Downloading dependencies
Installing Facebook-iOS-SDK (3.13.0)
Generating Pods project

You should now see the Pods' dependencies installed under the vendor/Pods directory. Adding new dependencies is now as simple as adding a new pod dependency to the Rakefile and re-running the rake pod:install task.

After registering for a Facebook developer account and creating an application you were assigned a Facebook App Id. The next step we need to take is to add a couple of keys based on the Facebook App Id to our application’s information property list file (Info.plist), which in RubyMotion is controlled via the Motion::Project::App.setup block using the app.info_plist hash:

Configuring Your Facebook App Id

Motion::Project::App.setup do |app|
  # ...
  app.info_plist['FacebookAppID'] =  ''
  app.info_plist['URL types'] = { 'URL Schemes' => 'fb' }
end

We are now ready to enable the App’s Facebook login and registration by adding the PFLogInFieldsFacebook flag to the configuration of the PFLogInViewController instance in the display_login method.

Adding the PFLogInFieldsFacebook Flag

@login.fields = PFLogInFieldsUsernameAndPassword | PFLogInFieldsLogInButton |
                PFLogInFieldsSignUpButton | PFLogInFieldsPasswordForgotten |
                PFLogInFieldsFacebook

Once the Facebook SDK is detected, the PFUser class becomes tightly integrated with it, making linking your users to their Facebook identities easy. Relaunching the application reveals the addition of the Facebook login button.

Facebook-enabled Login

Figure 11. Facebook-enabled Login

Clicking the Facebook login button brings up the Facebook login view:

Facebook Login Page

Figure 12. Facebook Login Page

Once the user enters their Facebook credentials they’ll be presented with the authorization view:

Facebook Authorization

Figure 13. Facebook Authorization

Associating the Todos with the Logged In User

The prior incarnation of the Todo class was implemented using the MotionModel gem for simple local storage. It also assumed that whoever was using the device was the user/owner of the Todos.

Parse-enabled Models with ParseModel

In this section we will replace MotionModel with ParseModel: “an Active Record implementation for Parse models on RubyMotion.” ParseModel is a light weight wrapper over Parse’s PFObject and PFUser classes. To get started we’ll add the ParseModel Gem to the Gemfile:

Adding ParseModel to the Gemfile

gem 'ParseModel'

And require it in the Rakefile:

Requiring ParseModel in the Rakefile

require 'ParseModel'

User Model

After we bundle the application we can create our first Parse enabled model: the User model. By simply including the ParseModel::User module we can now access the PFUser.currentUser as User.current_user:

Parse-enabled User Class

class User
  include ParseModel::User
end

The User model can be used directly to create and sign up a user as shown in Listing 20:

ParseModel User Class Usage

user = User.new
user.username = 'bsbodden'
user.email = 'bsbodden@integrallis.com  '
user.password = 'SeTeCAsTr0n0mY'
user.signUp

Todo Model

In order to store our Todos in Parse we’ll refactor the Todo class to include the ParseModel::Model module. Declare the fields to be stored using the fields class method and implement the new? and valid? methods to retain some of the functionality we’ve previously provided via MotionModel.

ParseModel Todo Class

class Todo
  include ParseModel::Model
        
  fields :name, :details, :due_date, :done, :owner
        
  def overdue?
    NSDate.new > self.due_date && !done
  end
        
  def new?
    objectId.nil?
  end
        
  def valid?
    !name.nil?
  end
end

Creating a New Todo

To provide a new Todo for the currently logged in User, we’ll refactor the add_todo method to create a blank Todo and set its owner property to the currently logged in user’s username (via the User.current_user.username property):

Creating and Associating a New Todo with the Current User

def add_todo
  todo = Todo.new
  todo.name = 'New Todo'
  todo.details = ''
  todo.due_date = NSDate.new.to_f
  todo.done = false
  todo.owner = RUBYMOTION_ENV != 'test' ? User.current_user.username : 'testuser'
        
  edit_todo(todo) unless RUBYMOTION_ENV == 'test'
        
  todo
end

Querying a User’s Todos

Now that we can create and save Parse-enable Todos we need to refactor the load_todos method to properly load the Todos for the currently logged in user.

ParseModel provides a query property on every ParseModel instance which wraps Parse’s PFQuery class. After creating a query using the Todo class, we’ll set the where clause of our query to find all Todos where the owner property matches the currently logged in user.

Setting the where Clause

def load_todos
  query = Todo.query
  query.whereKey('owner', equalTo: User.current_user.username)
  @todos = query.find
  refresh_display
end

Asynchronously Querying for Todos

The load_todos method works as advertised but closer inspection of the RubyMotion console reveals the following message:

RubyMotion Console Messages

Okonawa[66558:90b] Warning: A long-running Parse operation is being executed on the main thread.

Since querying for all of the User’s Todos could potentially be a time consuming operation we’ll use a Dispatch::Queue to run the operation off the main thread. Dispatch::Queue is part of Grand Central Dispatch (GCD) which “comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.” (Apple 2013)

In Listing 25 we refactor the load_todos method to work asynchronously. The call to Dispatch::Queue.concurrent returns one of the global concurrent priority queues. The subsequent call to asynch yields the passed block asynchronously. When the query returns we run a synchronous call (in the main thread) to refresh_display, which should repaint the Todos table.

Refactoring load_todos

def load_todos
  Dispatch::Queue.concurrent.async do
    query = Todo.query
    query.whereKey('owner', equalTo: User.current_user.username)
    @todos = query.find
        
    Dispatch::Queue.main.sync { refresh_display }
  end
end
Parse-enabled Okonawa

Figure 14. Parse-enabled Okonawa

Conclusion

We now have a fully cloud-enabled, multi-user mobile application with very little code. We learned how to take advantage of Parse, one of the new breed of Backend as a Service offerings. Using a BaaS you can shorten your time to market and focus your efforts where they would pay off best: your application’s user experience.

For those interested in continuing your exploration of iOS development with Ruby, make sure to follow the article series at http://integrallis.com/blog and keep in touch with the progress of the Okonawa application at https://github.com/integrallis/okonawa.

References

Share