2011
09.23

In my previous post, I talked about Polyglot programming in Grails. As I mentioned in that post, there are plugins for Clojure and Scala, but not for Ruby or Python. So, today I thought I would tackle one of those by creating a new Grails plugin for Ruby! It was really easy to integrate JRuby (the Ruby implementation for the JVM) with Grails and I think the plugin works pretty good so far! So, keep reading to see how I created the plugin as well as a demonstration of how to use the plugin!

I decided that I should do things the right way this time and start with some test cases and then implement the code to satisfy those tests (test-driven-development). So, lets create a sample app with a test class and a controller. I’m using an integration test instead of a unit test because as you’ll see later, I’m actually going to be doing some meta-programming and need access to the entire Grails environment in the test for my dynamic “ruby” method to work.

  • grails create-app grails-ruby-sample
  • cd grails-ruby-sample
  • grails create-controller home
  • grails create-integration-test grails.ruby.sample.HomeController

These are the only Groovy files we’ll need in the sample app. The only other thing will be a Ruby file which we’ll add later. Here’s the tests:


// test/integration/grails/ruby/sample/HomeControllerTests.groovy
package grails.ruby.sample
 
import static org.junit.Assert.*
import org.junit.*
 
class HomeControllerTests extends GroovyTestCase {
  def dayOfWeek
 
  @Before
  void setUp() {
    dayOfWeek = new Date().format("EEEE")
  }
 
  void testHi() {
    def controller = new HomeController()
    controller.hi()
    assertEquals controller.response.contentAsString, "Hi Bobby! Today is $dayOfWeek.".toString()
  }
 
  void testBye() {
    def controller = new HomeController()
    controller.bye()
    assertEquals controller.response.contentAsString, "Bye Bobby! Today is $dayOfWeek.".toString()
  }
}

As you can see, we are just testing the output of two Grails controller actions. One says hi and one says bye. This would be very easy to implement in straight Groovy, but we’re actually going to have the controller call out to Ruby for the implementation! Here’s the controller:


// grails-app/controllers/grails/ruby/sample/HomeController.groovy
package grails.ruby.sample
 
class HomeController {
  def hi() {
    ruby.put('name', 'Bobby')
    ruby.put('greeting', 'Hi')
    render ruby.eval('greet($name, $greeting)')
  }
 
  def bye() {
    ruby.put('name', 'Bobby')
    ruby.put('greeting', 'Bye')
    render ruby.eval('greet($name, $greeting)')
  }
}

So now we have our sample app all setup to test! The first thing you are probably wondering though is where the heck did that “ruby” property come from, right? To explain that, let’s switch gears and start looking at the source for the plugin (grails-ruby)! There’s really only four parts:

1) grails-app/conf/BuildConf.groovy: We need to add a runtime dependency for JRuby.

dependencies {
  runtime 'org.jruby:jruby:1.6.4'
}

2) RubyGrailsPlugin.groovy: We need to implement onChange and doWithDynamicMethods for evaluating Ruby code, automatically re-loading when a Ruby file changes and inserting the “ruby” property into all Groovy classes via meta-programming.

def onChange = { event ->
  ScriptEngine engine = new ScriptEngineManager().getEngineByName("jruby")
 
  def source = event.source
  if(source instanceof FileSystemResource && source.file.name.endsWith('.rb')) {
    source.file.withReader { reader ->
      engine.eval(reader);
    }
  }
}
 
def doWithDynamicMethods = { ctx ->
  ScriptEngine engine = new ScriptEngineManager().getEngineByName("jruby")
 
  def rubyFiles
  if(application.warDeployed) {
    rubyFiles = parentCtx?.getResources("**/WEB-INF/ruby/*.rb")?.toList()
  } else {
    rubyFiles = plugin.watchedResources
  }
 
  rubyFiles.each {
    it.file.withReader { reader ->
      engine.eval(reader)
    }
  }
 
  application.allClasses*.metaClass*."getRuby" = {
    return engine
  }
}

3) scripts/_Events.groovy: We need to use the eventCompileStart event to copy Ruby files over to the destination directory (WEB-INF/ruby).

eventCompileStart = {
  def rubyDestDir = "${grailsSettings.projectWarExplodedDir}/WEB-INF/ruby"
  ant.mkdir dir: rubyDestDir
  ant.copy (todir: rubyDestDir) {
    fileset(dir:"${basedir}/src/ruby", includes:"*.rb")
  }
}

4) scripts/_Install.groovy: We need to create a directory for applications using the plugin to put Ruby files in (src/ruby).

ant.mkdir(dir:"${basedir}/src/ruby")

And that’s really all there is to it! You can view the plugin source on GitHub here. So, now back to our sample application (grails-ruby-sample)! Let’s install the plugin, create a Ruby file that we will use from the controller, implement the Ruby code to satisfy our Groovy test cases and test our app!

  • grails install-plugin ruby
  • touch src/ruby/ruby_hello_world.rb


// src/ruby/ruby_hello_world.rb
class RubyHelloWorld
  def create_greeting(name, greeting)
    time = Time.new
    day = time.strftime("%A")
    "#{greeting} #{name}! Today is #{day}."
  end
end
 
def greet(name, greeting)
  hello_world = RubyHelloWorld.new
  hello_world.create_greeting(name, greeting)
end

  • grails test-app

And that’s it! The two integration test cases for our sample app should pass. It might take awhile to download the JRuby libraries the first time testing or running after you install the plugin though.

The source code for the plugin and sample app are located on GitHub and the plugin is documented in the Grails plugin portal:

I used Jeff Brown’s Clojure plugin as a guide for creating this Ruby plugin. I have set the version for this first release of the plugin to 1.0.M1 which simply means it’s a milestone release and not ready for production yet. I hope to have a final 1.0 release in the near future after I get some feedback on the plugin as people test it out. I hope you enjoyed reading this post about Grails Ruby!

4 comments so far

Add Your Comment
  1. [...] Warner sta cercando di “congelare l’inferno” con il suo Ruby Plugin. Probabilmente stiamo parlando di qualcosa come “JRuby on Grails”? Trovate maggiori dettagli [...]

  2. [...] Warner is attempting to make hell freeze over with his new Ruby Plugin. Not sure what to call this – “JRuby on Grails”? This is a followup to his post [...]

  3. [...] Bobby Warner announced that he has created a plugin which integrates JRuby with Grails. He wrote a post on his blog, describing how he built the plugin, and its source is (of course!) on GitHub (see also: the plugin’s sample application). This [...]

  4. [...] Warner, the author of the Grails Ruby plugin, has begun what I assume is a series of screencasts on Groovy topics. He has two up so [...]

*

Switch to our mobile site