views:

57

answers:

3

I have a Parent model which has Children. If all the Children of a certain Parent are deleted, I'd like to automatically delete the Parent as well.

In a non-AJAX scenario, in the ChildrenController I would do:

@parent = @child.parent
@child.destroy
if @parent.children.empty?
    redirect_to :action => :destroy, 
                :controller => :parents, 
                :id => @parent.id
end

But this is impossible when the request is XHR. The redirect causes a GET request.

The only way I can think of to do this with AJAX is add logic to the response RJS, causing it to create a link_to_remote element, "click" it, and then remove it. It seems ugly. Is there a better way?

Clarification

When I use the term redirect, I do not mean an HTTP redirect. What I mean is that instead of returning the RJS associated with destroying Child, I want to perform destroy on Parent and return the RJS associated with destroying Parent.

A: 

I would guess you could set the window.location.href in your rjs, something like

<% if @parent.children.empty? %>
  window.location.href='<%= url_for :action => :destroy, 
                                   :controller => :parents, 
                                   :id => @parent.id %>'
<% else %>
  .. do your normall stuff here ..
<% end %>

assuming you render javascript. Not sure if it is completely correct, but hope you get the idea.

[EDIT: added controller code]

TO make it clearer, your controller would look as follows

@parent = @child.parent
@child.destroy
if @parent.children.empty?
   render :redirect
end
nathanvda
I want the parent destroy action to be called via XHR. This solution is a URL redirect.
shmichael
You can't do a redirect in a XHR controller action, but you can render a RJS which will do the redirect for you. I thought that was your question? Your controller action needs to do the delete and if there are no more items: redirect. No?
nathanvda
Perhaps I misused the term redirect.I would like the response RJS to be the one associated with destroying `Parent`. i.e. from `ChildrenController`'s destroy I want to move to `ParentsController`'s destroy, skipping `ChildrenController`'s response altogether.So, no HTTP redirect should be issued in any stage of the process.
shmichael
Instead of the render, then just call `ParentsController.new.destroy`. You might need to set up your `params` correctly. But that should work. Not talking about pretty though. Another alternative would be to extract the similar functionality into a seperate function, place it in a module, and include that.
nathanvda
+1  A: 

I would listen to what nathanvda says, but you can do it via ruby syntax (and you don't need erb scriptlets in rjs):

if @parent.children.empty?
  page.redirect_to(url_for :action => :destroy, 
                           :controller => :parents, 
                           :id => @parent.id)
else
  .. do your normall stuff here ..
end
neutrino
See my comment to @nathanvda.
shmichael
A: 

A better approach to destroying the parent through a redirect is doing it in an after_hook. Not only you don't have to tell your user's browser to make another request, you also don't need to keep track of everywhere in the code where you delete children so you don't end up with hanging parents.

class Parent < ActiveRecord::Base

  # also worth getting the dependent destroy, so you don't have hanging children
  has_many :chilren, :dependent => :destroy 

end

class Child < ActiveRecord::Base

  after_destroy { parent.destroy  if parent.children.empty? }

end

Then you can just handle however you prefer what to show the user when that happens, like redirecting the user to '/parents'.

Marcos Toledo
This would call the Parent *model* `destroy` action. I want the ParentsController `destroy` so that the returned RJS will be that of a deleted Parent instead of a deleted Child. The reason is I want to remove the whole Parent div from the page. This will include the Child in it, so I don't need the Child RJS to be called.
shmichael
Yea, but 'business logic' shouldn't belong in your controller, it should belong in your model. If there is no use for a 'parent without a child' in your app, then it should be a hook in your model, to prevent you to have to implement this in every part of the app where a child can be deleted. Your controller can then just render an rjs that checks the existence of a parent and returns the removal of the div accordingly.
Marcos Toledo