Commit nested transaction

Let's say I have a method that provides access to an API client within a user, and the API client will automatically refresh the user's OAuth tokens when they expire.

class User < ActiveRecord::Base

  def api
    ApiClient.new access_token: oauth_access_token,
                  refresh_token: oauth_refresh_token,
                  on_oauth_refresh: -> (tokens) {
                    # This proc will be called by the API client when an 
                    # OAuth refresh occurs
                    update_attributes({
                      oauth_access_token: tokens[:access_token],
                      oauth_refresh_token: tokens[:refresh_token]
                     })
                   }
  end

end

      

If I use this API in a Rails transaction and an update happens and then an error occurs - I cannot transfer the new OAuth tokens (since the above is also considered part of the transaction):

u = User.first

User.transaction { 
  local_info = Info.create!

  # My tokens are expired so the client automatically
  # refreshes them and calls the proc that updates them locally.
  external_info = u.api.get_external_info(local_info.id)

  # Now when I try to locally save the info returned by the API an exception
  # occurs (for example due to validation). This rolls back the entire 
  # transaction (including the update of the user new tokens.)
  local_info.info = external_info 
  local_info.save!
}

      

I'll simplify the example, but basically the consumption of the API and the persistence of the data returned by the API should happen in a transaction. How can I guarantee that the user's token update will be committed even if the parent transaction fails.

+3


source to share


3 answers


You tried to open a new db connection inside a new thread and on that thread do an update

u = User.first

User.transaction { 
   local_info = Info.create!

   # My tokens are expired so the client automatically
   # refreshes them and calls the proc that updates them locally.
   external_info = u.api.get_external_info(local_info.id)

   # Now when I try to locally save the info returned by the API an exception
   # occurs (for example due to validation). This rolls back the entire 
   # transaction (including the update of the user new tokens.)
   local_info.info = external_info 
   local_info.save!

   # Now open new thread
   # In the new thread open new db connection, separate from the one already opened
   # In the new connection execute update only for the tokens
   # Close new connection and new thread
   Thread.new do
      ActiveRecord::Base.connection_pool.with_connection do |connection|
         connection.execute("Your SQL statement that will update the user tokens")        
      end
   end.join
}

      



I hope this helps

+3


source


This discussion from the previous question might help you. It looks like you can set a flag requires_new: true

and essentially mark the child transaction as a sub transaction.



User.transaction { 
  User.transaction(requires_new: true) { 
    u.update_attribute(:name, 'test') 
  }; 

  u.update_attribute(:name, 'test2'); 

  raise 'boom' 
}

      

0


source


Nermin (accepted) answer is correct. Here is an update for Rails> = 5.0

Thread.new do
  Rails.application.executor.wrap do
    record.save
  end
  # Note: record probably won't be updated here yet since it async
end

      

Documented here: Rails Manages Threads and Concurrency

0


source







All Articles