This project is archived and is in readonly mode.

#2307 ✓invalid
mail2lf (at gmail)

AssociationCollection + Optimistic Locking + Save callback break things

Reported by mail2lf (at gmail) | March 21st, 2009 @ 11:40 AM | in 2.x

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.

Comments and changes to this ticket

  • mail2lf (at gmail)

    mail2lf (at gmail) March 21st, 2009 @ 11:43 AM

    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

  • mail2lf (at gmail)

    mail2lf (at gmail) March 24th, 2009 @ 05:00 PM

    • Assigned user set to “DHH”
  • schorsch

    schorsch April 16th, 2009 @ 08:43 PM

    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?
    
  • schorsch

    schorsch April 16th, 2009 @ 08:45 PM

    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

  • Jeremy Kemper

    Jeremy Kemper May 4th, 2010 @ 06:48 PM

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

    Neeraj Singh June 28th, 2010 @ 08:32 AM

    • 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]
    
  • Santiago Pastorino

    Santiago Pastorino June 29th, 2010 @ 02:28 AM

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

  • Neeraj Singh

    Neeraj Singh June 29th, 2010 @ 02:44 AM

    works fine on latest 2-3-stable.

  • Santiago Pastorino

    Santiago Pastorino June 29th, 2010 @ 05:09 AM

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

  • Neeraj Singh

    Neeraj Singh July 29th, 2010 @ 09:19 PM

    • State changed from “new” to “invalid”

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

  • Ryan Bigg

    Ryan Bigg October 9th, 2010 @ 10:04 PM

    • Tag cleared.

    Automatic cleanup of spam.

  • Jeff Kreeftmeijer
  • csnk

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

<h2 style="font-size: 14px">Tickets have moved to Github</h2>

The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>

Pages