views:

161

answers:

3

I have certain initializing functions that I use to set up audit logging on the DB server side (ie, not rails) in PostgreSQL. At least one has to be issued (setting the current user) before inserting data into or updating any of the audited tables, or else the whole query will fail spectacularly.

I can easily call these every time before running any save operation in the code, but DRY makes me think I should have the code repeated in as few places as possible, particularly since this diverges greatly from the ideal of database agnosticism. Currently I'm attempting to override ActiveRecord::Base.establish_connection in an initializer to set it up so that the queries are run as soon as I connect automatically, but it doesn't behave as I expect it to. Here is the code in the initializer:

class ActiveRecord::Base
  # extend the class methods, not the instance methods
  class << self
    alias :old_establish_connection :establish_connection # hide the default

    def establish_connection(*args)
      ret = old_establish_connection(*args) # call the default

      # set up necessary session variables for audit logging
      # call these after calling default, to make sure conn is established 1st
      db = self.class.connection
      db.execute("SELECT SV.set('current_user', 'test@localhost')")
      db.execute("SELECT SV.set('audit_notes', NULL)") # end "empty variable" err

      ret # return the default's original value
    end
  end
end

puts "Loaded custom establish_connection into ActiveRecord::Base"
sycobuny:~/rails$ ruby script/server 
=> Booting WEBrick
=> Rails 2.3.5 application starting on http://0.0.0.0:3000
Loaded custom establish_connection into ActiveRecord::Base

This doesn't give me any errors, and unfortunately I can't check what the method looks like internally (I was using ActiveRecord::Base.method(:establish_connection), but apparently that creates a new Method object each time it's called, which is seemingly worthless cause I can't check object_id for any worthwhile information and I also can't reverse the compilation).

However, the code never seems to get called, because any attempt to run a save or an update on a database object fails as I predicted earlier. If this isn't a proper way to execute code immediately on connection to the database, then what is?

A: 

Do you know that your initializer is being called before the database connection is established?

Without authoritative information on what could be wrong, I'd take advantage of the fact that the source to ActiveRecord is on my hard drive.

Dig into the base class and do a little puts debugging to see when ActiveRecord establish_connection is being called relative to when your initializer is called. Either that or just turn on extra verbose debugging.

Chris
+1  A: 

Rails uses connection pooling, so your best bet is to use an alias_method_chain for ActiveRecord::ConnectionAdapters::ConnectionPool#new_connection

module ActiveRecord
  Module ConnectionAdapters
    class ConnectionPool
      alias_method_chain :new_connection, :my_stuff
      private
      def new_connection_with_my_stuff
        c = new_connection_without_my_stuff
        # Do your stuff with 
        # c.exec(<your sql command>)
        c
      end
    end
  end
end
TekWiz
i believe alias_method_chain needs to to be run after the method is declared.
Kevin
alias_method_chain does need to be called after the method is declared, or else it won't find the method. Also, the method to run off of c is execute, rather than exec (I believe the error it gave me was that exec was private). But otherwise, this solves the issue I was having. Thanks!
sycobuny
A: 

In terms of writing code to hook rails database calls and initializations, you might want to use the code from db-charmer as a template. It demonstrates a number of interesting ways of interacting with ActiveRecord and one of them will probably do what you want.

corprew