11

Is there any way to hook into ActiveRecord connection creation? I want to run some code whenever a connection has just been created.

I feel like it might be a way to set a MySQL variable on the connection, since "variables" in database.yml doesn't seem to work for me. (How to turn off MySQL strict mode in Rails)

Community
  • 1
  • 1
mahemoff
  • 44,526
  • 36
  • 160
  • 222

3 Answers3

12

The ConnectionAdapter defines two callbacks :checkout (connect) and :checkin (disconnect). You can use it for specific adapter as

ActiveRecord::ConnectionAdapters::MysqlAdapter.set_callback :checkout, :after do
  raw_connection.set_your_variables ...
end

Or you can use ActiveRecord::Base.connection.class for whatever adapter is currently declared in database.yml

Vladimir
  • 191
  • 3
  • 4
  • 2
    Please note that as of Rails 5.2 this callback fired not once after connection established but on every checkout from the connection pool (think as before processing of every HTTP-request). Also, you can't call model methods from it (you will get endless recursion). – Envek Jan 12 '18 at 17:51
  • @Vladimir As a part of row level security, we need to set tenantId as database session variable for each DB connection the application makes. Here is my code, ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :after, ->(conn) { unless (ActsAsTenant.current_tenant.nil?) res = conn.execute("SET session.current_co_id ='#{ActsAsTenant.current_tenant.id}'") end } Could you suggest if it's the right use-case to use AbstractAdapter's checkout callback ? – Vivek Pratap Singh Oct 21 '20 at 11:50
2

Also, if you need to configure your model after connection has been made and column information was retrieved, you can redefine load_schema! class method in model.

See: https://github.com/rails/rails/pull/31681#issuecomment-357113030

Envek
  • 4,426
  • 3
  • 34
  • 42
  • As a part of row level security, we need to set tenantId as database session variable for each DB connection the application makes. Here is my code, ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :after, ->(conn) { unless (ActsAsTenant.current_tenant.nil?) res = conn.execute("SET session.current_co_id ='#{ActsAsTenant.current_tenant.id}'") end } Could you suggest if it's the right use-case to use AbstractAdapter's checkout callback ? – Vivek Pratap Singh Oct 21 '20 at 11:47
1

Just to add to the accepted solution, if you need to fire a database query from within the callback, you can do so in the following way:

ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :before, ->(conn) {
    res = conn.execute('SELECT COUNT(*) from table_name')
    puts "res is #{res.each { |tuple| puts tuple.inspect}}"
}

Note: since callback is defined on ActiveRecord::ConnectionAdapters::AbstractAdapter, this should be executed before checking out connection for ANY database type

As @Envek pointed out in comment to the accepted answer, you should not really use ActiveRecord models since you risk running into endless recursion. Also (also noted by @Envek) keep in mind that the callback will be fired on each connection checkout with pooled connections, not just once.

Finally, the chances are, you want to use this from Rails. If so, you can place this code in a file under config/initializers folder. Then it would be run on application startup. Useful for cases where you want to add logic to how database connection is established, especially if precise action depends on results of a specific query.

wondersz1
  • 757
  • 8
  • 15
  • As a part of row level security, we need to set tenantId as database session variable for each DB connection the application makes. Here is my code, ActiveRecord::ConnectionAdapters::AbstractAdapter.set_callback :checkout, :after, ->(conn) { unless (ActsAsTenant.current_tenant.nil?) res = conn.execute("SET session.current_co_id ='#{ActsAsTenant.current_tenant.id}'") end } Could you suggest if it's the right use-case to use AbstractAdapter's checkout callback ? – Vivek Pratap Singh Oct 21 '20 at 12:11