Topic: Frozen Hash error with before_destroy

I have a method that works just fine when run by itself, but in conjunction with "before_destroy" it craps out.

Essentially I have a acts_as_tree model and am trying to preserve the children when I delete a node on the tree. My method "save_children" turns the children's parent into the parent of the soon-to-be-deleted object. When I try to use it I keep getting a "can't modify frozen hash" error and I haven't been able to find a solution that will work. Is this even the best way to try and "bubble up" children on a tree? Should I even be using a tree? I'm at a total loss.

Saxon

Re: Frozen Hash error with before_destroy

joeunrue wrote:

I have a method that works just fine when run by itself, but in conjunction with "before_destroy" it craps out.

Essentially I have a acts_as_tree model and am trying to preserve the children when I delete a node on the tree. My method "save_children" turns the children's parent into the parent of the soon-to-be-deleted object. When I try to use it I keep getting a "can't modify frozen hash" error and I haven't been able to find a solution that will work. Is this even the best way to try and "bubble up" children on a tree? Should I even be using a tree? I'm at a total loss.

Saxon

I had that 'can't modify frozen hash' thing and it turned out to be a mistake in my code (can't remember any more details, sorry).  Will you post your method up?

As to whether you should be using acts_as_tree - i dunno, what are you trying to do?

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: Frozen Hash error with before_destroy

Thanks for the reply, Max. I've got a model "Page" that acts_as_tree that I'm using for my content management system. I'm trying to let the administrators delete a page from the tree and not lose all the children.

class Page < ActiveRecord::Base
  acts_as_tree :order => :title
  before_destroy :save_children
 
  def save_children
    for c in self.children do
      c.parent_id = self.parent_id
      c.save
    end
  end
end

There's the important parts of the Page model. When I run it in the console by itself it works just fine. However in conjuction with the destroy method it gives me the frozen hash error.

Re: Frozen Hash error with before_destroy

joeunrue wrote:

Thanks for the reply, Max. I've got a model "Page" that acts_as_tree that I'm using for my content management system. I'm trying to let the administrators delete a page from the tree and not lose all the children.

class Page < ActiveRecord::Base
  acts_as_tree :order => :title
  before_destroy :save_children
 
  def save_children
    for c in self.children do
      c.parent_id = self.parent_id
      c.save
    end
  end
end

There's the important parts of the Page model. When I run it in the console by itself it works just fine. However in conjuction with the destroy method it gives me the frozen hash error.

i wish i could remember what my problem was. sad  It was something equally random.  I think i worked around it by not using the usual AR save.  Try this:

  def save_children
    ActiveRecord::Base.connection().execute("update pages set parent_id = #{self.parent_id} where parent_id = #{self.id}")
  end

this is a total guess...

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: Frozen Hash error with before_destroy

Actually, thinking about it more, i think it was the references to self.children that were breaking it.  If my last suggestion doesn't work, try

  def save_children
    Page.find_all_by_parent_id(self.id).each do |c|
      c.update_attributes(:parent_id => self.parent_id)
    end
  end

I think the frozen hash problem has to do with accessing associations while/just before deleting: eg if a post has many comments, then 'post.comments' might cause the frozen hash error while 'Comment.find_all_by_post_id(post.id)' would be ok.

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: Frozen Hash error with before_destroy

The second solution, at least, works when I enter it by itself into the console, and neither of them throw up any frozen hash errors, but  neither of them actually work when used with the destroy method. It's not even updating the database. It just destroys both pages.

Re: Frozen Hash error with before_destroy

joeunrue wrote:

The second solution, at least, works when I enter it by itself into the console, and neither of them throw up any frozen hash errors, but  neither of them actually work when used with the destroy method. It's not even updating the database. It just destroys both pages.

Weird.  what happens if you comment out all the code in save_children and just have
  puts "hello!"

instead - do you see that output in the console when you destroy the page?

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams

Re: Frozen Hash error with before_destroy

God I hate it when I finally figure things out and feel so stupid afterwards.

