Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Tuesday, November 27, 2007

Performance: Groovy/Grails vs. Ruby/Rails?

So, in the comment thread that followed: The Pain of Java after Ruby, I off-handedly commented that I'd been given the impression that Groovy or Grails' performance was worse than that of Ruby or Rails. I was inevitably challenged on that point.

Let me back up a little and cover some of the ground I covered in that comment: I haven't attempted to benchmark Groovy and/or Grails at all; Grails didn't have great support for REST services when I last looked at it, so I didn't include it in some of my performance explorations at the time. We were able to get 600 requests per second from Restlet, and were able to get something on the order of 40 requests per second on the same hardware using Ruby on Rails. I'm sure both of those results could be improved upon. (At the time, JRuby was scoring about 28 requests per second; I gather performance has improved since then).

So, it begs the question: Does Groovy and/or Grails outperform Ruby and/or Rails? You'd certainly like to think so, given the Java VM underneath it, but what have people said?

Well, the Grails folks would probably argue that their solution performs better although it's still clearly a tight race. There's a little grumbling from some people with more Rails expertise, but there doesn't seem to be any outright disagreements.

That said, if you compare either of those solutions back to a Java equivalent, the results are usually pretty striking. There's a few examples of that out there.

So for now, I'll say that it seems like Groovy may have the edge on performance compared to Ruby, although it's still very much a two-horse race, and both have many further opportunities to gain ground.

However, it's also clear that both of these two solutions are, in the overall spectrum of development platforms, relatively slow. Depending on what you're building, that may not be a big deal, but it can certainly be a concern.

Thursday, August 30, 2007

Rails: Resource Links in Resource List

Navigating a REST application should not require a deep understanding of the namespace of the application's addressable resources. As much as possible, one REST query should lead to another.

The most obvious case of this is when one has a resource that is a list or collection (e.g. /customers) of the available resources (e.g. /customers/1), e.g.:


<customers>
<customer>
<name>The Customer</name>
<a href="myserver/customers/1.xml" />
</customer>
</customers>


And yet, it's surprisingly difficult to put these kind of links into Rails resources, particularly if you use the approach that resource_scaffold suggests.

Resource scaffold generates the XML representation of the collection as by calling the to_xml method that Rails adds to Array, which in turn calls to_xml on the model classes, something like this:

@customers = Customers.find(:all)
render :xml => @customers.to_xml


In order to add links to the XML representation, you'd need two pieces of information: the URL structure for resource and the id of the resource. The ActionController has the most understanding of the URL structure (as it can use
url_for and named resource helpers like customer_path), but the model class knows its id and is responsible for generating the XML.

This awkward split in the available information makes adding these links to the XML somewhat painful; perhaps no more painful than building the XML by hand would normally be, but it's a departure from the usual ease of accomplishing common tasks in Rails.

After trying a number of alternatives, we settled on approach. In the controller, we do:

# GET /customers.xml
def index
@customers = Customer.find(:all)
respond_to do |format|
format.xml do
render :xml => @customers.to_xml( :format => :summary, :base=>url_for(:controller=>'customers') )
end
end
end

# GET /customers/1.xml
def show
@customer = Customer.find(params[:id])
respond_to do |format|
format.xml { render :xml => @customer.to_xml( :format => :full ) }
end
rescue ActiveRecord::RecordNotFound
head 404
end


This relies on some model code, like this:

def to_xml( options = {} )
case options[:format]
when :summary
resource_url = proc { |options| options[:builder].a(:href=>"#{options[:base]}/#{id}.xml") }
super( options.merge!( :only => [:id, :name], :procs => [resource_url] ) )
when :full
super( options.merge!( :include => [ :main_contact_info, :billing_contact_info ],
:except => [ :main_contact_id, :billing_contact_id ] ) )
else
super( options )
end
end


It's still a little awkward, and I'm hopeful that we'll find a better alternative, but it does the job and is less ugly than some of the other alternatives we considered.

The only other option we looked at that seems reasonable is to generate all the XML within the controller, which means reproducing the work done by Array.to_xml, but is otherwise relatively sensible.

Friday, August 24, 2007

Rails Plugins: Globalize

Imagine that you've built yourself a small application in Ruby on Rails. Here's the good news: there are all sorts of ways to simply add significant functionality to your Rails applications through plugins. Let me introduce you to a few.

