Ruby and Rails vs Groovy and Grails

nvisia is an award-winning software development partner driving competitive edge for clients.

Ruby on Rails has generated significant buzz within the industry. However, for a large number of our clients this represents an unusual challenge because Ruby and Rails don’t necessarily integrate well with their significant existing investments in Java code/frameworks and they require new skill sets and training for their existing staff.

Groovy and Grails have emerged as competitors to Ruby and Rails that tightly embrace the Java language and platform. For example, while Grails copies many ideas from Rails, it does so by leveraging existing frameworks such as Spring and Hibernate to achieve this.

It’s worth noting that from a deployment perspective, both Ruby on Rails (more specifically JRuby on Rails) and Groovy/Grails can be deployed as WAR files in a JDK 1.4 container. So neither approach requires radical changes to the underlying deployment and operations environment. But it’s in the development area where these frameworks differ.

This article is an attempt to explore the capabilities of both languages and frameworks and compare and contrast their approaches to problems. And while bias is unavoidable, I’ll at least try to separate the factual information from my own biased conclusions.

NOTE: Additional Rails and Grails competitors outside the scope of this document include:

  • Trails - a Rails clone based on Tapestry and Naked Objects
  • JMatter - a Rails clone based on Naked Ojbects which creates WebStart apps

Ruby vs Groovy

Ruby and Groovy are both dynamic languages. The primary focus of this section is to compare the language level and standard library features and capabilities. However, some references to the additional language specific frameworks will be unavoidable since they are sometimes the best demonstrations of the capabilities of each language.

One thing that I will keep out of this section are specific comparisons between the Rails and Grails frameworks, since I will devote another entire section to that comparison later.

Ruby Overview

Ruby was created in 1993 and is a reflective, dynamic, object-oriented programming language. It combines syntax inspired by Perl with Smalltalk-like object-oriented features, and also shares some features with Python, Lisp, Dylan, and CLU. Ruby is a single-pass interpreted language. Its official implementation is free software written in C.

Features

  • object-oriented
  • four levels of variable scope: global, class, instance, and local
  • three class/instance variable visibilities: public, protected, and private
  • exception handling
  • iterators and closures (based on passing blocks of code)
  • native, Perl-like regular expressions at the language level
  • operator overloading
  • automatic garbage collecting
  • highly portable
  • introspection, reflection and metaprogramming
  • large standard library
  • supports dependency injection (via external frameworks)
  • supports Object runtime alteration[8]
  • supports mixins (at class/static level via extend, at instance level via include)
  • continuations and generators (examples in RubyGarden: continuations and generators)
  • can run on the Java JVM (via JRuby)
  • default MRI runtime features (not running on JVM):
    • cooperative multi-threading on all platforms using Green threads
    • DLL/shared library dynamic loading on most platforms

Shortcomings

  • Ruby currently lacks full support for Unicode, though it has partial support for UTF-8.

Groovy Overview

Groovy was created in 2003 and is a dynamic language that was the first, and currently only, JSR approved language (JSR-241) other than Java for the JVM. Though to be fair, other languages like Ruby fall under the umbrella of JSR-223 Scripting for the Java Platform, and hundreds of other languages can compile to bytecode and run on the JVM.

Groovy includes features found in Python, Ruby, and Smalltalk, but uses syntax similar to the Java programming language. Groovy’s similarities to Java and reliance on the JDK set it apart from these other dynamic languages and enable it to achieve a level of integration with Java that has yet to be reached in other languages.

Features

  • most valid Java code is also valid Groovy code
  • object-oriented
  • four levels of variable scope: binding (global), class, instance, and local
  • three class/instance variable visibilities: public, protected, and private (java’s default package access is not supported)
  • exception handling (unlike Java, catching any exception type is optional)
  • iterators and closures (based on passing blocks of code)
  • native, Perl-like regular expressions at the language level
  • operator overloading
  • automatic garbage collecting
  • highly portable
  • DLL/shared library dynamic loading on most platforms
  • introspection, reflection and metaprogramming
  • large standard library
  • supports dependency injection (via external frameworks)
  • supports Object runtime alteration
  • supports mixins (at the class and instance level via Categories)
  • static typing and dynamic typing (i.e. static typing is optional)
  • native syntax for lists, maps, and regular expressions (different than Java)
  • layers additional Groovy JDK methods on existing Java JDK classes
  • language level concurrency support (can be implemented via native or green threads depending on JVM)

Shortcomings

  • no support for continuations
  • no support for generators (I’d like to research this further–it appears you can kludge it, but can’t truly support it without continuations)
  • Groovy uses a different syntax than Java for for loops. As such, a lot of valid Java code that would otherwise also be valid Groovy code ends up with compilation errors and must either remain Java or be ported to Groovy’s syntax.
  • Groovy has the quirk of being able to optionally use parenthesis for method calls with parameters, but requiring them for methods with no parameters (unless you use the hack of naming the method getMethod to trick Groovy into thinking it’s a property). (This quirk is actually a result of Groovy’s tight embracing of the existing JavaBean’s standard.)

Ruby vs Groovy Comparisons

Java Integration

Stack traces

Stack traces are long and illegible. The reason for this is that making a dynamic call in Groovy adds an average of somewhere between 6 and 12 calls to the stack. So what might be a reasonable 20 line stack trace in “normal” Java ends up as a 200+ line stack trace in Groovy. And what’s worse, you probably don’t care about the majority of the stack (it would be nice if there was a simple way to filter out that crud or at least hide it). Here’s an example from a little script I wrote to call a web service (which btw is as simple as this in Groovy):

def currencyconverter = new SoapClient('http://www.webservicex.net/CurrencyConvertor.asmx?WSDL')
println 'USD to EUR rate: '+currencyconverter.ConversionRate('USD', 'EUR')

