Rubyfication of Raise Manager
I spent a couple of hours transforming the RaiseMan application from Cocoa Programming for Mac OS X (2nd Edition). In the RubyCocoa examples folder, there is a version of this but it is based on the first edition of the book. My version includes key-value-binding, undo and redo and also alert panels. I also implemented some of the end-of-chapter exercises that I felt were useful. I skipped the part on Localization though.
Rereading the book makes me think of how much I do not like it. There is very little rationale behind each of the examples. Most of the time, the author just says do this or that. And his anecdotes are pretty annoying (I am not sure where he got his stories from). I felt that the book could have been better if the author spent more time explaining why things are done that way instead of listing the API and describing what it does (it's almost identical to the documentation).
Things actually made more sense to me this time around because I was exposed to some design patterns and could see the rationale behind the way of doing things. I am not sure if a beginning programmer would appreciate the way of doings things just from reading this book. I have heard better things about Cocoa Programming but that book is old and has not been updated. I have not read that book yet so I cannot offer my opinions on it.
Anyway, here are somethings I learned from this effort that may be useful to people:
- Before starting, you should read this page on RubyCocoa to get acquainted with the conventions. You can choose to use
somemethodwithargument0_withargument1_withargument2(arg0,arg1,arg2)orsomemethodwithargument0(arg0, :withargument1, arg1, :withargument2, arg2). Once you have decided on one, it is best to stick with it. - Since you cannot drag-and-drop your MyClass.h (we don't have one, we only have MyClass.rb) into Interface Builder to get it to update any new ib_outlets, you need to do this by hand. The easiest way I can think of is to click on the "Classes" tab on the nib file, locate your MyClass and right click to add actions or outlets. That being said, Interface Builder is pretty good for creating user interfaces. The separation between code and user interface is pretty clean and it is not too hard to get things to work the way you want it to.
- For key-value-binding to work on an array, use
kvc_array_accessor. For an example, look at my MyDocument.rb file. More information on key-value-bindings can be found in oc_import.rb in the RubyCocoa source. - Always, always, always, build and run regularly. There is virtually no good debugging support for RubyCocoa. Sometimes the error message can tell you which file (and in the optimistic case, which line the error occurred at). But in general, it is going to be cryptic. By testing early and frequently, you can at least narrow the error down to the last edit that you made.
- Remember to qualify your ib_outlets when you used them with the '@' symbol. For instance, if you have
ib_outlet :some_objectthen in your methods, you refer to that object as@some_object. I am not sure why I keep forgetting this but it has been the cause of many problems. - Remember to always prefix Cocoa classes with
OSX::. You can avoid this by usinginclude OSX. Also be careful that you check the spelling for the Cocoa classes (you need the NS prefix, etc). Misspellings have bitten me quite a few times. - Read oc_attachment.rb in the RubyCocoa source code to find out how you can use Ruby idioms like [], []=, etc for accessing arrays and dictionaries. Also decide if you want to use those notations or just stick with
objectForKey(), etc. - There are some idiosyncrasies with
OSX::NSRunAlertPanel. You cannot do Ruby string substitution in the arguments. If you need string substitution, you can do it using the special @ symbol as such:choice = OSX::NSRunAlertPanel("Delete", "Do you want to delete %@ records?","Delete","Cancel",nil, selected.size).
One problem that I had was that I was not able to build it for release. I had to build it for debug. I think there could be something wrong with the way the project is setup. Anyway, the file is available from here.
Update: I just installed RubyCocoa 0.9 from Subversion. Instructions can be found here. I can now successfully built it as a universal binary. The next test I did was to run this RaiseManager application. I was greeted by a successful build... but the application could not create new employees. The error logs report that there is something wrong with the NSUndoManager but I suspect that it has something to do with key-bindings since there is supposed to be some change to how that is done in RubyCocoa 0.9. I will have to take a look at that. On the bright side, RubyCocoa is approaching the 1.0 mark after so many years!
Update (Jan 3, 2007): The latest version from the Subversion repository has addressed the issues that I reported above. The current working version (revision 1325) was checked in today by Laurent. Suffice to say that there are major additions from RubyCocoa 0.5 that are worth checking out. It would be good to see how RubyCocoa plays with the new Objective-C 2.0 that is included with Leopard. On a side-note, there is a new Ruby/Objective-C bridge out by Tim Burks here.
RubyCocoa
Since I had some free time this week, I wanted to brush up on some Cocoa programming. However, I have not been using Objective-C for some time and would rather not have to program in it. Instead, I decided to use Ruby via the RubyCocoa. I have always had great difficulties installing RubyCocoa on my machine. It never seems to just work. I recall getting version 0.4.3 to work somehow over Spring but the steps seem to elude me now.
I tried searching on the web but there was no clear answer. Even the installation instructions for RubyCocoa seems incomplete (and might even be auto-translated from the Japanese version). Anyway, I downloaded the source code for version 0.5 and follow the instructions to do ruby install.rb config and ruby install.rb setup. The installation failed -- it was not able to locate some of the symbols.
So I reread the instructions on the website. There was some mention of using Ruby 1.8.5. and I only had version 1.8.4 installed. Since Ruby 1.8.5 has been out for some time, I decided to upgrade my version. There are various discussions on what needs to be done to get Ruby 1.8.5 to compile on OS X but I stuck with the instructions from HiveLogic and just substituted the latest version Ruby into the instructions. Everything works fine, even readline. I had to recompile RMagick though since the links were broken after the upgrade. Some might also find the instructions here useful for upgrading.
I followed the steps on the RubyCocoa website and tried to build RubyCocoa. It still was not working. Then I realized something. I was actually issuing the following command: sudo ruby install.rb config --build-universal. I removed the build-universal just to try the default settings. And it WORKED! I am not sure how the developers themselves got the universal binary to build but it was not working on my machine. Right now, RubyCocoa is not a universal binary but that matters little. At least it is finally working.
It's always the simple things that make life so complicated. It also shows that one should always try the default values for configure. After all this, I really think I should have just stuck with rereading some Objective-C. RubyCocoa is a very nice framework but it takes a lot of guess work to get it working. However, as a consolation, I finally got Ruby 1.8.5 installed.