Testing Macruby and Objective C Together

MacRuby logo
When working on Macruby apps, there is a wealth of useful Cocoa code snippets and open source frameworks online, but they are primarily in Objective C. Macruby can use Objective C code transparently, but if we want to access that code in individual unit tests we need to set up our project so that the Objective C parts are compiled and accessible to the test.

One way we can do this is to use embedded frameworks to hold some (or all) of the code in our app.
This little tutorial describes how to set up your a Macruby project so that you can run your unit tests both from Xcode or from the command line, and have access to any Objective C classes your project uses when running individual tests from the command line.

This Mac Dev Center article talks about creating Frameworks.

Here are the steps I followed to organize third-party code into framework and build a testing target that used those frameworks (the information came from this tutorial on Cocoadev and the Macruby TDD tutorial).

These steps assume you have used the one of the Macruby templates current at the time I wrote this, which include a “Unit Tests” target that has a Run Script phase that calls Tests/run_suite.rb.

  • Organize Your Code

    If it is not already, organize the code you wish to build into an embedded framework into a directory and either include it in your project as a folder reference or create a corresponding group. Xcode does not require you to do this, but it makes life much easier. Let’s call it MyFramework for this example.

  • Create A New Framework

    Create a new Cocoa Framework Build Target in the project (you do this by right-clicking on Targets and choose Add > New Target…, and then choosing the Cocoa > Framework template). Then enter MyFramework and click “Finish”. The resulting target will have the icon for a framework. There should also be an entry under Products called MyFramework.framework, which will be red because it hasn’t been built yet.

  • Set the Installation Directory

    Select the “Build” tab of the Info Pane for the new target. Find the “Installation Directory” setting in the “Deployment” setting group.

    The default value is

    $(HOME)/Library/Frameworks

    Change it to

    @executable_path/../Frameworks

  • Link the Framework in the Application Target

    Next you need to make sure the application target links your framework. To do that, drag MyFramework.framework from the Products group into the “Link Binaries With Libraries” phase of the application target (the target with the same name as your Xcode project). In a new Macruby project, this will already have entries for Cocoa.framework and Macruby.framework.

  • Copy the Framework into the Application

    You also need to have the application target copy the built framework into the application. To do this, add a new “Copy Files” build phase to the application target. You do this by right-clicking the application target and selecting Add > New Build Phase > New Copy Files Build Phase. Then drag MyFramework.framework from the Products group into the new build phase.

  • Add Framework as a Dependency

    Now you need to make sure that building the application or running the unit tests also builds the framework if it needs building, by adding MyFramework as a dependency to the application target and the “Unit Tests” target. To do that, do a “Get Info” on each target, and in the General pane click the + under Direct Dependencies and add MyFramework.

  • Accessing the Framework in Unit Tests

    Next we need to tell our unit tests where to find the framework, which we can do by adding:

    # Tell MacRuby where to find our framework
    ENV['DYLD_FRAMEWORK_PATH'] ||= ENV['BUILT_PRODUCTS_DIR']
    

    to the Tests/run_suite.rb file.

In order to run tests from the command line that have dependencies on this , we can do this:

export DYLD_FRAMEWORK_PATH="${HOME}/builds/Debug" # or whatever your build directory settings are

If the code in the framework changes, we can rebuild them in XCode, or use the command line:

xcodebuild -project MyApp.xcodeproj -target MyFramework

Programming Cocoa With MacRuby: Part One

In this series of articles, I will work through the Pragmatic Programmer book Programming Cocoa with Ruby by Brian Marick, porting the examples to MacRuby.

Examples from Chapter 2

The first few examples are almost identical. require 'osx/cocoa' becomes framework 'cocoa'. MacRuby lets us use the familiar SomeObject.new instead of SomeObject.alloc.init, and there is no need to explicitly inherit from NSObject, since NSObject replaces Object in the standard class hierarchy:

irb(main):011:0> class Foo;end;Foo.ancestors
=> [Foo, NSObject, Kernel]
irb(main):012:0>

most-basic-app.rb

#!/usr/bin/env macruby
framework 'cocoa'
NSApplication.sharedApplication
NSApp.run

no-ui.rb

#!/usr/bin/env macruby
framework 'cocoa'

class AppDelegate
  def applicationDidFinishLaunching(aNotification)
    puts "#{aNotification.name} makes me say: Hello, world"
  end
end

our_object = AppDelegate.new
NSApplication.sharedApplication
NSApp.setDelegate(our_object)
NSApp.run

Pages: 1 2 3

“Ruby Rocks” ported to MacRuby

MacRuby logoWhen I first started playing around with writing Cocoa apps using Ruby, I was using RubyCocoa. I did the Ruby Rocks tutorial and was pretty impressed by how easy it was to write graphics apps using Ruby and Cocoa. I quickly discovered MacRuby and decided to use it going forward. The first thing I did was to port my finished Ruby Rocks to MacRuby, so I’m posting in as a MacRuby sample.

Possibly at some point I will write up the conversion to MacRuby as a tutorial. Anywhere, here it is. You can see that I kept playing for a while after finishing the tutorial…

Download

A Dirt-Simple Hierarchical, Configurable Logger for Ruby

