9781449380373
_using_ruby_3rd_party_libraries.html

Chapter 13. Using Ruby Third-Party Libraries

In Chapter 12, Objective-C Code in MacRuby Apps, you saw how to write or include Objective-C libraries in MacRuby apps using frameworks or dynamic libraries. This is very useful for existing Cocoa code or low-level Objective-C wrappers. However, the amount of free open source Ruby libraries is quite impressive. As a matter of fact, there are currently more published Ruby libraries than Perl libraries! This chapter explains how to access these Ruby resources.

RubyGems

Ruby libraries are usually packaged as gems, which are library packages used by the RubyGems standard library. A gem includes its own library files, defining a version number and dependencies on other libraries, if any. You can look for gems at the RubyGems site. In C Ruby, the default Ruby implementation, use the gem command-line tool to install gems on your system. In MacRuby, the gem command line is prefixed to avoid conflicting with the C Ruby command. Very much like irb is available as macirb, gem for MacRuby is available as macgem.

You can use the macgem command-line tool the same way you use gem. In this case, I am going to locally install the dispatch gem, a MacRuby-specific gem that adds an abstraction layer on top of the GCD API:

$ sudo macgem install dispatch

Notice that I’m installing the gem as a superuser (by prefixing the command with sudo), because the gems will be installed in the /Library/Frameworks/MacRuby.framework folder, which is owned by the root account.

While this is very useful, you can’t really expect your app users to install MacRuby so they can get access to the macgem command line to manually install your app’s dependencies. So what we need to do is to embed all the third-party dependencies with our app so nothing is required on the end user’s side and all the versions are locked.

There are many different ways to do that, but we will start with the method that is simplest and probably best in most cases.

MacRuby Deploy

MacRuby ships with a command-line tool called macruby_deploy, which you can use to prep an application to be deployed to the users. This tool is used in the Xcode MacRuby template targets to compile Ruby source code and embed MacRuby inside an app. But the tool can do much more than that. A quick call to the command shows what it is capable of:

$ macruby_deploy --help
Usage: macruby_deploy [options] application-bundle
        --compile                    Compile the bundle source code
        --embed                      Embed MacRuby inside the bundle
        --no-stdlib                  Do not embed the standard library
        --stdlib [LIB]               Embed only LIB from the standard library
        --gem [GEM]                  Embed GEM and its dependencies
        --bs                         Embed the system BridgeSupport files
        --verbose                    Log all commands to standard out
    -v, --version                    Display the version

Because you’ll be working in Xcode, you’ll normally just modify the Xcode target to change the options passed to the macruby_deploy command line.

To illustrate how to embed a gem in a MacRuby app written in Xcode, we’ll use a very simple Cocoa app written in Xcode using the Nokogiri gem. This gem is a C extension (a Ruby gem using a C wrapper), giving a nice Ruby API around libxml. Our app won’t do much, but it should be enough for you to understand how to embed a gem.

Start a new Xcode app or download the sample from the book git repository. The only thing this app will do is to parse an XML string and find content inside it. Once the content is found, we will print it to the console. Here is the content of my AppDelegate.rb file:

require 'rubygems'
require 'nokogiri'

class AppDelegate
  attr_accessor :window
  def applicationDidFinishLaunching(a_notification)
    nokogiri_example
  end

  def nokogiri_example
    xml = "
    <root>
      <shows>
        <show>
          <name>Battlestar Galactica - 2004</name>
            <characters>
            <character species='human'>William Adama</character>
            <character species='human'>Laura Roslin</character>
            <character species='human'>Kara 'Starbuck' Thrace</character>
            <character species='human'>Lee 'Apollo' Adama</character>
            <character species='human'>Dr. Gaius Baltar</character>
            <character species='cylon'>Number Six</character>
            <character species='cylon'>Number Eight</character>
          </characters>
        </show>
      </shows>
    </root>"

    # Parse the XML string
    xml_obj  = Nokogiri::XML(xml)

    # find all characters using xpath and extract the node contents in an array
    all_characters = xml_obj.xpath("//characters/character").map(&:content)
    puts "BSG main characters: #{all_characters}"
    # => BSG characters: ["William Adama", "Laura Roslin", "Kara 'Starbuck' Thrace",
    # "Lee 'Apollo' Adama", "Dr. Gaius Baltar", "Number Six", "Number Eight"]

    # Now do the same but filter the cylons using the species attribute.
    cylons = xml_obj.xpath("//characters/character[@species='cylon']").map(&:content)
    puts "Cylons: #{cylons}"
    # => Cylons: ["Number Six", "Number Eight"]
  end
end

The code is quite simple, particularly if you have used XPath as part of a jQuery application or elsewhere. Look inside the nokogiri_example method. I start by defining a string containing an XML document with some information about the Battlestar Galactica TV show. I then feed that XML document to Nokogiri and assign the returned object to a variable. I use an XPath query, embedding this variable, to find all the character nodes. The xpath method call returns an array that I pass directly to a Ruby map method call, looping through each node and calling the content method. All the character names are now stored in the all_characters variable, which I print to the console. I then use another xpath query to filter the characters based on their XML node attributes. Nokogiri offers a really nice CSS3 selector API, in case you are not familiar with XPath.

To work properly, my code obviously needs Nokogiri, and that’s why I require RubyGems and Nokogiri at the top of the file.

Warning

Unlike Ruby version 1.9, the current version of MacRuby doesn’t automatically load RubyGems for performance reasons. You need to require it manually.

However, unless you installed the Nokogiri gem before, your code won’t work. So let’s go ahead and install Nokogiri:

$ sudo macgem install nokogiri

Now you can compile your project. Make sure the normal schedule (not Deployment) is selected and build the project. If everything goes well, you should see the two print statements in the debug window.

Now, click the project name in the Project Navigator and edit the Deployment target to add the extra parameters (Figure 13.1, “macruby_deploy arguments change in the target”).

Figure 13.1. macruby_deploy arguments change in the target

macruby_deploy arguments change in the target

Note

You always have to use the --embed option in conjunction with the --gem option, otherwise the gem embedding won’t work.

Now that the target is properly set, change the scheme to Deployment and build your project. If you look at the package contents (right-click the .app file and choose Package Contents) and dig into PackagedGemexample.app/Contents/Frameworks/MacRuby.framework/Versions/macruby version/usr/lib/ruby/site_ruby/1.9.2, you will notice a folder called nokogiri.

What’s also nice with this approach is that you can remove the require statement for rubygems from your code, making your app’s booting time slightly faster.

Finally, if you need to embed multiple gems, you can use the --gem option multiple times. Here is an example using the command line:

$ macruby_deploy --compile --embed  --gem nokogiri --gem dispatch MyApp.app
Site last updated on: November 9, 2011 at 10:00:57 AM PST