Having fun with MacRuby on Snow Leopard

macruby_logo
As the owner of the semi-official MacRuby github mirror I would like to introduce you MacRuby.

MacRuby is a Ruby bridge to Apple’s Cocoa/Objective-C that enables a developer to take fully advantage of the OS X frameworks while developing the application in Ruby programming language.

Quoting the official README:

MacRuby is a Ruby implementation based on Mac OS X technologies, such as the Objective-C runtime and garbage collector, the CoreFoundation framework and the LLVM compiler infrastructure.

Neat I’d say.

Let’s start by installing MacRuby (remember: you have to install Xcode from official Snow Leopard DVD if you want to compile from source).

Update: check out the MacRuby nightly builds.

First we need the LLVM trunk, and keep in mind that PowerPC is not supported.

Start by grabbing a stable version of LLVM from their svn server.

Update: the recommended LLVM revision is now 82747.

svn co -r 82747 https://llvm.org/svn/llvm-project/llvm/trunk llvm-trunk
cd llvm-trunk
./configure

Now you should know that compiling LLVM takes a long while.
To help speed up things you can take advantage of using two cores of your CPU, which is the case of the majority of the newer machines.

UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" ENABLE_OPTIMIZED=1 make -j2

However this can leave your machine in a sort of unresponsitive state, so if you prefer you can take away the “-j2″ parameter.

After your usual cup of coffee we can install LLVM with this command:

sudo env UNIVERSAL=1 UNIVERSAL_ARCH="i386 x86_64" ENABLE_OPTIMIZED=1 make install

Now we are ready to build MacRuby.

Start by cd-ing, if you’re not there already, into your preferred directory, we are going to use Git to download from trunk.

Update: instructions on how to install git on Snow Leopard are here.

git clone git://github.com/masterkain/macruby.git
cd macruby
rake
sudo rake install

That’s it.

Installing MacRuby won’t touch your existing ruby binaries, in fact it will install them prefixed with “mac”.
For instance you can open up MacRuby’s version of IRB with the command:

macirb  --simple-prompt

and start playing around.

There are other commands, just type “mac” and press tab in your terminal to see other binaries.

The very first thing to try out is to load a framework, namely ‘Cocoa’, so fire up your macirb and give it a try:

kain-osx:~ kain$ macirb
irb: warn: can't alias exit from irb_exit.
irb(main):001:0> framework 'Cocoa'
=> true
irb(main):002:0>

Go on and try loading an Objective-C class:

irb(main):002:0> NSSound.ancestors
=> [NSSound, NSObject, Kernel]

All classes in MacRuby always inherit from NSObject:

irb(main):003:0> Regexp.ancestors
=> [Regexp, NSObject, Kernel]

There are many things that are cool/exciting in this technology, for example take a look at this:

irb(main):004:0> String
=> NSMutableString

Uh. Seems like Ruby classes are mapped to their equivalents in Objective-C.
That’s right, and this happen with no data loss nor they require manual conversion!

One difference with RubyCocoa is that MacRuby supports keyed arguments, for instance:

MacRuby:

1
2
3
4
window = NSWindow.alloc.initWithContentRect frame, 
    styleMask:NSBorderlessWindowMask,
    backing:NSBackingStoreBuffered,
    defer:false

RubyCocoa:

1
2
3
4
5
window = NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
    frame,
    NSBorderlessWindowMask,
    NSBackingStoreBuffered,
    false)

Objective-C:

1
2
3
4
5
NSWindow *window = [[NSWindow alloc]
    initWithContentRect:frame
    styleMask:NSBorderlessWindowMask
    backing:NSBackingStoreBuffered
    defer:false];

Let’s see how one can send message to our objects using MacRuby.
There are different syntaxes to accomplish this, let’s see some examples along with their Objective-C equivalent:

1
2
person.setName(name) # [person setName:name];
person.name = name # [person setName:name];

As we already said every class here inherits from NSObject, therefore is not necessary to explicitly define subclassing, like we did in RubyCocoa:

1
2
3
4
# RubyCocoa
class TestController < OSX::NSObject
# MacRuby
class TestController

Let’s build a very simple app to demonstrate that MacRuby works, open Xcode and choose New Project; from User Templates go into Application and select MacRuby Application, press “Choose..”.

A dialog will appear, give the application a name you prefer (“Hello World” will suit) and press Save. I usually save my project in a proper dedicated folder.

