Measure Once, Cut Twice

Notes on MacRuby and sqlite3

Posted in macruby, ruby by steve on April 10, 2011

Each time I install the latest version of MacRuby, I spend an hour re-figuring this out, so here it is…

Under certain conditions installing the ‘sqlite3-ruby’ gem on OSX with macgem fails with this error:

Building native extensions.  
This could take a while...
/bin/sh: line 1: 27196 Abort trap
/Library/Frameworks/MacRuby.framework/Versions/0.11/usr/bin/macruby extconf.rb
ERROR:  Error installing sqlite3-ruby:	ERROR: Failed to build gem native extension.

/Library/Frameworks/MacRuby.framework/Versions/0.11/usr/bin/macruby extconf.rb
checking for sqlite3.h... yes
checking for sqlite3_libversion_number() in -lsqlite3... no
sqlite3 is missing.
Try 'port install sqlite3 +universal'or 'yum install sqlite3-devel' and check
your shared library search path (the location where your sqlite3 shared library
is located).*** extconf.rb failed ***Could not create Makefile due to some
reason, probably lack of necessary libraries and/or headers.  Check the mkmf.log
file for more details. You may need configuration options.
Provided configuration options:
    ... etc ....

in `asplode:': sqlite3 is missing. Try 'port install sqlite3 +universal'or 'yum 
install sqlite3-devel' and check your shared library search path (the location 
where your sqlite3 shared library is located). (SystemExit) from 
in `<main>'\

The problem is that macports installs into /opt/local by default so even after doing ‘port install sqlite3 +universal’, you’ll get this error message. You need to specify the install prefix using this awkward command line:

sudo macgem install --version '= 1.3.2' sqlite3-ruby -- --with-sqlite3-dir=/opt/local

Version 1.3.2 is the one I’ve had luck with on MacRuby up to version 0.11.

Notes on using MacRuby and WebView

Posted in macruby, objective-c by steve on October 29, 2010

After much head bashing, the following works for two-way calling between MacRuby and Javascript within a WebKit WebView. Key issues:

  • Get the script object via the callback method. Many tutorials show obtaining it via [webView windowScriptObject]. This does not always work since the script object may not be ready (e.g. the page isn’t fully loaded).
  • Take note of which delegate methods are static (isSelectorExcludedFromWebScript) and which are not.
  • MacRuby names that you want callable from javascript must consist only of lower case characters. The mappings given in the docs (fooBar: converted to fooBar_, foo_bar converted to foo$_bar) have some idiosyncrasies.
    • For no-args methods, everything works smoothly as long as you use all lowercase ruby method names with no underscores.
    • If you want to pass an argument, you need to call it from JS with an underscore, declare it in macruby without the underscore, and also register it via ‘respondsToSelector’. To summarize:
      • Define method in macruby: mymethod(somearg)
      • Call from javascript: webscriptObj.mymethod_(somearg)
      • In the webView initialization on the object that contains mymethod(somearg): self.respondsToSelector(‘mymethod:’)
      • All of this confusion seems to arise from translation between JS methods, selectors, and strings/symbols in macruby. The colon at the end matters and it doesn’t work if it is a symbol, :’mymethod:’. For no-args methods though symbols work just fine.
  • It is handy setting up the delegate methods to trap the console.(log|error|warn) methods as well as window.status changes.
  • Note the WebScriptObject does not exist for the iPhone UIWebView, only for the OSX WebKit WebView. The PhoneGap project has a workaround for UIWebView for call from Javascript to Objective-C. For the opposite direction, Objective-C calling Javascript, the method stringByEvaluatingJavaScriptFromString is portable across both UIKit and WebKit.
class MyMacRubyController

    attr_accessor :scriptobj

    # In this example, the top level app_controller creates MyMacRubyController
    # and initializes it with a WebView created via Interface Builder.
    def initialize(view)
        @view = view
        #@view.delegate = self   # only for UIWebView, below are for WebView
	@view.UIDelegate = self
	@view.frameLoadDelegate = self
	@view.setMainFrameURL(NSBundle.mainBundle.pathForResource(‘test.html’, ofType:nil))

    def set_body_content(name)
        js = ‘document.getElementById(“content”).innerHTML = “some html text”’

    #### Methods exposed to javascript via ‘myController’ object ####

    def test
        puts “In test method”

    def test(somearg)
        puts "In test method with arg: #{somearg}"

    #### WebScripting delegate related ####

    # This WebScripting delegate method required and must be static.
    # Ensures only methods you want are exposed to javascript.
    def self.isSelectorExcludedFromWebScript(selector)
        if selector == :test or selector == 'test:'
            return false
            return true

    # Delegate method for obtaining the script object. Sets up the main
    # application callback object, myController. MacRuby method names
    # seem to need to be all lowercase characters e.g. ‘runme’, ‘test’
    def webView(wv, windowScriptObjectAvailable:obj)
        @scriptobj = obj
        puts “Got window script object #{@scriptobj}”
        @scriptobj.setValue(self, forKey:’myController’)
        self.respondsToSelector('test:')    # this registration is only needed for methods with args

    # Called whenever window.status is called in JS
    def webView(wv, setStatusText:text)
        puts “JS status -> “ + text

    # Private WebViewUIDelegate method to trap console.log messages
    def webView(wv, addMessageToConsole:message)
        puts “JS console -> “ + message[‘message’]
<script type=“text/javascript”>
function doSomething() {
    console.log(“in dosomething”)
    window.status = “status set in dosomething”
    console.log(window.myController.test_("arg passed"));

  <button type=“button” onclick=“doSomething()”>Click me</button>
<div id=“content”></div>

Update: found this related article from the merbist.