Object_transactions plugin w/2.x disables AR session store

All,

Win XP
Rails 2.0.2
object_transactions plugin
(http://code.bitsweat.net/svn/object_transactions/)
transaction-simple gem 1.4 (required by object_transactions plugin)
AR SQL Server Adapter 1.0
SQL Server 2000

Using the object_transactions plugin with Rails 2.0.2 (and I assume
based on what I’m seeing that this goes for Rails 2.x) disables the
ability to save to an ActiveRecord based session store (e.g. the
sessions table).

I haven’t verified this in SQLite or MySQL, but am suspecting that the
behavior would be the same.

What appears to happen is that the AR::save call is not ever made under
this configuration (see the
CGI::Session::ActiveRecordStore::Session.update method in
action_controller/session/active_record_store.rb - the call to
ActiveRecord::Base.save is never made from here).

Not sure what’s happening yet, but I am sure that if I remove the
object_transactions plugin from my app., that my AR-based sessions will
start saving again.

Wes

One last thing.

I assert that the object_transactions plugin, in it’s current state, not
only doesn’t allow for sessions to be saved - it doesn’t allow for
anything to be saved to the database, since the calls that were not
being made were basically the AR::Base save calls. This implies that no
data could be persisted to the database.

Wes

SOLUTION:

It appears that this method (lines 52-56 of object_transactions.rb):

Wrap the original transation instance method with one that takes

# *objects args.
def transaction_with_object_transactions(*objects, &block)
  self.class.transaction(*objects, &block)
end

is attempting to call the transaction_with_object_transactions method
and pass it a block. But the transaction_with_object_transactions
method didn’t take a block as a parameter and so could not ultimately
pass that block to the “real” transaction method in ActiveRecord.

If you simply add the block into the parameter list for
transaction_with_object_transactions and then pass the block on to
transaction_without_object_transactions, then it seems to work fine.

What I don’t understand is why an exception isn’t thrown when the call
is made in the first place. I just tested it in irb, is the &block
parameter in a Ruby method call always optional?

Here’s a patch - Austin Z. - if you’re out there, am I right?

$ diff -u orig_object_transactions.rb object_transactions.rb
— orig_object_transactions.rb 2008-02-28 14:24:47.010000000 -0600
+++ object_transactions.rb 2008-03-19 19:11:47.055985400 -0500
@@ -59,11 +59,11 @@
# Wrap the original transaction class method with one that
manages
# an object-level transaction in addition to the database
transaction.
# If an exception is caught, the object’s state is rolled back.

  •  def transaction_with_object_transactions(*objects)
    
  •  def transaction_with_object_transactions(*objects, &block)
       objects.each { |o| o.extend Transaction::Simple }
       objects.each &:start_transaction
    
  •    transaction_without_object_transactions
    
  •    transaction_without_object_transactions(&block)
    
       objects.each &:commit_transaction
     rescue Exception => object_transaction_rollback

I don’t believe that the previous post is a necessary patch.

Wes G. wrote:

$ diff -u orig_object_transactions.rb object_transactions.rb
— orig_object_transactions.rb 2008-02-28 14:24:47.010000000 -0600
+++ object_transactions.rb 2008-03-19 19:11:47.055985400 -0500
@@ -59,11 +59,11 @@
# Wrap the original transaction class method with one that
manages
# an object-level transaction in addition to the database
transaction.
# If an exception is caught, the object’s state is rolled back.

  •  def transaction_with_object_transactions(*objects)
    
  •  def transaction_with_object_transactions(*objects, &block)
       objects.each { |o| o.extend Transaction::Simple }
       objects.each &:start_transaction
    
  •    transaction_without_object_transactions
    
  •    transaction_without_object_transactions(&block)
    
       objects.each &:commit_transaction
     rescue Exception => object_transaction_rollback
    

Updated Diff (Jeremy K. - where are you?)

It turns out that the block needs to be passed only if present - duh.

$ diff -u orig_object_transactions.rb object_transactions.rb
— orig_object_transactions.rb 2008-02-28 14:24:47.010000000 -0600
+++ object_transactions.rb 2008-03-27 16:50:39.796592000 -0500
@@ -59,11 +59,11 @@
# Wrap the original transaction class method with one that
manages
# an object-level transaction in addition to the database
transaction.
# If an exception is caught, the object’s state is rolled back.

  •  def transaction_with_object_transactions(*objects)
    
  •  def transaction_with_object_transactions(*objects, &block)
       objects.each { |o| o.extend Transaction::Simple }
       objects.each &:start_transaction
    
  •    transaction_without_object_transactions
    
  •    block ? transaction_without_object_transactions(&block) : 
    

transaction_w
ithout_object_transactions

     objects.each &:commit_transaction
   rescue Exception => object_transaction_rollback

Here is a copy of an email that I sent to Jeremy K. about this
plugin. At the end of the day, I cannot get my objects to save, so I
give up. I’m going to stop using object-level transactions, and remove
this plugin from my project.

Perhaps this email will help someone who runs into the same issue.

Wes

======================

Jeremy,

I’ve decided to give up on trying to use the object_transactions plugin,
because I can’t get all of my saves to work. The patch that I already
submitted to you handles my session saving, but I can’t get anything
else to save.

I’ll tell you what seems to be happening, but I can’t spend any more
time on this (it’s been way too many hours already).

Here’s some code from my controller - it’s purpose is to assign an
identifier to an AR::Base descendant (@current_job):

@current_job = Job.new(:contact => Contact.find(session[:user]))
@current_job.TPEMail = 1

Job.transaction(@current_job) do
  Job.find_by_sql("SELECT DISTINCT 'x' FROM #{Job.table_name} WITH 

(TABLOCK XLOCK)")
@current_job.ABSJobNumber = Job.get_next_job_number.to_s
@current_job.save!
end

What appears to happen here is that when things get down to calling the
save_with_transactions! method inside of active_record/transactions.rb
(shown below), for some reason the { transaction {
save_without_transactions! } } block is not being executed, and the call
to AR::Base.save! never happens.

def save_with_transactions! #:nodoc:
rollback_active_record_state! { transaction {
save_without_transactions! } }
end

The same behavior applies if you just call the “regular” save (sans the
!), which in turn, ultimately causes the save_with_transactions method
to be called. Again, the call to AR::Base.save never happens.

Note that if I simply call “save_without_transactions!” or
“save_without_transactions” outside of that transaction block, then they
work fine and dutifully execute save! and save, respectfully. But these
calls do not work when they are inside the block that is passed to
transaction().

What I was unable to figure out was which method is being called when
“transaction” is called when { transaction { save_without_transactions!
}} is executed. Wouldn’t that be the
“transactions_with_object_transactions” method again (which, BTW is
where this code is getting called from)? There’s some weird “too much
method chaining thing” going on here I think, but again, I don’t have
the time nor the energy to try and follow the chained logic. Perhaps
there should be some way of knowing that the save calls are already
within the context of a transaction, and thus, don’t need to put the
save_without_transaction calls inside of a block to pass to transaction?

Thanks,
Wes G.