Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

AssociationCollection + Optimistic Locking + Save callback break things

#2307

Consider the following models:

class User has_many :posts end

class Comment belongs_to :user

after_save :update_user

def update_user

// do some weird stuff
user.save

end end

In case you have Optimistic locking enabled on User model, the following code fragment will fail:

@user.posts = [@post1, @post2] ... @user.save

This occured on ActiveRecord 2.1.0.

The cause is simple: when @post1 and @post2 get saved, they invoke their :update_user callbacks, but every model uses its own instance of same User model. The optimistic locking counter gets increased in the models and in the DB, but when you try to save your user model, it has an obsolete version.

An alternative fix is to add a callback: :before_add => lambda { |post, user| r.send :user=, o }, but it's kinda dirty hack I think.

Reported by mail2lf (at gmail) · March 21st, 2009 @ 11:40 AM

State: invalid
Milestone: 2.x
Assigned to: DHH DHH
Importance: none

Activity

  1. mail2lf (at gmail)
    mail2lf (at gmail)

    Crap, guys, I'm new here, sorry for breaking the code text a bit:

    class User has_many :posts end

    class Comment belongs_to :user

    after_save :update_user

    def update_user

    // do some weird stuff
    user.save
    
    

    end end

    March 21st, 2009 @ 11:43 AM

  2. mail2lf (at gmail)
  3. schorsch
    schorsch

    I have similar probs with callbacks from related records changing their parents. In my case its an invoice which is to be destroyed. Its attaxched payments have a callback which changes the status of the parent invoice in an after_destroy callback.

    when the transaction reaches the invoice its locking version has been increased and the destroy fails.

    @@@ruby class Payment

    belongs_to :invoice

    after_destroy :change_status

    def change status #change the invoice unless self.invoice.to_be_destroyed

    end

    end

    
    
    I had to enhance the stale error output in /activerecord/lib/active_record/locking/optimistic.rb to track my probs down
    
    Yes it might be a bad design and i'll try to avoid such things in the future but for now i need a way to figure out if the object referenced in the callback in inside a (destroy) transaction.
    
    I tried setting an instance var @to_be_destroyed but as i just read both objects(invoice / payment.invoice) are no equal. Maybe a thread variable could help or an explicit destroy switch in the db-table? 
    
    Or has anybody another suggestion?
    

    April 16th, 2009 @ 08:43 PM

  4. schorsch
    schorsch

    I have similar probs with callbacks from related records changing their parents. In my case its an invoice which is to be destroyed. Its attaxched payments have a callback which changes the status of the parent invoice in an after_destroy callback.

    when the transaction reaches the invoice its locking version has been increased and the destroy fails.

    class Payment

    belongs_to :invoice

    after_destroy :change_status

    def change status #change the invoice unless self.invoice.to_be_destroyed

    end

    end

    I had to enhance the stale error output in /activerecord/lib/active_record/locking/optimistic.rb to track my probs down

    Yes it might be a bad design and i'll try to avoid such things in the future but for now i need a way to figure out if the object referenced in the callback in inside a (destroy) transaction.

    I tried setting an instance var @to_be_destroyed but as i just read both objects(invoice / payment.invoice) are no equal. Maybe a thread variable could help or an explicit destroy switch in the db-table?

    Or has anybody another suggestion?

    .... to dumb for using formatting

    April 16th, 2009 @ 08:45 PM

  5. Jeremy Kemper
    Jeremy Kemper
    • Milestone changed from 2.x to 3.x

    May 4th, 2010 @ 06:48 PM

  6. Neeraj Singh
    Neeraj Singh
    • Milestone changed from 3.x to 2.x
    • Importance changed from to

    I am not able to reproduce this in rails 3.

    ActiveRecord::Schema.define(:version => 20100628071959) do
      create_table "brakes", :force => true do |t|
        t.string  "name"
        t.integer "car_id"
      end
      create_table "cars", :force => true do |t|
        t.string  "name"
        t.integer "lock_version", :default => 0
      end
    end
    
    class Brake < ActiveRecord::Base
      belongs_to :car
      after_save :update_car
      def update_car
        car.save
      end
    end
    
    class Car < ActiveRecord::Base
      has_many :brakes
    end
    
    Car.create
    Car.first.brakes.create
    Car.first.brakes.create
    Car.create
    
    Car.last.brakes = [Brake.first, Brake.last]
    

    June 28th, 2010 @ 08:32 AM

  7. Santiago Pastorino
    Santiago Pastorino

    Neeraj can you try this on 2-3-stable?

    June 29th, 2010 @ 02:28 AM

  8. Neeraj Singh
    Neeraj Singh

    works fine on latest 2-3-stable.

    June 29th, 2010 @ 02:44 AM

  9. Santiago Pastorino
    Santiago Pastorino

    mail2lf, schorsch: can you try the git versions of rails 3 and 2-3 to see if you can reproduce the problem?.
    Thanks.

    June 29th, 2010 @ 05:09 AM

  10. Neeraj Singh
    Neeraj Singh
    • State changed from new to invalid

    Closing this ticket. If it could be reproduced then it will be opened.

    July 29th, 2010 @ 09:19 PM

  11. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 10:04 PM

  12. Jeff Kreeftmeijer