Try the above code (you need the GroovySOAP jar which bundles XFire internally) and it will work to call a real internet web service. Rediculously simple. The problem is that SoapClient currently doesn’t support a proxy parameter, so if you are behind a firewall that requires a proxy server, you’re SOL. The stack trace below is part of an attempt to use Groovy Categories to fix this shortcoming, but it’s obviously not quite there yet. 

groovy.net.soap.SoapClient.invokeMethod(Unknown Source)
	at org.codehaus.groovy.runtime.Invoker.invokePogoMethod(Invoker.java:102)
	at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:79)
	at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:69)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:170)
	at WebServiceExample$_run_closure1.doCall(WebServiceExample.groovy:18)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:243)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:78)
	at WebServiceExample$_run_closure1.doCall(WebServiceExample.groovy)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:243)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:643)
	at groovy.lang.Closure.call(Closure.java:291)
	at groovy.lang.Closure.call(Closure.java:286)
	at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:135)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.use(DefaultGroovyMethods.java:277)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod.invoke(NewInstanceMetaMethod.java:54)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:803)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodOnCurrentN(ScriptBytecodeAdapter.java:78)
	at WebServiceExample.run(WebServiceExample.groovy:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:803)
	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:643)
	at org.codehaus.groovy.runtime.Invoker.invokePogoMethod(Invoker.java:98)
	at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:79)
	at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:69)
	at org.codehaus.groovy.runtime.InvokerHelper.runScript(InvokerHelper.java:369)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:930)
	at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:69)
	at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:69)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:170)
	at WebServiceExample.main(WebServiceExample.groovy)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.codehaus.groovy.runtime.metaclass.ReflectionMetaMethod.invoke(ReflectionMetaMethod.java:56)
	at org.codehaus.groovy.runtime.MetaClassHelper.doMethodInvoke(MetaClassHelper.java:538)
	at groovy.lang.MetaClassImpl.invokeStaticMethod(MetaClassImpl.java:930)
	at org.codehaus.groovy.runtime.Invoker.invokeMethod(Invoker.java:69)
	at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:69)
	at groovy.lang.GroovyShell.runMainOrTestOrRunnable(GroovyShell.java:244)
	at groovy.lang.GroovyShell.run(GroovyShell.java:218)
	at groovy.lang.GroovyShell.run(GroovyShell.java:147)
	at groovy.ui.GroovyMain.processOnce(GroovyMain.java:492)
	at groovy.ui.GroovyMain.run(GroovyMain.java:308)
	at groovy.ui.GroovyMain.process(GroovyMain.java:294)
	at groovy.ui.GroovyMain.processArgs(GroovyMain.java:111)
	at groovy.ui.GroovyMain.main(GroovyMain.java:92)
	at groovy.lang.GroovyShell.main(GroovyShell.java:51)
Caught: java.lang.NullPointerException: Cannot set property http.proxyHost() on null object
	at SoapClientCategory.setClientProperty(WebServiceExample.groovy:7)
	at WebServiceExample$_run_closure1.doCall(WebServiceExample.groovy:19)
	at WebServiceExample$_run_closure1.doCall(WebServiceExample.groovy)
	at WebServiceExample.run(WebServiceExample.groovy:15)
	at WebServiceExample.main(WebServiceExample.groovy)

Anyway, nasty, nasty stack traces.

Rails vs Grails

Rails vs Grails Comparison

Getting up and running

rails your-app-name
rails
grails create-app your-app-name

Both frameworks are almost identical when it comes to getting up and running. Here’s a simple example of creating an application with a single model/domain object and running it (assuming you’ve already downloaded the frameworks and put them in your path):

ORM

Rails ORM

  • Rails ORM is based on ActiveRecord (limited but evolving framework)
  • Rails model objects must extend ActiveRecord
  • Rails doesn’t define properties in the model object, it picks them up at runtime from the database
  • Rails defines tables in Rake migration files as Ruby code

Grails ORM (GORM)

  • Grails ORM is based on Hibernate (industrial strength ORM framework)
  • Grails domain objects do not need to extend anything
  • Grails defines tables in the domain objects (as properties and with additional metadata in static fields such as hasMany, belongsTo, constraints, etc)
  • GORM also differs from Hibernate in that the byte code is modified at runtime using CGLIB (by default)

ORM Comparison

The fact that Grails leverages Hibernate gives it a powerful advantage when it comes to advanced ORM capabilities such as being able to plug in a second level cache. As to whether those features are needed, that really depends on the project, but it is one possible source of advantage for Grails.

The RoR “model” vs the Grails “domain” is also a very interesting difference. And I don’t think it’s entirely a matter of one being better than the other. RoR’s approach ties you at the hip to the database, since you really can’t use the model objects if the database isn’t up (at least not that I’m aware of), however by doing so it adhere’s better to the DRY (don’t repeat yourself) test.

That is, in Grails you need to explicitly define your constraints which then becomes redundant in the underlying database (you can argue that if you allow Grails to modify your schema you aren’t repeating yourself, but the fact of the matter is that that information now exists in two places which means it’s possible for it to be out of sync). Plus you need to learn the Grails way of defining constraints in addition to the SQL way that you probably already know.

That being said, the Grails approach removes the dependency on the database, and in a Utopian world makes dealing directly with the database irrelevant. You could argue that not needing the database lets you write unit tests without needing the database, but realistically almost any application is going to be using at least some of the dynamic finders in GORM which rely on the underlying database to execute.

Summary

I’ll be continuing to do more research and experiementing, but it’s clear to me that we’ll probably be doing more work in Rails and/or Grails over the next year.

Related Articles