I've had an issue with a project I'm working on, where I am creating a nested hash of records to allow quick lookup by certain fields, the reason for this is as follows. Say for example I have 100,000 despatch lines, and 100,000 self bill lines, and I have a process which will loop through each despatch line, and find the corresponding self bill line. I could easily do this with a simple find:
SelfBillLine.find(:first, :conditions => ["manifest_number = ? and order_number = ? and part_number = ?", despatch.manifest_number, despatch.order_number, despatch.part_number])
But this means running 100,000 SQL queries, and experience tells me this will be sloooowww.
I could also just get a list of all the self bill lines, and then use Ruby's select method to find the one I want, but this will be quite demanding on Ruby. So my solution was as follows, create a nested hash:
h = {}
self_bill_lines.each do |line|
h[line.manifest_number] ||= {}
h[line.manifest_number][line.order_number] ||= {}
h[line.manifest_number][line.order_number][line.part_number] ||= line
end
Then I could find a matching line by doing:
matching_line = h[despatch.manifest_number][despatch.order_number][despatch.part_number]
Bonzer, works a treat. Now my next problem was that I want this to be easily re-usable in a new project, that may have different fields that are used to match with. Ideally I wanted to be able to define the fields used in an array, ie. ["manifest_number","order_number","part_number"]. Then maybe for the next project, it could be ["manifest_number","invoice_number","part_number","price"].
So I came up with this, I need to create a method (I'll call it hashify) at runtime, based on the contents of that array. Here's the resulting code:
f = Customize::matching_fields
str = "
def hashify(lines)
h = {}
lines.each do |line|
h[line.#{f[0]}] ||= {}
"
if f.length > 1
1.upto(f.length - 1) do |i|
str_line = " h[line.#{f[0]}]"
1.upto(i) do |j|
str_line = str_line + "[line.#{f[j]}]"
end
if i == f.length-1
str = str + str_line + " ||= line"
else
str = str + str_line + " ||= {}\n"
end
end
end
str = str + "
end
h
end
"
eval(str)
This basically creates a string of the same method as before, based on whats in the matching_fields array, so if I change the array to ["manifest_number","invoice_number","part_number","price"], it produces:
def hashify(lines)
h = {}
lines.each do |line|
h[line.manifest_number] ||= {}
h[line.manifest_number][line.invoice_number] ||= {}
h[line.manifest_number][line.invoice_number][line.part_number] ||= {}
h[line.manifest_number][line.invoice_number][line.part_number][line.price] ||= line
end
h
end
Then the eval method basically executes the code in the string, and the hashify method is born. And by golly it works, and is being used in my app, I love Ruby!
<!--break-->

Comments