I've been investigating the Globalize and Acts As Audited (acts_as_audited) and Acts As Versioned (acts_as_versioned) plugins for Ruby on Rails over the past few days. I've got a lot to share, so I'll break it up into a few posts.

Let's start by imagining that you've built yourself a really, really simple model:
You've cut a customer model class with a unique identifier and a name. Now, let's add to that.

Globalize
If your application needs to operate in a multilingual or global environment, you'll be pleased to discover that not only is Globalize a general-purpose internationalization and localization (i18n/l10n) mechanism for Ruby on Rails, but it can even translate your model classes.

If you're going to translate fields within your model, you should be aware that it has two storage models for the translated information. Historically, it did this by storing the translated text in a separate table, globalize_translations. I'll call this the external storage model.

Using our model from above, the name field corresponds to the name in the base language (whatever you decide that is; en-US is a common choice in this part of the world), and all other translations are stored in the globalize_translations table as follows:

When you load or save your model class within the context of a particular Locale, Globalize does its magic behind the scenes to make sure that the text is loaded from or saved to the appropriate spot -- base language text goes to the primary table, and text from any other language goes to the translated-text table.

As often seems to be true, this magic interferes with some of ActiveRecord's default mechanisms, and :select and :include clauses stop working as advertised. There are workarounds, apparently.

However, in recent versions of Globalize, you have another choice - you can store the translations directly in your model, as follows:

This interferes less with ActiveRecord, although it's better-suited to applications that need to work with a known number of languages up front, rather than an unknown and changing set of languages, as each language addition requires new columns for every translatable field. It has a few caveats of its own.

Thursday, August 23, 2007

Globalize 'Internal Storage', First Look

I took a moment this morning to examine the new internal storage model for Globalize, wherein translations are kept within the original table. It seems to have a few more caveats:

  • Rails doesn't instinctively understand that the translated column names are not otherwise normal attributes; that means that any "iterate over all fields" code may have the translated columns show up as well. (e.g. scaffold show html).
  • If you haven't inserted the translation column for a locale to which the user can set, rather than using the base language, the user will not see any value. So, for instance, I turned on internal-storage on an example class with a single translated 'name' field. I created an example record (e.g. name='Hello') and switched to an 'fr' locale; instead of getting the contents of the name field ('Hello'), I got an empty string ('').
It's an interesting choice, mostly because it breaks less of Rails than Globalize's other model. I prefer the 'external storage' approach in many cases, particularly if you're dealing with a product or SaaS application where the number of supported languages is not clearly known up-front.

Wednesday, August 8, 2007

Ruby on Rails on Bamboo: Unit Tests

In my previous post about Continuously Integrating Ruby on Rails with Bamboo, I walked through a simple setup for a Ruby on Rails project in Bamboo. That setup is a good starting point, but it's missing useful elements, like understanding the unit tests within the project, and test coverage:


Unit Testing Ruby on Rails with Bamboo
Let's start with unit tests. Bamboo understands the JUnit XML format, which Ruby doesn't normally emit (what with not using JUnit and all). Fortunately, someone's written a Rails plugin called ci_reporter that emits these files for us.


$ gem install ci_reporter
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed ci_reporter-1.3.3
Installing ri documentation for ci_reporter-1.3.3...
Installing RDoc documentation for ci_reporter-1.3.3...
$ script/plugin install http://svn.caldersphere.net/svn/main/plugins/ci_reporter
+ ./ci_reporter/tasks/ci_reporter.rake
$ svn add vendor/plugins/ci_reporter/
A vendor/plugins/ci_reporter
A vendor/plugins/ci_reporter/tasks
A vendor/plugins/ci_reporter/tasks/ci_reporter.rake
$ svn ci -m "Added CI reporter to project."
Adding vendor/plugins/ci_reporter
Adding vendor/plugins/ci_reporter/tasks
Adding vendor/plugins/ci_reporter/tasks/ci_reporter.rake
Transmitting file data .
Committed revision 411.


You'll probably have to ensure the ci_reporter gem is installed on your instance of Bamboo as well. Once this is complete, you'll want to modify your Plan:



And build the project:


See? Tests! Bamboo can find the XML files generated in test/reports, and use them to understand the testing of your Ruby on Rails application. Now, in a Java/JUnit project, Bamboo does a fine job of turning the test methods into readable names, but for now that's not true with Test::Unit test names, but they're working on it.


Next step: Coverage. But that'll be another day.