At this point Xcode will show you its main window, click on the left on “Other sources”.

You will see two files in there, main.m and rb_main.rb .
Xcode main interface
main.m is a small file which contains the method that calls the MacRuby initializer.
rb_main.rb is the ruby script that will be executed once your application started.

If you click on “Build and run” the application will start, showing you an empty window. cmd+Q for quitting the application.

Now click from the top menu File -> New file or just press cmd+N, from the left select “Other” and click on “Empty file”.
New file dialog

Press next and in the File name input write “MyController.rb”.
Controller generation

Press “Finish”.

If you see that MyController.rb on the left is floating around, just drag it into “Classes” folder. Be aware that Xcode folders doesn’t reflect the actual data structure on filesystem, but rather represent a logical approach.

Edit MyController.rb by selecting it and writing in the editor window:

1
2
3
4
5
6
  class MyController < NSWindowController
    attr_writer :button
    def clicked(sender)
      puts "Button clicked!"
    end
  end

Adding code

cmd+s to save the file.

Now, this code defines a subclass of NSWindowController and have two methods, a setter:

1
attr_writer :button

and clicked method:

1
def clicked(sender)

Those will serve the purpose especially with Interface Builder. In fact IB will see them respectively as an outlet and an action (for Obj-C users IBOutlet, IBAction).

To make sure that Interface Builder will pick up our method as an action, remember that it must have only one argument and that must be named sender.

