Making today worse so tomorrow seems better.
JNDI with Rails
I like to explain the relationship between Rails and Java is that Rails makes the things I hate doing easy and Java makes the things I like doing possible.
In this mind set, when using Rails on JRuby I want to do as little configuration in Java (XML sit-ups are bad) and leverage Rails as much as possible (a few yml stretches are good). The most common configuration cross over is the database connection. This is where JNDI helps out, now both sides can use the same connection pool.
Putting your ducks in a row
For the following examples to work, you will need the follow depedencies:
- Sun’s JNDI File System Service Provider. Follow “Download JNDI 1.2.1 & More” to download File System Service Provider. You will need the fscontext and the providerutil jars both in you classpath. I would like to take this moment and say that Sun needs to get their head out of the mud and join the Maven party, instead of forcing people to navigate their cumbersome site.
- Maven will make you hate less. Manually managing the cadre of jars that Java demands will just chalk you full of spite. While Maven does not alleviate the problem, it does put a purdy bow on it
- A given, you must already have gone through the steps of setting up a JRuby runtime. Steps might be to strong of a word, basically it is unpackage and go.
Configuring Rails to create its own JNDI and Connection Pool
Now it is time to get the show on the road. The following setups will show how to startup a JNDI instance, register a Connection Pool, and then use the same Connection Pool in Rails and in a Spring
Setting up the database.yml
The latest version of activerecord-jdbc already supports using JNDI for connections. By setting additional information in database.yml, jndi_factory_initial and jndi_provider_url, a connection pool can be built and registered into JNDI. The jndi_factory_initial relates to the JNDI property java.naming.factory.initial, which boils down to the JNDI service that is going to be created. The jndi_provider_url relates to the JNDI property java.naming.provider.url, which is the URL to connect to the JNDI service. The standard setting, jndi, is going to be used as the location to store and retrieve the Connection Pool.
JRuby Service in Rails
I have two worlds that need to collide. Rails solves the decade long problem of abusive Java web stacks and Java provides mature codebase to the Rails paradigm. JRuby has brought me the spark, now if I can only get the fire burning.
The problem: I need a Spring context to startup with Rails and use the config/database.yml.
The solution: A JRuby Service.
I want to expose tons of existing Java code and libs into Rails, but do not want to deal with a segmented world. Everything should be configured and started from one place, this is were a JRuby Service helps. The JRuby runtime will automatically load a class implementing a JRuby service interface (such as org.jruby.runtime.load.BasicLibraryService) when it is packaged in a specifically named jar at a specifically named placed.
Originally I was jumping though hoops trying to call static singletons, but ended up scratching my head how to get access to the Rails runtime. After finding hints from Ola Bini, with some searching and source diving, I found the javadoc for org.jruby.runtime.load.LoadService has the key. (An aside, it would be nice if the JRuby folks published the JavaDocs, even if it changed with every release).
Except from LoadService.java JavaDoc:
How to make a class that can get required by JRuby
First, decide on what name should be used to require the extension. In this purely hypothetical example, this name will be ‘active_record/connection_adapters/jdbc_adapter’. Then create the class name for this require-name, by looking at the guidelines above. Our class should be named active_record.connection_adapters.JdbcAdapterService, and implement one of the library-interfaces. The easiest one is BasicLibraryService, where you define the basicLoad-method, which will get called when your library should be loaded.
The next step is to either put your compiled class on JRuby’s classpath, or package the class/es inside a jar-file. To package into a jar-file, we first create the file, then rename it to jdbc_adapter.jar. Then we put this jar-file in the directory active_record/connection_adapters somewhere in JRuby’s load path.
The short and skinny of this? The package defines the directory and the class name determines the jar name. The package period separators are converted to slashes, so the package com.slackworks would be the directory com/slackworks. For brevity, this example uses the simple package slackworks, for a directory of slackworks. The class name, sans Service and converted from CamelCase to under scores determines the jar name. So the RailsSpringService class translates to rails_spring.jar
Wildcard DNS and Rails
The most common Rails config wants a separate hostname for each application. If you tend to create new ones frequently, this can mean a lot of running back and forth to your DNS config. At best this means waiting for the changes to propagate, at worst it could mean waiting for corporate IT to get around to it.
Luckily, in addition to normal names, DNS allows you to create “wildcard” records that match any name not otherwise defined.
We setup each hostname with their own wildcard by default, so that foo.hostname is always an alias (CNAME) for hostname.
sprocket IN A 69.27.234.180 *.sprocket IN CNAME sprocket
This means that if I want to deploy my new app, The Awesomator, I can use the hostname ‘awesomator.sprocket.slackworks.com’ without having to change anything in DNS.
Then I drop my virtual host config awesomator.conf file, largely stolen from Coda Hale’s config , into Apache’s config directory (/etc/apache2/sites-available/ on Debian, /etc/httpd/conf.d/ on Red Hat). An Apache reload and a few mongrels later and I’m up and running on my new hostname.
Sprint Error "911" and the "Download Domain"
Trying to load a new version of a J2ME app on a Sprint v9m, I got this helpful error.
The issue has been reported. Please try again later. 911
Great, now I’m reported. But for what?
After a lot of fruitless Google work (911 is a noisy search term), even checking the unofficial Every Sprint 3g Error Code page, I settled in on trial and error.
It turns out that this is a kind of permissions problem, caused by the “Download Domain” of the new version not matching the old one.
When an application is downloaded the device stores the hostname part of the URL, any updates to the application must come from the same hostname. So if you downloaded it from www.foo.com the first time, you can’t update it from m.foo.com.
On the v9m (RAZR 2) you can find out what the current “Download Domain” is for a specific application by going to the “Applications” folder under “My Content” and bringing up the “Options” → “Properties” menu for it. The “Download Domain” will be listed at the bottom of the scrollable page.
Getting a Good View of Your Couch
So far I have been doing all my CouchDB queries using “temporary views” which are Javascript strings which are POSTed to the database and used to select the records you’re interested in. A more efficient way to do it is to save the Javascript strings as documents, these can be called with less traffic and a simpler API. “Permanent views” also create cached indexes, so they offer performance advantages over temporary views as well.
Tim Kofol took my ideas and ran with them, creating an ingenious way to maintain CouchDB views right in the Ruby code. Witness:
class Post < Blarg::CouchBase
couch_accessor :tags
couch_view :comments_view, %[
function(doc) {
if (doc.type "Comment") {
map(doc.post_id, doc);
}
}
]
couch_view :all_tags_view, %[
function(doc) {
if (doc.type ‘Post’) {
map(null, doc.tags);
}
}
]
def self.all_tags
self.all_tags_view.flatten.uniq.sort
end
def comments
comments = self.class.comments_view(:key => self.document_id)
comments.sort{|a,b| a.created_at <=> b.created_at}
end
end
With the views relevant to this class defined in the class itself, we need to load them into CouchDB. I added Rake task which finds all views defined in the project and loads them into CouchDB, creating them or replacing older versions.
rake db:views:load
Bring Selenium to the integration party
Running Selenium on a headless box. Not particularly complex, but amazingly useful for integration testing.
Do you wish you could leverage the power of Selenium? Do you long for it’s “automated web application UI tests”, but are boned by some headless horseman of a server? I have felt your pain. Tons of UI tests, but unable run them on the integration server. Ah, but there is a light at the end of the tunnel (that isn’t convincing the hosting company to attach a monitor)!
Here is the gist of it:
nohup Xvfb :4 -screen 0 1024x768x24 2>&1 > xvfb.log & sleep 3 # give Xvfb a few seconds to sort itself out DISPLAY=4 nohup java -jar selenium-server.jar 2>&1 > selenium.log &
Basically, Xvfb creates a virtual X session, without the need for those kludgey monitors and keyboards. The Selenium Remote Control server is started up with the newly created Xvfb display, that is the DISPLAY=4 part. That is that, the Selenium server is now running on the default 4444 port using Xvfb.
Bj Makes Attachment_fu Happy
The attachment_fu plugin for Rails is great, and it’s support for S3 as a backend sounds really handy. Sadly tho ugh, it isn’t practical for anything other a demo or a proof-of-concept. Luckily its limitations can be worked around with the cunning use of Bj .
When you upload a file to a Rails app, your browser waits while Mongrel buffers the file, then your browser waits some more while Rails processes the fully uploaded file. Normally this “processing” is simply copying the file from where Mongrel put it to where Rails wants it, no big whup. The problem arises when this “processing” is substantially more time consuming, like, say transmitting the file to S3. There’s no guarantee that this will take place in a reasonable amount of time and meanwhile, not only is the user’s browser getting nearer to timing out, but that whole Rails instance is going to block and no one else can use it either. Rails is single threaded, remember?
The solution is to write the file to disk like normal, then spawn a process in the background to take care of uploading it to S3. This is where Bj comes in. Install thusly:
./script/plugin install http://codeforpeople.rubyforge.org/svn/rails/plugins/bj ./script/bj setup rake db:migrate
Bj is a light weight work queue that uses your app’s database as a store. Requests are put into the bj_job table and run one at a time outside of the mongrel_rails process. Say we have a model UploadFile that uses attachment_fu:
Restore Finder magic to a sparsebundle directory
This is a quick fix to restore t he special behavior of a .sparsebundle directory in Finder.
While experimenting with TimeMachine’s unsupported network backup capabilities I rsync’d my host_blah.sparsebundle from one drive to another (without using -E, more on that later). The result was that my .sparsebundle now appeared in the Finder just as a normal folder, instead of with a disk image icon. This also meant that I couldn’t open the image in DiskUtility any more.
The fix is to use SetFile to restore the “Bundle” attribute.
$ /Developer/Tools/SetFile -a B host_blah.sparsebundle
The first time I mounted it afterwards I had to wait for fsck_hfs to complete, since the “com.apple.diskimages.fsck” attribute had been cleared, but otherwise it’s back to it’s old self.
First Post
What do we have here?
It would seem to be yet another blog, and it is. This one, however, is built with merb and CouchDB so building it gave me a chance to experiment with some interesting projects that I don’t get to use in my daily life as a professional web developer.
The code for this site is available to the many-eyes beasts of the Interwebs here:
git://sprocket.slackworks.com/srv/git/blarg.git
It requires merb and friends, as well as an instance of CouchDB to be running somewhere. There are a few reasons I chose merb over Rails (which is what I generally use professionally) to do this:
- It comes with no pre-packaged ORM. I wanted to use CouchDB, so none of the existing ORMs would really work for me. My previous experiences of trying to rip ActiveRecord out of Rails have been frustrating.
- It has more flexible routing than Rails. I realize there are ways to accomplish the things I’m doing here in Rails, but the merb routes seem clearer to me, less hand-wavy. And perhaps most importantly:
- It’s not Rails. I use Rails all day long, and I wanted to see what else is out there.
- About
- Technology, programming, the interwebs and other topics by members of the slackworks community.
- Recent
- Jul 23 2008 JNDI with Rails
- Jul 06 2008 JRuby Service in Rails
- May 16 2008 Wildcard DNS and Rails
- Jan 30 2008 Sprint Error "911" and the "Download Domain"
- Jan 16 2008 Getting a Good View of Your Couch
- Jan 12 2008 Bring Selenium to the integration party
- Jan 12 2008 Bj Makes Attachment_fu Happy
- Jan 03 2008 Restore Finder magic to a sparsebundle directory
- Dec 18 2007 First Post
- Tags