Quick, Convert Your RubyCocoa To MacRuby!

You won’t regret it. Really. MacRuby is like filet mignon, while RubyCocoa is just a sad, undercooked rumproast. Did I just paraphrase a joke from American Pie 2? You better believe it, buster.

Anyway, if you have a RubyCocoa project that needs a little bit of shininess, it may be surprisingly easy (and satisfying) to quickly port it over to MacRuby. I just spent about 4 hours converting a fairly sophisticated app (aside from its MIDI interface, which is a bit more tricky, but you probably won’t be dealing with that).

My first stop was this post, a very nice summary of the basic steps that need to be taken. However, I encountered quite a few additional snags, and I thought I’d post them just in case you, random internet denizen, want to skip them all together. Most of them are covered in the MacRuby docs, of course, but they’re here in a nice digestible table format so you can more easily knock out your conversion. Because as fun as prettying up your code is, you’d still rather be adding functionality, right?

RubyCocoa MacRuby
The basics
ib_outlet :property
attr_writer :property
ib_action :method
def method(sender)...
# Just ditch the ib_action call!
def method(sender)...
Key-value coding (KVC)
kvc_accessor :property
attr_accessor :property
# Default setter automatically notifies
property = 'new hotness'
# Have to use notifying method (for now...)
setValue 'new hotness', forKey:'property'
Setting an action selector
@table.setDoubleAction :doubleClick
@table.setDoubleAction 'doubleClick:'
Dealing with Cocoa’s funky named parameters
# Parameter names are included in method name,
# separated by underscores; values follow
# in sequential order
year.setValue_forKey 525600, 'minutes'
# Only the first parameter name ("value") is
# included in  the method signature; the rest can be
# defined with Ruby 1.9's named parameter support:
year.setValue 525600, forKey:'minutes'

# Or, alternatively, a hash:
year.setValue 525600, :forKey => 'minutes'
Overriding Cocoa’s funky named parameters
def drawInteriorWithFrame_inView(frame, controlView)
def drawInteriorWithFrame(frame, inView:controlView)
Calling the current method in the superclass
super_drawInteriorWithFrame_inView(frame, controlView)
super # Now that's what I'm talking about!
Instantiating collections
array = NSMutableArray.alloc.init
hash = NSMutableDictionary.alloc.init
array = [] # It's a real NSMutableArray!
hash = {} # It's a real NSMutableDictionary!
Instantiating objects
table = NSArrayController.alloc.init
table = NSArrayController.new
Defining class initializers
def init
  super_init # If a Cocoa subclass
  # Do stuff
  self
end
def initialize
  super # If a Cocoa subclass
  # Do stuff. Note: you can still override #init,
  # but then you must return self.
end
Also, if you’re in the habit of grouping your code into directories, the default loadup require scheme might not work for you, as it only grabs the files in the root directory. Just change that Dir.entries to a Dir.glob and you’ll be all set:
# In rb_main.rb
ROOT = NSBundle.mainBundle.resourcePath.fileSystemRepresentation
Dir.glob("#{ROOT}/**/*.rb").each do |file|
  if file != File.basename(__FILE__)
    require(file)
  end
end

And that’s just for starters! If I remember or encounter anything else, I’ll be sure to append this post. And don’t you dare think this is conclusive – the wonderful folks working on HotCocoa have been streamlining things for even greater efficiency, so be sure to check that out.

4 Responses to “Quick, Convert Your RubyCocoa To MacRuby!”

Laurent Sansonetti on May 23rd, 2009 5:56 pm:

Hi Mike,

Very nice article! Thanks for taking the time to write this!

2 small things:

1) It is also possible to do NSTableview.new in RubyCocoa. However this example is confusing, since you are supposed to go through -[NSView initWithFrame:] when creating views.

2) If you want to overwrite the default -[NSObject init] method in MacRuby, you must still return self. However if you decide to overwrite #initialize (which -init calls], it is not necessary to return self here (as in pure Ruby).

Would you be interested to convert your blog post as an article for our website (http://www.macruby.org/documentation.html)? I think it would be awesome to have a “MacRuby for RubyCocoa programmers” short guide.

Laurent


Rafael Bugajewski on May 25th, 2009 12:06 pm:

Hi,

thanks for this great summary. I have a slightly different question: Is there any documentation / possibility to use Ruby classes and execute their methods right from Objective C? I’ve seen some examples that use NSClassFromString(), but they didn’t work in my case.


Mike Laurence on May 28th, 2009 7:44 pm:

@Laurent – thanks for the notes! I’ve updated the post.

@Rafael – I’m actually in the midst of figuring that sort of thing out myself. It seems that the best way to go from obj-c => ruby is using the performRubySelector method, but unfortunately documentation is sparse right now (I believe the feature is brand new in MacRuby 0.4, so not too many people have played around with it yet.) However, I’m in the middle of a discussion on the MacRuby mailing list about it, so maybe I’ll get it figured out soon and create some documentation myself!


Jesper on December 6th, 2009 10:39 am:

Good overview.

Just to nitpick, Objective-C does not have “named parameters”, it has selectors. [x a:1 a:-2 a:@"z"] uses a valid selector, and Objective-C and MacRuby both deal with that. Hell, even [x :1 :2 :3] uses a valid selector (no names for any of the parts), although I have no idea how either RubyCocoa or MacRuby solves declaring that. (It’s spectacularly bad form while overlapping with being valid.)


Leave a Reply

(required)

(required)