We now have to wire our class with Interface Builder (from now on “IB”, go into the “Resources” folder and double click on MainMenu.nib .

In IB we need to instantiate our class to make it aware that we want to use one, to do that from the Library pane drag and drop a NSObject (blue cube) to the main window and select it.

From the inspector pane, as show in the next picture, select MyController as the Object Class.

ib

Now drag a NSButton always from the Library pane to your main window.
window

While having the just placed button selected Press CTRL and left click, this will make a line appear; place it over My Controller we defined earlier (blue cube) in your MainMenu.nib window.
When releasing the mouse a little hud-like menu will appear, named “Received Actions”.
Release the mouse when hovering “clicked:”.

That’s it, we made a connection from this button to our method in our MyController.

Save the Nib file, go back to Xcode and press “Build and run”.

Your application will start, showing a window and a button.
Leaving the application running let’s go back to xcode, and press the little icon on the right with “gdb” on it, this will open up the debugger window.

Now, leaving this window visible let’s try clicking on our button and see what happen.
If all went right we should see a bold “Button clicked!”.

That’s it for today, however there is much more to talk about, say HotCocoa.

For further documentation please refer to the official website of MacRuby project.

$1.99 domains with SSL purchase!

Send SMS with Ruby on Rails

SMSOnce upon a time embedding the chance to send SMS withing an app was daunting, no more.

We are going to use Ruby on Rails Edge (git version, but should work on stable too) and the excellent SmsOnRails plugin, along with Clickatell SMS provider.

Open your project with your favourite text editor and edit config/environment.rb by adding gem requirement:

1
2
config.gem 'blythedunham-sms_on_rails', :lib => 'sms_on_rails', :source  => 'http://gems.github.com'
config.gem 'clickatell' # if using Clickatell

Install the gems:

sudo rake gems:install

Or if you’re not going to use gems, install as plugin:

script/plugin install git://github.com/blythedunham/smsonrails.git

The plugin itself is complete with generators, for both models and migrations, I will not show them here (even because rails edge still cannot handle gem generators as of this writing), instead I will show you how to send sms quickly and easily.

We are going to use one specific provider for sending our sms, it’s Clickatell.
Therefore go to their website and open an account to use Clickatell Central API.
Account starts with a balance of 10 SMS, on which you cannot add custom text, but once you buy a proper package those limitations wears off (of course).

After you open up your account, proceed in buying sms at your liking, after that click on My Connections.

From here you can add a new HTTP connection by clicking Add Connection select box – HTTP.
You can leave the fields blank in the form that will appear on page reload, but for extra security you better edit them soonish.

When you add a new connection, it will get assigned a API ID, most likely it’s numeric.

Back to our plugin configuration, keep in mind that SmsOnRails also inherits settings from ActionMailer, so it can send also emails instead of SMS.

Create a new file called sms.rb in config/initializers directory and write this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SmsOnRails::ServiceProviders::Clickatell.config =
{
   :api_id => 'numeric_api_id',
   :user_name => 'your_username',
   :password => 'your_password'
}
 
 
SmsOnRails::ServiceProviders::EmailGateway.config =
{
   :sender => 'no-reply@domain.com',
   :subject => 'Default Subject Text'
   #:bcc => nil,
   #:mailer_klass => nil
}
 
 
SmsOnRails::ServiceProviders::Base.set_default_service_provider :clickatell

Replace api_id, user_name and password with your data.
As you can see we set our provider as Clickatell.

There are different methods for sending a SMS, and service providers are singletons and can be accessed by their instance.

Therefore:

1
2
3
phone_number = '392848676093485693' # not a real number
message_text = 'hi'
SmsOnRails::ServiceProviders::Clickatell.instance.send_message(phone_number, message_text)

That’s it. Easy as pie.
SmsOnRails is a robust plugin, pings the clickatell service to keep the connection alive.
I have yet to find out a way to show a custom sender ID, but for now, it’s fine and works well.

Advanced configurations can be seen at the github project page.

Install MySQL and MySQL Ruby gem on Snow Leopard, 64 bit

Warning: I consider this tutorial outdated. Please switch to a better system like brew, rvm and the mysql2 gem.

This is the source approach to get MySQL and MySQL Ruby gem working on Snow Leopard.

I assume you don’t have any previous MySQL installations lying around, this guide covers the installation from scratch.

If you want to install your ruby gems in your self-contained home directory without using sudo, check the end of the article.

Install required software

Let’s start by getting into a directory, I’ll use ~/src.
By convention you may want to use /SourceCache aswell.

Download the latest MySQL source distribution and install Xcode from Snow Leopard DVD.

While you’re downloading MySQL and installing Xcode you need to set your PATH environment variable correctly.

PATH Setup

Open a Terminal and verify what’s your current PATH by doing this:

echo $PATH

This will return a string containing the current search paths, you have to make sure that’s containing /usr/local/bin, /usr/local/sbin and /usr/local/mysql/bin.

Don’t rely on your terminal application to set those paths; instead go ahead and edit/create the file .bash_profile (file that starts with a dot are hidden) in your home folder (~).

As a Textmate user I’ll use the mate command to fire up my editor.

mate ~/.bash_profile

Inside this file, write:

PATH="/usr/local/bin:/usr/local/sbin:$PATH" # if not already present
PATH="$PATH:/usr/local/mysql/bin"
export PATH=$PATH

With this in place we are sure that we will find MySQL required files later.

If you don’t want to reopen the terminal or switch to another tab, just execute:

source ~/.bash_profile

This will pickup changes to $PATH.

MySQL installation

Decompress the MySQL source:

tar xzvf mysql-5.1.40.tar.gz && cd mysql-5.1.40

Of course match the version number you downloaded, the latest as of this writing is 5.1.40.

Configure MySQL this way (one line):

CC=gcc CFLAGS="-arch x86_64 -O3 -fno-omit-frame-pointer" CXX=gcc CXXFLAGS="-arch x86_64 -O3 -fno-omit-frame-pointer -felide-constructors -fno-exceptions -fno-rtti" ./configure --prefix=/usr/local/mysql --with-extra-charsets=complex --enable-thread-safe-client --enable-local-infile --enable-shared --with-plugins=innobase

Compile MySQL and install:

make && sudo make install

We are not done with MySQL yet, we need to install default tables and set up permissions:

cd /usr/local/mysql
sudo ./bin/mysql_install_db --user=mysql
sudo chown -R mysql ./var

We now have to auto start MySQL upon booting.
Create a file named

com.mysql.mysqld.plist

and add the following code in it:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeepAlive</key>
    <true />
    <key>Label</key>
    <string>com.mysql.mysqld</string>
    <key>Program</key>
    <string>/usr/local/mysql/bin/mysqld_safe</string>
    <key>RunAtLoad</key>
    <true />
    <key>UserName</key>
    <string>mysql</string>
    <key>WorkingDirectory</key>
    <string>/usr/local/mysql</string>
</dict>
</plist>

or see the script on github (raw).

Next move this file in the proper place with sudo and assign correct permissions:

sudo mv ~/src/com.mysql.mysqld.plist /Library/LaunchDaemons
sudo chown root /Library/LaunchDaemons/com.mysql.mysqld.plist

Start up MySQL now with:

sudo launchctl load -w /Library/LaunchDaemons/com.mysql.mysqld.plist

To stop MySQL manually do:

sudo launchctl unload -w /Library/LaunchDaemons/com.mysql.mysqld.plist

MySQL should be running (unless you stopped it, eh), it’s time to change our MySQL root’s password:

mysqladmin -u root password "mypassword"

Replace mypassword with your password.

Test MySQL:

mysqladmin -u root -p version

Write your password when asked and you should see some useful data.

Time to install the ruby mysql bindings gem.

MySQL Ruby gem setup

Please see the section below for a note about sudo.

First, update rubygems.

sudo gem update --system

If this doesn’t work for some reason or you get “Nothing to update” try this instead:

sudo gem install rubygems-update
sudo update_rubygems

Time to build our gem, write:

sudo env ARCHFLAGS="-arch x86_64" gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config

And we are done, thanks for reading.

Extras

Not always is desiderable to install gems system-wide, so add this to your .bash_profile

export GEM_PATH="$HOME/.gem/ruby/1.8"
export GEM_HOME="$HOME/.gem/ruby/1.8"
PATH="$HOME/.gem/ruby/1.8/bin:$PATH"
export PATH=$PATH

This will self-contain gem files and binaries in your home directory and you can install gems without using sudo.

Bonus
My entire .bash_profile:

export GIT_EDITOR="/usr/bin/mate -w"
export EDITOR="/usr/bin/mate -w"
export RAILS="$HOME/src/rails"
export GEM_PATH="$HOME/.gem/ruby/1.8"
export GEM_HOME="$HOME/.gem/ruby/1.8"
 
# CAPP_BUILD, directory for built Cappuccino framework.
export CAPP_BUILD="$HOME/src/cappuccino_framework"
 
# Cappuccino jake branch.
PATH="$CAPP_BUILD/Release/CommonJS/objective-j/bin:$PATH"
PATH="$HOME/src/narwhal/bin:$PATH"
 
# Normal stuff.
PATH="$PATH:/opt/local/sbin"
PATH="$PATH:/opt/local/bin"
PATH="$PATH:/usr/local/mysql/bin"
PATH="$HOME/.gem/ruby/1.8/bin:$PATH"
 
# Finalize PATH
export PATH=$PATH
 
function parse_git_branch {
  ref=$(git symbolic-ref HEAD 2> /dev/null) || return
  echo "("${ref#refs/heads/}")"
}
 
RED="\[\033[0;31m\]"
YELLOW="\[\033[0;33m\]"
GREEN="\[\033[0;32m\]"
 
PS1="$RED\$(date +%H:%M) \w$YELLOW \$(parse_git_branch)$GREEN\$ "

jGrowl on Rails (with i18n)

Install JQuery and jGrowl plugin.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  def display_flash
    flash_types = [:error, :warning, :notice]
 
    messages = ((flash_types & flash.keys).collect do |key|
      "$.jGrowl('#{flash[key]}', { header: '#{I18n.t(key, :default => key.to_s)}', theme: '#{key.to_s}'});"
    end.join("\n"))
 
    if messages.size > 0
      content_tag(:script, :type => "text/javascript") do
        "$(document).ready(function() { #{messages} });"
      end
    else
      ""
    end
  end

Beautify Ruby code in Textmate

This is based on Paul Lutus’ work, version 2.4

Update: this is now based on Version 2.9, 10/24/2008, adding here_doc support.

Beautify code in Textmate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env ruby
 
=begin
/***************************************************************************
 *   Copyright (C) 2008, Paul Lutus                                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
=end
 
PVERSION = "Version 2.9, 10/24/2008"
 
# Modified to work as a TextMate command by Claudio Poli (http://www.icoretech.org)
 
$tabSize = 2
$tabStr = " "
 
# indent regexp tests
 
$indentExp = [
  /^module\b/,
  /^class\b/,
  /^if\b/,
  /(=\s*|^)until\b/,
  /(=\s*|^)for\b/,
  /^unless\b/,
  /(=\s*|^)while\b/,
  /(=\s*|^)begin\b/,
  /(^| )case\b/,
  /\bthen\b/,
  /^rescue\b/,
  /^def\b/,
  /\bdo\b/,
  /^else\b/,
  /^elsif\b/,
  /^ensure\b/,
  /\bwhen\b/,
  /\{[^\}]*$/,
  /\[[^\]]*$/
]
 
# outdent regexp tests
 
$outdentExp = [
  /^rescue\b/,
  /^ensure\b/,
  /^elsif\b/,
  /^end\b/,
  /^else\b/,
  /\bwhen\b/,
  /^[^\{]*\}/,
  /^[^\[]*\]/
]
 
def makeTab(tab)
  return (tab < 0) ? "" : $tabStr * $tabSize * tab
end
 
def addLine(line,tab)
  line.strip!
  line = makeTab(tab)+line if line.length > 0
  return line + "\n"
end
 
def beautifyRuby
 
  comment_block = false
  program_end = false
  in_here_doc = false
  here_doc_term = ""
  multiLine_array = []
  multiLine_str = ""
  tab = 0
  source = STDIN.read
  dest = ""
  source.split("\n").each do |line|
    if(!program_end)
      # detect program end mark
      if(line =~ /^__END__$/)
        program_end = true
      else
        # combine continuing lines
        if(!(line =~ /^\s*#/) && line =~ /[^\\]\\\s*$/)
          multiLine_array.push line
          multiLine_str += line.sub(/^(.*)\\\s*$/,"\\1")
          next
        end
 
        # add final line
        if(multiLine_str.length > 0)
          multiLine_array.push line
          multiLine_str += line.sub(/^(.*)\\\s*$/,"\\1")
        end
 
        tline = ((multiLine_str.length > 0) ? multiLine_str:line).strip
        if(tline =~ /^=begin/)
          comment_block = true
        end
        if(in_here_doc)
          in_here_doc = false if tline =~ %r{\s*#{here_doc_term}\s*}
        else # not in here_doc
          if tline =~ %r{=\s*< <}
            here_doc_term = tline.sub(%r{.*=\s*<<-?\s*([_|\w]+).*},"\\1")
            in_here_doc = here_doc_term.size > 0
          end
        end
      end
    end
    if(comment_block || program_end || in_here_doc)
      # add the line unchanged
      dest += line + "\n"
    else
      comment_line = (tline =~ /^#/)
      if(!comment_line)
        # throw out sequences that will
        # only sow confusion
        while tline.gsub!(/\{[^\{]*?\}/,"")
        end
        while tline.gsub!(/\[[^\[]*?\]/,"")
        end
        while tline.gsub!(/'.*?'/,"")
        end
        while tline.gsub!(/".*?"/,"")
        end
        while tline.gsub!(/\`.*?\`/,"")
        end
        while tline.gsub!(/\([^\(]*?\)/,"")
        end
        while tline.gsub!(/\/.*?\//,"")
        end
        while tline.gsub!(/%r(.).*?\1/,"")
        end
        # delete end-of-line comments
        tline.sub!(/#[^\"]+$/,"")
        # convert quotes
        tline.gsub!(/\\\"/,"'")
        $outdentExp.each do |re|
          if(tline =~ re)
            tab -= 1
            break
          end
        end
      end
      if (multiLine_array.length > 0)
        multiLine_array.each do |ml|
          dest += add_line(ml,tab)
        end
        multiLine_array.clear
        multiLine_str = ""
      else
        dest += addLine(line,tab)
      end
      if(!comment_line)
        $indentExp.each do |re|
          if(tline =~ re && !(tline =~ /\s+end\s*$/))
            tab += 1
            break
          end
        end
      end
    end
    if(tline =~ /^=end/)
      comment_block = false
    end
  end
 
  STDOUT.write(dest)
  # uncomment this to complain about mismatched blocks
  # if(tab != 0)
  #   STDERR.puts "#{path}: Indentation error: #{tab}"
  # end
end
 
beautifyRuby

Save it as a TextMate command, I usually give it cmd-B shortcut.

i18n URL-based breadcrumbs in Rails

in application_helper.rb put this code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  # Generate URL-based Breadcrumbs.
  # Based on http://rubysnips.com/url-based-breadcrumbs
  # Modified and adapted for i18n by Claudio Poli (http://www.icoretech.org)
  def show_breadcrumbs(wrapper = "p", separator = " ยป ")
    r = []
    r < < link_to(I18n.t(:home), "/")
    url = request.path.split('?')
    segments = url[0].split('/')
    segments.shift
    segments.each_with_index do |segment, i|
      title = segment.gsub(/-/, ' ').titleize
      title = I18n.t(title.downcase.to_sym, :default => title)
      r < < link_to_unless_current(title, "/" + (0..(i)).collect{ |seg| segments[seg] }.join("/"))
    end
    content_tag(wrapper, "#{I18n.t(:path)}: " + r.join(separator), :class => "breadcrumbs")
  end