log4r is currently broken in MacRuby, and I was desperate for a log4j-ish logger in the MacRuby app I’m working on, so I put together this very basic hierarchical logger based on the standard ruby logger. I’m posting it in case anybody else has the same problem with MacRuby, or wants something that is a little simpler than log4j to configure.

require 'logger'
require 'forwardable'

class LogGroup < Logger
  extend Forwardable

  def_delegators :@child_loggers, :[]=, :has_key?

  attr_accessor :child_loggers

  def initialize(logdev, shift_age = 0, shift_size = 1048576)
    super
    @child_loggers = {}
  end

  def self.init_with_config(config)
    logdev = config['device'] || STDOUT
    shift_age = config['shift_age'] || 0
    shift_size = config['shift_size'] || 1048576
    root = LogGroup.new(logdev, shift_age, shift_size).with_name('root')

    config['loggers'].each do |name, level|
      name_parts = name.split('.')
      log_group = root
      name_parts.each do |name_part|
        unless log_group.has_key?(name_part)
          log_group[name_part] = LogGroup.new(logdev, shift_age, shift_size).with_name("#{log_group.progname}.#{name_part}")
        end
        log_group = log_group[name_part]
      end
      log_group.level =  Logger::Severity.const_get(level.upcase.to_sym)
    end
    root
  end

  def with_name(name)
    @progname = name
    self
  end

  def inspect
    "#{@progname}(#{@level}) -- #{@child_loggers.inspect}"
  end

  def [](dotpath)
    logger = self
    name_parts = dotpath.split('.')
    while (name = name_parts.shift) && logger.has_key?(name)
      logger = logger.child_loggers[name]
    end
    logger
  end
end

To use it, create a config file like so:

device: logfile
loggers:
  cocoa.document:           debug
  cocoa.codeview:           warn
  cocoa.diagramview:        debug

And use it like this:

require "yaml"
require "log_group"
log_cfg = YAML.load_file("log_config.yaml")

$log = LogGroup.init_with_config(log_cfg)

$log['cocoa'].debug("this is a debug message from cocoa")
$log['cocoa.codeview'].debug("this is a debug message from cocoa.codeview")
$log['cocoa.diagramview'].debug("this is a debug message from cocoa.diagramview")

This will output to logfile:

  # Logfile created on 2009-12-19 02:42:05 -0800 by logger.rb/20321
  D, [2009-12-19T02:42:05.549777 #49807] DEBUG -- root.cocoa: this is a debug message from cocoa
  D, [2009-12-19T02:42:05.586840 #49807] DEBUG -- root.cocoa.diagramview: this is a debug message from cocoa.diagramview

Zen Coding

Improved productivity coding html
by using CSS Selectors

Zen Coding

I’ve just discovered Zen Coding. This is a plugin for various editors that lets you write html using an abbreviation syntax that is based on CSS selectors.

I’m pretty keen on using this, not only because it will drastically reduce the number of keystrokes required for creating html once I become familiar with it, but it will also help me become more fluent at thinking in css-selector, which will make me more productive at writing functional tests & javascript.

I tried the TextMate plugin, since that is my editor of choice. Installation was simple; it is supplied as a bundle which you can install just by double-clicking it. Or you can copy it to ~/Library/Application\ Support/TextMate/Pristine\ Copy/Bundles.

The TextMate version of Zen Coding consists of a single command (written in python) that expands css-selector expressions when you type ⌘E. It supports a subset of CSS Selectors, and adds some operators specific to generating html:

Description example expands to:
E any html element div <div></div>
E#id an html element with an id div#bob <div id="bob"></div>
E.class an html element with class(es) div.bob.mary <div class="bob mary"></div>
E>N Child element div#bob>p>span.mary <div id="bob">
  <p><span class="mary"></span></p>
</div>
E+N multiple siblings h2+h3+p <h2></h2>
<h3></h3>
<p></p>
E*N multiply element ul>li*5>a <ul>
  <li><a href=""></a></li>
  <li><a href=""></a></li>
  <li><a href=""></a></li>
  <li><a href=""></a></li>
  <li><a href=""></a></li>
</ul>
E$*N item numbering ul>li.item-$*5 <ul>
  <li class="item-1"></li>
  <li class="item-2"></li>
  <li class="item-3"></li>
  <li class="item-4"></li>
  <li class="item-5"></li>
</ul>>

For example, I created the basic html for the above table by typing:

tr*7>td+td+td+td

Grouping

Currently Zen Coding has no provision for grouping expressions -- for example I was unable to do this:

tr*7>td+td+(td>code)+(td>code)

Similarly, it does not have a way to move back up the hierarchy in an expression, so far as I could determine.

Snippets

Zen Coding uses its own internal idea of snippets to do what it does. These live inside the bundle in Support/zencoding/settings.py. All the html tags are implemented as snippets in this file, so if you want to change something (for example, which default attributes get placed in an element), you can change it here. Along with snippets for all the html tags, it comes with a number of other snippets; details here. It also has approximately a bajillion snippets for writing CSS which I have not tried yet.

HAML

Hmmm, a quick google doesn't seem to show a HAML emitter. I guess I'll have to make one at some point. It should be fairly easy to do, though I'll have to learn a little python to switch between settings.py files depending on context.

Return top

Web Application Developer

LastObelus hacks on web applications for enterprise. Usually in Rails or Merb these days, but he has worked WebObjects, J2EE, and various PHP frameworks.