I'm a total amateur when it comes to debugging RoR applications and forgot I could puts things to the console. After some experimenting I found out that everything was working exactly as it was supposed to, only in the wrong order. acts_as_tree obviously has its own before_destroy method and was taking precedence over my method so it was starting with the children and working its way back up. Since none of those children had any children to save it just deleted them like it should.

All I had to do to fix this thing was move my before_destroy method above the acts_as_tree declaration. Fixed.



Thank you so much for your help, Max.

Re: Frozen Hash error with before_destroy

joeunrue wrote:

After some experimenting I found out that everything was working exactly as it was supposed to, only in the wrong order. acts_as_tree obviously has its own before_destroy method and was taking precedence over my method so it was starting with the children and working its way back up. Since none of those children had any children to save it just deleted them like it should.

argh, that's a weird one.  Happy to help, this forum was a major lifeline for me when i was doing my first RoR project last year so it's karma payback smile

Another useful debugging method is the logger:  you can type 'logger.debug "some text"' anywhere and the result will be output to log/development.log, which you can then view with tail or a dynamic log viewer if you have one.  I have this method in config/environment.rb which automatically prefixes the location of the logger call and also prefixes it with "###" so it stands out:

#in config/environment.rb
def ldb(debug_text)
  logger.debug "### #{caller[0]}: #{debug_text}"
end

Now you can type 'ldb "some text"' anywhere.  For example, i have the following controller action:
  def edit_schools
    @schools = School.find(:all, :include => [:music_service], :order => "music_services.name, schools.name")
    #put instrumental subscriber schools ids into an array, all prefixed by s, eg ["s1", "s3", etc]
    @instrumental_school_ids = School.find_all_by_instrumental_subscriber(true).collect{|s| "s#{s.id}"}
    ldb "@chools.size = #{@schools.size}, @instrumental_school_ids.size = #{@instrument_school_ids.size}"
  end

which would output this to log/development.log when i access that controller (besides my ldb call, rails automatically outputs lots of stuff to the logger, such as controller calls with params (very useful for diagnosis!) and any sql calls to the db).  You can see my ldb result down the bottom.

Processing SchoolsController#edit_schools (for 127.0.0.1 at 2008-05-29 15:13:28) [GET]
  Session ID: b0ab94682560807c1b17968006b30530
  Parameters: {"action"=>"edit_schools", "controller"=>"admin/schools"}
  School Load Including Associations (0.001154)   SELECT `schools`.`id` AS t0_r0, `schools`.`name` AS t0_r1, `schools`.`music_service_id` AS t0_r2, `schools`.`instrumental_subscriber` AS t0_r3, `music_services`.`id` AS t1_r0, `music_services`.`created_at` AS t1_r1, `music_services`.`name` AS t1_r2, `music_services`.`host` AS t1_r3, `music_services`.`is_live` AS t1_r4, `music_services`.`html_header` AS t1_r5, `music_services`.`denies_comments` AS t1_r6, `music_services`.`message` AS t1_r7, `music_services`.`locality` AS t1_r8, `music_services`.`signup_quote` AS t1_r9, `music_services`.`signup_quote_attribution` AS t1_r10, `music_services`.`signup_email` AS t1_r11, `music_services`.`signup_phone` AS t1_r12, `music_services`.`signup_contact_name` AS t1_r13, `music_services`.`small_logo_file` AS t1_r14, `music_services`.`medium_logo_file` AS t1_r15 FROM `schools` LEFT OUTER JOIN `music_services` ON `music_services`.id = `schools`.music_service_id ORDER BY music_services.name, schools.name
  School Load (0.000321)   SELECT * FROM `schools` WHERE (`schools`.`instrumental_subscriber` = 1)
### /home/jars/rails/lesson_planner/branches/bundles/app/controllers/admin/schools_controller.rb:16:in `edit_schools': @chools.size = 251, @instrumental_school_ids.size = 247

###########################################
#If i've helped you then please recommend me at Working With Rails:
#http://www.workingwithrails.com/person/ … i-williams