Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Create method on siblings or children fails if after_save or before_save callback exists #9

Closed
mattheworiordan opened this Issue Jan 7, 2010 · 6 comments

Comments

Projects
None yet
2 participants

Hi there

I have run into some strange errors when trying to access the parent within a before_save or after_save callback, and have discovered that strangely it happens when using the create method on children or siblings, but not when using the build method on the same objects.

Here is some simple code to replicate this problem:

Simplified migration code

class CreatePlaces < ActiveRecord::Migration
  def self.up
    create_table :places do |t|
      t.string :name
      t.string :ancestry
    end
    add_index :places, :ancestry
  end

  def self.down
    remove_index :places, :ancestry
    remove_column :places, :ancestry
    drop_table :places
  end
end

Ruby code to replicate this issues

This code has been run with Ruby 1.8.7 and Rails 2.3.4, under the script/console

class Place < ActiveRecord::Base
  validates_presence_of :name
  acts_as_tree

  def after_save() 
     puts "#{parent.id}" if parent 
  end 
end

# Create an orphaned root level parent - works fine
parent = Place.new(:name=>"Parent"); parent.save

# Create a child node for that parent - works fine
child = Place.new(:name=>"Child",:parent=>parent); child.save

# Create a child using the children instance var of parent, using build - works fine
child_from_children = parent.children.build(:name=>"Child created from children instance method"); child_from_children.save

# Now use the dodgy create method of children and it FAILS
child_from_children = parent.children.create(:name=>"Child created from children instance method")

I have tried using before_save or after_save and this problem is exactly the same. However, if I remove the before_save or after_save callbacks, then the create method on children works.

The specific error I receive is:
ActiveRecord::RecordNotFound: Couldn't find Place with ID=17123
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1586:in find_one' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:1569:infind_from_ids'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:616:in find' from /Library/Ruby/Gems/1.8/gems/ancestry-1.1.4/lib/ancestry/acts_as_tree.rb:329:inparent'
from (irb):5:in after_save' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:347:insend'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:347:in callback' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:251:increate_or_update'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2538:in save_without_validation' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/validations.rb:1078:insave_without_dirty'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/dirty.rb:79:in save_without_transactions' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:229:insend'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:229:in with_transaction_returning_status' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:136:intransaction'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:182:in transaction' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:228:inwith_transaction_returning_status'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:196:in save' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:208:inrollback_active_record_state!'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:196:in save' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:723:increate'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:181:in send' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:181:inmethod_missing'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2143:in with_scope' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:113:insend'
from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:113:in with_scope' from /Users/matthew/.gem/ruby/1.8/gems/activerecord-2.3.4/lib/active_record/named_scope.rb:174:inmethod_missing'

Owner

stefankroes commented Jan 7, 2010

After some investigation, I've concluded that this is definitely a bug in ActiveRecord. When finding the parent, the Place class is scoped where it shouldn't be. I'm gonna investigate some more but see little hope for a solution. I recommend you use the method that does work for the time being.

Kind regards,

Stefan Kroes

Owner

stefankroes commented Jan 7, 2010

Ok, turns out the scoping of the Place class done by AR when calling create through the children scope is propagated to your before_save method because it is done in a stateful way. This is definitely erroneous behavior from AR and it isn't something I can get around with a patch to Ancestry. If you really want this resolved you should take it up with the Rails team. The behavior will occur in callbacks whenever you create an object through a scope. I suggest you try replicating the bug without using ancestry before passing the bug report on to the rails core team.

Kind regards,

Stefan Kroes

Hi Stefan

Ok, I will try and recreate and send to the ActiveRecord Rails team. Just to be clear then, are you saying that the scope which is passed into the before_save/after_save method is incorrect somehow?

Anyway, I'll play around and see if I can replicate with a very simple model and no plugins.

Thanks again for looking into it.

Matt

Owner

stefankroes commented Jan 8, 2010

The key to recreating the error is creating a association the same way ancestry does. The you call AncestryModel.children the AncestryModel class is explicitly scoped with a condition to only return children. Somehow this scope is also applied when querying for the parent in your callback method. Since you then look for the parent in its own children you won't find anything.

Hi Stefan

Not sure if you want comment on the Lighthouse Rails Ticket thread at about this "bug".

This issue was closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment