I have a Category model that uses acts_as_tree. So the subcategories of a category are accessible via Category#children. The categories table has the requisite parent_id column. It all works quite nicely. I''m trying to prevent the deletion of a category if it has subcategories. So I thought I''d specify a before_destroy callback and return false if the Category instance had "children.length > 1". Well it wasn''t working -- the parent isn''t getting destroyed, but the sub is. When debugging the code when attempting to delete a parent category, I noticed that my before_destroy callback was being called TWICE -- first for the subcategory of the parent and THEN for the parent. I''m hypothesizing that acts_as_tree sets up the dependency so that children are automatically destroyed when their parents are destroyed. That probably explains why before_destroy is being called twice and in that order (sub then parent). Regardless, I''m returning true from before_destroy when invoked on the sub (since it has no subcategories) and then returning false from before_destroy when invoked on its parent. Unfortunately, although I''m halting the destruction of the parent, I''m not halting that of the sub. I thought that, perhaps, I should raise and Exception rather than return false from before_destroy, but that didn''t change things. Does anyone know how to properly handle this scenario? Many thanks... --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Rob Biedenharn
2008-Jan-16 23:30 UTC
Re: How to prevent a parent with children from being deleted
On Jan 16, 2008, at 5:33 PM, Decker wrote:> > I have a Category model that uses acts_as_tree. So the subcategories > of a category are accessible via Category#children. The categories > table has the requisite parent_id column. It all works quite nicely. > > I''m trying to prevent the deletion of a category if it has > subcategories. So I thought I''d specify a before_destroy callback and > return false if the Category instance had "children.length > 1". Well > it wasn''t working -- the parent isn''t getting destroyed, but the sub > is. > > When debugging the code when attempting to delete a parent category, I > noticed that my before_destroy callback was being called TWICE -- > first for the subcategory of the parent and THEN for the parent. I''m > hypothesizing that acts_as_tree sets up the dependency so that > children are automatically destroyed when their parents are > destroyed. That probably explains why before_destroy is being called > twice and in that order (sub then parent). > > Regardless, I''m returning true from before_destroy when invoked on the > sub (since it has no subcategories) and then returning false from > before_destroy when invoked on its parent. Unfortunately, although > I''m halting the destruction of the parent, I''m not halting that of the > sub. > > I thought that, perhaps, I should raise and Exception rather than > return false from before_destroy, but that didn''t change things. Does > anyone know how to properly handle this scenario? > > Many thanks...Since save and destroy are automatically wrapped in a transaction, perhaps something like this: http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#M001115 class Category < ActiveRecord::Base before_destroy :ensure_no_children acts_as_tree def ensure_no_children unless self.children.empty? self.errors.add_to_base "Can''t destroy a category having subcategories" raise ActiveRecord::Rollback end end end If you raise any other kind of exception, you have to rescue it yourself begin @category.destory rescue MyException => e flash[:error] = e.message redirect_to "_somewhere_safe_" end -Rob Rob Biedenharn http://agileconsultingllc.com Rob-xa9cJyRlE0mWcWVYNo9pwxS2lgjeYSpx@public.gmane.org --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Robert Walker
2008-Jan-17 01:42 UTC
Re: How to prevent a parent with children from being deleted
An alternative that I have been experimenting with and seems to work: class Person < ActiveRecord::Base def before_destroy # add whatever logic you want here... errors.add_to_base("Woops! Can''t destroy that person.") false end end class PeopleController < ApplicationController ... ... # DELETE /people/1 # DELETE /people/1.xml def destroy @person = Person.find(params[:id]) if @person.destroy respond_to do |format| format.html { redirect_to(people_url) } format.xml { head :ok } end else respond_to do |format| format.html { render :action => ''edit'' } format.xml { head :bad_request } end end end end This is similar to Rob''s solution, but does not involve catching exceptions. Also the responsibility of error display is back with the views rather than the controller. Note that returning false from before_destroy cancels the call backs before the record is destroyed preventing the deletion of the person. As far as I can tell this seems to work nicely, but I would love some feedback in case I''ve overlooked something. On Jan 16, 6:30 pm, Rob Biedenharn <R...-xa9cJyRlE0mWcWVYNo9pwxS2lgjeYSpx@public.gmane.org> wrote:> On Jan 16, 2008, at 5:33 PM, Decker wrote: > > > > > > > I have a Category model that uses acts_as_tree. So the subcategories > > of a category are accessible via Category#children. The categories > > table has the requisite parent_id column. It all works quite nicely. > > > I''m trying to prevent the deletion of a category if it has > > subcategories. So I thought I''d specify a before_destroy callback and > > return false if the Category instance had "children.length > 1". Well > > it wasn''t working -- the parent isn''t getting destroyed, but the sub > > is. > > > When debugging the code when attempting to delete a parent category, I > > noticed that my before_destroy callback was being called TWICE -- > > first for the subcategory of the parent and THEN for the parent. I''m > > hypothesizing that acts_as_tree sets up the dependency so that > > children are automatically destroyed when their parents are > > destroyed. That probably explains why before_destroy is being called > > twice and in that order (sub then parent). > > > Regardless, I''m returning true from before_destroy when invoked on the > > sub (since it has no subcategories) and then returning false from > > before_destroy when invoked on its parent. Unfortunately, although > > I''m halting the destruction of the parent, I''m not halting that of the > > sub. > > > I thought that, perhaps, I should raise and Exception rather than > > return false from before_destroy, but that didn''t change things. Does > > anyone know how to properly handle this scenario? > > > Many thanks... > > Since save and destroy are automatically wrapped in a transaction, > perhaps something like this:http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMet... > > class Category < ActiveRecord::Base > before_destroy :ensure_no_children > acts_as_tree > > def ensure_no_children > unless self.children.empty? > self.errors.add_to_base "Can''t destroy a category having > subcategories" > raise ActiveRecord::Rollback > end > end > end > > If you raise any other kind of exception, you have to rescue it yourself > > begin > -vwTnWBwdg3HStIrcTF2+PQ@public.gmane.org > rescue MyException => e > flash[:error] = e.message > redirect_to "_somewhere_safe_" > end > > -Rob > > Rob Biedenharn http://agileconsultingllc.com > R...-xa9cJyRlE0mWcWVYNo9pwxS2lgjeYSpx@public.gmane.org--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Frederick Cheung
2008-Jan-17 01:54 UTC
Re: How to prevent a parent with children from being deleted
On 16 Jan 2008, at 22:33, Decker wrote:> > I have a Category model that uses acts_as_tree. So the subcategories > of a category are accessible via Category#children. The categories > table has the requisite parent_id column. It all works quite nicely. > > I''m trying to prevent the deletion of a category if it has > subcategories. So I thought I''d specify a before_destroy callback and > return false if the Category instance had "children.length > 1". Well > it wasn''t working -- the parent isn''t getting destroyed, but the sub > is. >Why not just use a foreign key constraint (and then you don''t have to worry about category.children having changed behind your back) Fred> When debugging the code when attempting to delete a parent category, I > noticed that my before_destroy callback was being called TWICE -- > first for the subcategory of the parent and THEN for the parent. I''m > hypothesizing that acts_as_tree sets up the dependency so that > children are automatically destroyed when their parents are > destroyed. That probably explains why before_destroy is being called > twice and in that order (sub then parent). > > Regardless, I''m returning true from before_destroy when invoked on the > sub (since it has no subcategories) and then returning false from > before_destroy when invoked on its parent. Unfortunately, although > I''m halting the destruction of the parent, I''m not halting that of the > sub. > > I thought that, perhaps, I should raise and Exception rather than > return false from before_destroy, but that didn''t change things. Does > anyone know how to properly handle this scenario? > > Many thanks... > > >--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Robert Walker
2008-Jan-17 01:54 UTC
Re: How to prevent a parent with children from being deleted
Sorry, ignore the line:> format.xml { head :bad_request }from my previous post. I''m sure that''s not really correct. It should return something more appropriate for the XML format when errors occur. On Jan 16, 8:42 pm, Robert Walker <rwalker...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> An alternative that I have been experimenting with and seems to work: > > class Person < ActiveRecord::Base > def before_destroy > # add whatever logic you want here... > errors.add_to_base("Woops! Can''t destroy that person.") > false > end > end > > class PeopleController < ApplicationController > ... > ... > > # DELETE /people/1 > # DELETE /people/1.xml > def destroy > @person = Person.find(params[:id]) > > if @person.destroy > respond_to do |format| > format.html { redirect_to(people_url) } > format.xml { head :ok } > end > else > respond_to do |format| > format.html { render :action => ''edit'' } > format.xml { head :bad_request } > end > end > end > end > > This is similar to Rob''s solution, but does not involve catching > exceptions. Also the responsibility of error display is back with the > views rather than the controller. > > Note that returning false from before_destroy cancels the call backs > before the record is destroyed preventing the deletion of the person. > As far as I can tell this seems to work nicely, but I would love some > feedback in case I''ve overlooked something. > > On Jan 16, 6:30 pm, Rob Biedenharn <R...-xa9cJyRlE0mWcWVYNo9pwxS2lgjeYSpx@public.gmane.org> > wrote: > > > On Jan 16, 2008, at 5:33 PM, Decker wrote: > > > > I have a Category model that uses acts_as_tree. So the subcategories > > > of a category are accessible via Category#children. The categories > > > table has the requisite parent_id column. It all works quite nicely. > > > > I''m trying to prevent the deletion of a category if it has > > > subcategories. So I thought I''d specify a before_destroy callback and > > > return false if the Category instance had "children.length > 1". Well > > > it wasn''t working -- the parent isn''t getting destroyed, but the sub > > > is. > > > > When debugging the code when attempting to delete a parent category, I > > > noticed that my before_destroy callback was being called TWICE -- > > > first for the subcategory of the parent and THEN for the parent. I''m > > > hypothesizing that acts_as_tree sets up the dependency so that > > > children are automatically destroyed when their parents are > > > destroyed. That probably explains why before_destroy is being called > > > twice and in that order (sub then parent). > > > > Regardless, I''m returning true from before_destroy when invoked on the > > > sub (since it has no subcategories) and then returning false from > > > before_destroy when invoked on its parent. Unfortunately, although > > > I''m halting the destruction of the parent, I''m not halting that of the > > > sub. > > > > I thought that, perhaps, I should raise and Exception rather than > > > return false from before_destroy, but that didn''t change things. Does > > > anyone know how to properly handle this scenario? > > > > Many thanks... > > > Since save and destroy are automatically wrapped in a transaction, > > perhaps something like this:http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMet... > > > class Category < ActiveRecord::Base > > before_destroy :ensure_no_children > > acts_as_tree > > > def ensure_no_children > > unless self.children.empty? > > self.errors.add_to_base "Can''t destroy a category having > > subcategories" > > raise ActiveRecord::Rollback > > end > > end > > end > > > If you raise any other kind of exception, you have to rescue it yourself > > > begin > > -vwTnWBwdg3HStIrcTF2+PQ@public.gmane.org > > rescue MyException => e > > flash[:error] = e.message > > redirect_to "_somewhere_safe_" > > end > > > -Rob > > > Rob Biedenharn http://agileconsultingllc.com > > R...-xa9cJyRlE0mWcWVYNo9pwxS2lgjeYSpx@public.gmane.org--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Thanks to all of you. I think I took a hybrid approach. I am throwing an ActiveRecord::Rollback exception -- which does prevent both records from being destroyed. This exception must be swallowed up by the infrastructure; it is not rethrown to my controller (just as Rob implied). So the next task was to detect if the destroy succeeded. At first I examined the errors collection on the ActiveRecord object (that worked), but then Roberts snippet clued me in to the fact that destroy returns a boolean status value (is that in the docs anywhere)? Incidentally, I agree that the database should also have the foreign key constraints. I''d like to put them in a db migration, but I can''t seem to find the syntax... --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Just when I thought I had it licked... The technique described above works fine in development. But I have one unit test failing (it''s deleting the sub but not the parent). I wonder if the handling of the ActiveRecord::Rollback exception is different between the development environment and the environment that is established when running unit tests? On Jan 16, 11:12 pm, Decker <hpins...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Thanks to all of you. I think I took a hybrid approach. I am > throwing an ActiveRecord::Rollback exception -- which does prevent > both records from being destroyed. This exception must be swallowed > up by the infrastructure; it is not rethrown to my controller (just as > Rob implied). So the next task was to detect if the destroy > succeeded. At first I examined the errors collection on the > ActiveRecord object (that worked), but then Roberts snippet clued me > in to the fact that destroy returns a boolean status value (is that in > the docs anywhere)? > > Incidentally, I agree that the database should also have the foreign > key constraints. I''d like to put them in a db migration, but I can''t > seem to find the syntax...--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
I think a more straight forward approach would be to override the destroy method with something like this: def destroy super if children.blank? end As for the rollback code that works in development and breaks in test. The rails test framework uses transactions and rollbacks to reset the database after every test. This is likely changing the behavior of your code while running in the test environment. Aaron On Jan 16, 9:41 pm, Decker <hpins...-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:> Just when I thought I had it licked... The technique described above > works fine in development. But I have one unit test failing (it''s > deleting the sub but not the parent). I wonder if the handling of the > ActiveRecord::Rollback exception is different between the development > environment and the environment that is established when running unit > tests?--~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe@googlegroups.com For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Keynan Pratt
2008-Jan-17 07:03 UTC
Re: How to prevent a parent with children from being deleted
there are three simple solutions that while simple are not necessarily pleasant. the best i have come up with is find the acts_as_tree plugin in vendor (or rails if your still in v1.x) and alter the method so you can specify :dependent => :destroy on invocation rather than it hard coding. the other two require either not using acts_as_tree but specifying children and parent relations should you not be using full functionality the third option is my favorite but was refuse from the core a few months ago which is tho add a before/after_destroy_dependents hook into active recored. In this case I would suggest you try the first solution and wish you the best of luck. -K -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Frederick Cheung
2008-Jan-17 11:03 UTC
Re: How to prevent a parent with children from being deleted
On 17 Jan 2008, at 04:12, Decker wrote:> > > Incidentally, I agree that the database should also have the foreign > key constraints. I''d like to put them in a db migration, but I can''t > seem to find the syntax...There''s no magic rails support for this (i seem to recall there is a plugin that adds this, but I don''t recall the name). You can of course execute arbitrary sql in your migrations, so just write whatever your database requires, eg for mysql you could say execute "ALTER TABLE some_table ADD CONSTRAINT fk_some_table_other_tables FOREIGN KEY (other_table_id) REFERENCES other_tables(id)" Fred --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Xavier Noria
2008-Jan-17 11:09 UTC
Re: How to prevent a parent with children from being deleted
On Jan 17, 2008, at 12:03 PM, Frederick Cheung wrote:> There''s no magic rails support for this (i seem to recall there is a > plugin that adds this, but I don''t recall the name).Yes, that''s foreign_key_migrations: http://www.redhillonrails.org/#foreign_key_migrations We[*] use it in all projects whose database supports FKs. -- fxn [*] ASPgems --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
> > the third option is my favorite but was refuse from the core a few > months ago which is tho add a before/after_destroy_dependents hook into > active recored.It seems odd to me that it has not been included in the :dependent options, why not something like :dependent=>''constrained'' which would prohibit deletion if a dependent exists. Tonypm --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Keynan Pratt
2008-Jan-18 01:51 UTC
Re: How to prevent a parent with children from being deleted
support ticket http://dev.rubyonrails.org/ticket/10843 with a +1 in the comments -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
astaroth astaroth
2008-May-22 18:25 UTC
Re: How to prevent a parent with children from being deleted
I had a similar problem. I wanted children categories to become deleted record''s siblings, then destroy it. A sensible solution is to add a method to your model to use instead of destroy: def soft_destroy children.each {|ch| ch.update_attribute(:parent_id, parent ? parent.id : nil) } reload destroy end I hope you find it useful... -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---