Over the past couple of weeks, I’ve been upgrading a particularly large and older Rails 3 app to Rails 4. This app uses Oracle against a legacy database, meaning a large chunk of the data structure does not follow Rails conventions.
One of the examples is that the primary key we use on the tables is not named
id but is instead called something like
row_no. This is defined in the table as a
NUMBER type with no precision or scale (in Oracle, you can define types like
NUMBER(9,2) which would have 9 significant digits, 2 of which are after the decimal). To access the database, we’re using the excellent Oracle Enhanced adapter. However, by default, the adapter treats columns defined as
NUMBER with no precision or scale as a
decimal type. Since we’re using them as IDs, we expect them to be integers.
Prior to the Rails 4 upgrade, we had forked the adapter library to change this behavior. However, that left us in the position of having to maintain our own library – something I didn’t want to do. In looking at the latest version of the adapter, they had added the ability to configure the behavior of
NUMBER types using an initializer: `
ActiveSupport.on_load(:active_record) do ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do self.emulate_integers_by_column_name = true def self.is_integer_column?(name, table_name = nil) !!(name =~ /row_no$/i) end end end
This worked great to convert all of the columns. But, we discovered along the way that, for one specific table, this logic wasn’t being applied. The adapter has a method called simplified_type which applies the appropriate logic. But when I put a breakpoint on it, I noticed that the logic wasn’t applied for this one specific table.
The first clue came at the timing of the breakpoint. I noticed that I hit the breakpoint for the trouble table during startup of the rails console, while the other tables weren’t hit until I interacted with them in the console. The second clue was then when I hit the breakpoint,
emulate_integers_by_column_name was set to false – even though I had it set above. The third clue was remembering that initializers are run in alphabetical order and since my Oracle initializer was named, well, oracle.rb, there was a good chance something was running ahead of time.
And there was – but it wasn’t what I was expecting. Because this is a legacy database, there are some specific sanity checks we run on startup to check certain aspects of it. And sure enough, we were doing a call which caused the problematic table’s model to become initialized. That meant that table would be called – and column mappings cached – before we had the chance to configure the database to have the behavior we really needed.
We solved the issue by renaming the database adapter initializer to run first (
This problem represents the joy and challenge of debugging problems. The debugger was able to tell us the state of the application, but it should provide input for hypothesis. The column isn’t converted – what could lead to that? The flag isn’t set – when should it be set, and why wouldn’t it be? When could code run before other code? So when you face a challenge, start with a hypothesis before the debugger – not only will you potentially learn more, you might be able to deploy it in the right spot. Use it to answer questions.