First some background: I've got a Rails 2.2.2 app with all kinds of janky dependencies and customizations that keep me from upgrading past 2.2.2 for the moment. Current plan is to migrate this app to JRuby (1.6.5), deploying it as a war in Tomcat (6.0.23—not yet tied to this particular version FWIW), using warbler (1.3.2) to package it up.
I want to keep the JRuby branch of the app from diverging substantially from the vanilla app since I don't know how long it's going to take to tease out and fix all of the issues caused by discrepancies in the runtime environments. So I want to be able to continue to cleanly and easily merge in ongoing work from the vanilla app.
A number of customized behaviors in the app can be configured with globally namespaced CONSTANTs set in the environments/*.rb
files. But these values can be overridden by using a yaml configuration file or setting a shell environment variable; this is primarily for flexibility during development, where different devs sometimes need to customize various settings. This system has worked well so far, leaving our environments/development.rb
file relatively stable and uncluttered while still giving us a great deal of control over customization as we're hacking.
I would like to preserve this system of customization in the move to JRuby. So far, I'm using a customized config/web.xml.erb
that converts relevant environment variables and settings from the yaml file into <env-entry>
fragments, which makes them available during initialization via the java:comp/env
JNDI context. In more restricted environments (like staging or production), many of these settings can be locked down by including corresponding <Environment>
settings in the container's context.xml
file with the override
attribute set to false
.
So far, so good. But now I am struggling to turn the values retrieved from the JNDI context into global namespace CONSTANTs during initialization. Here are excerpts of the relevant bits:
config/environments/developer.rb:
...
envset "FOO", "baz"
FOO="fail" unless defined? FOO
printf "FOO is now '%s'\n", FOO
...
lib/config_helper.rb:
1 include Java
2 import javax.naming.InitialContext
3 import javax.naming.NameNotFoundException
4
5 ctx = InitialContext.new
6
7 def envset(value_name, default_value)
8 value = nil
9 begin
10 value = ctx.lookup("java:comp/env/#{value_name}")
11 rescue NameNotFoundException => e
12 value = default_value
13 end
14 printf "setting %s to '%s'\n", value_name, value
15 eval("%Q[ #{value_name} = '#{value}' ]")
16 end
web.xml:
...
<env-entry>
<env-entry-name>FOO</env-entry-name>
<env-entry-value>bar</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
...
catalina.out:
setting FOO to 'bar'
FOO is now 'fail'
Where I am expecting this last line to be "FOO is now 'bar'", of course. Line 14 of lib/config_helper.rb
seems to be where things are going wrong. Similar code has worked fine in vanilla ruby, but it doesn't work here during initialization in Tomcat.
So, how can I create globally namespaced CONSTANTs out of JNDI environment entries during Rails initialization in Tomcat?
A good rule of thumb is that there is always a better answer than eval()
. Replacing line 15 with the following line correctly creates constants in the global namespace:
Object.const_set(value_name, value)
I'm still not sure what namespace the original was creating constants in, but Object
appears to be the "global" one.