Dealing with JRuby Jar Deployments – Reading Files
One of the nice things about using JRuby is being able to use Warbler to package up all the files and scripts into a single jar for easy deployment. One of the problems with that is that some of the common coding statements don't work the way you expect them to - but they do work.
When I had an issue with reading a CSV file in a jar, my solution was to "go to the metal" and work it out in Java. This worked, and it was OK, but it wasn't something that was transportable to a non-JRuby environment, and I wanted to have that. So this morning I tackled just that.
The thing that's been bugging me was that Configulations works just fine in our code - jar or no-jar, so why was that working and the file reads of CSV files not? What I needed to do was to look seriously at how they were being used.
We were using Configulations in the following snippet:
require 'configurations' require 'date' AppConfig = Configulations.new(File.dirname(__FILE__)+'/../config')
I know that JRuby 1.7.0 doesn't like the use of the '/../' in the path in a jar as it tries to "walk" the structure, so I changed this to a slightly less round-about way:
require 'configurations' require 'date' AppConfig = Configulations.new(File.dirname(File.dirname(__FILE__))+'/config')
This allows JRuby to handle the parsing of the path as it sees fit and I don't have to worry about moving around within the jar file like I can in a filesystem.
And then it hit me… how we're using the filenames in the reading of the CSV files:
require 'csv' require 'json' module FileUtility DATA_FILE = 'lib/sales/histData.csv' # ... if File.exists?(filename) File.open(filename) do |file| contents = file.read end else # ... end # ... end
and it stood out as clear as can be - the path was wrong. We were looking for these CSV files in a directory relative to the existing directory. How was JRuby to know that we wanted to look within the jar? Impossible.
The Configulations example worked because it used the dirname() method relative to the existing file - which is in the jar. That means that we were the ones telling JRuby to look in the jar (or on the filesystem), and it was all about the path we prepended to the beginning of the file we wanted to load.
There was no need to have the Java solution - we just needed to be more careful with the location of the CSV files. What we have now is far simpler:
def self.read_file(filename) contents = '' File.open(project_root + '/' + filename) do |file| contents = file.read end contents end def self.read_csv(filename, key) res = {} CSV.parse(read_file(filename), :headers => true).each do |rec| k = key.map { |c| rec[c] } res[k] = rec end res end def self.read_json(filename) JSON.parse(read_file(filename)) end def self.project_root @root ||= File.dirname(File.dirname(__FILE__)) end
where once again, we use the "double dir" method chain to get the parent's directory, and use that to know that it's the root of the project - based on the location of this file in the project. This is far, far simpler than we had in the past, and it's removed all the silly Java code that wasn't really necessary in the first place.
It's important to realize you solved a problem, but not in the way it was intended. I'm glad I went back and fixed this. Very glad.