views:

104

answers:

5

I'm working on an application that allows user to select multiple entries. Once those entries are selected, the user can choose to delete them all. When clicking on the delete button (after selected one or more entries), a modal dialog window is displayed showing the entries that the user has selected with a confirmation button.

Currently, all of the above is done using jQuery on the clientside. I've managed to get it all set so that the confirmation button (which is a link_to using the :method => :delete) has a URL of /entries/12,13,16,17 where the numbers represent the id's of the entries that the user selected. I've also tried sending the url with the format of /entries/[1,2,3,4] with no luck. I have a feeling I'm not sending a real array.

Here's my delete method, which works perfectly fine when one id is used (i.e. /entries/1), and would hopefully continue to work with one id:

def destroy
 @entry = current_user.entries.find(params[:id])
 @entry.destroy

 respond_to do |format|
  format.html { redirect_to(entries_url) }
  format.xml  { head :ok }
 end
end

When I send multiple id's (in the format of 1,2,3,4,5), only the first entry is deleted, while the rest are left untouched. Here is what my webrick log looks like after performing the action:

Processing EntriesController#destroy (for 127.0.0.1 at 2009-12-03 23:07:24) [DELETE]
  Parameters: {"action"=>"destroy", "_method"=>"delete", "id"=>"19,22", "controller"=>"entries"}
  User Load (0.3ms)   SELECT * FROM "users" WHERE ("users"."id" = '2') LIMIT 1
  User Update (0.3ms)   UPDATE "users" SET "updated_at" = '2009-12-04 04:07:24', "last_request_at" = '2009-12-04 04:07:24' WHERE "id" = 2
  Entry Load (0.2ms)   SELECT * FROM "entries" WHERE ("entries"."id" = 19 AND ("entries".user_id = 2)) 
  Tagging Load (0.4ms)   SELECT * FROM "taggings" WHERE ("taggings".taggable_id = 19 AND "taggings".taggable_type = 'Entry' AND (taggings.context = 'tags')) 
  Tag Load (0.2ms)   SELECT * FROM "tags" WHERE ("tags"."id" IN (22,23,24,29)) 
  Tagging Destroy (0.2ms)   DELETE FROM "taggings" WHERE "id" = 66
  Tagging Destroy (0.1ms)   DELETE FROM "taggings" WHERE "id" = 67
  Tagging Destroy (0.0ms)   DELETE FROM "taggings" WHERE "id" = 68
  Tagging Destroy (0.1ms)   DELETE FROM "taggings" WHERE "id" = 69
  Tagging Load (0.1ms)   SELECT * FROM "taggings" WHERE ("taggings".taggable_id = 19 AND "taggings".taggable_type = 'Entry') 
  Entry Destroy (0.1ms)   DELETE FROM "entries" WHERE "id" = 19
  Entry Load (0.5ms)   SELECT * FROM "entries" WHERE ("entries".user_id = 2) 
Redirected to http://localhost:3000/entries

As you can see, the ids are sent ("id"=>"19,22") but only the first entry is loaded. After that, its associated taggings are deleted (I'm using the acts_as_taggable_on plugin). Then, the first entry (id => 19) is destroyed, and the user is redirected back to the entries listing page.

What is should do is repeat that process (up the the redirect) for each id passed. I'm confused, because the RoRs docs state that:

id - Can be either an Integer or an Array of Integers.

What am I doing wrong? I feel like I'm so close, I can taste it! It was a huge triumph for me to get all the jQuery working, so now I know the only piece missing in the controller method handling the multiple id's.

Any help would be greatly appreciated!

A: 

You can parse out the params[:id] and create an array from it and then use it.

Jim
A: 

Quick Fix:

I personally would send the ids a different way, but I don't want to mess up your routing. So this will work, but I am only addressing the destroy part:

current_user.entries.destroy(params[:id].split(','))

This keeps it scoped to current_user but calls the destroy function directly with the array of ids.

The Better Way:

How I might suggest making it more obvious is something like this:

Instead of /entries/12,13,16,17 I would use /entries/multiple?del[]=12&del[]=13&del[]=16&del[]=17

I know that is longer, but it is more in line with how rails works naturally. Then on the controller:

def destroy
   id = params[:id]
   id = (params[:del] || []) if(id == "multiple")

   current_user.entries.destroy(id)

   respond_to do |format|
     format.html { redirect_to(entries_url) }
     format.xml  { head :ok }
   end 
end

Now your function supports the normal single destroy action as well as your new multiple destroy.

Doug Neiner
This breaks RESTful conventions and should be put in another action.
bensie
+2  A: 

There's an ActiveRecord method to do exactly what you want -- check out destroy_all. It expects a string, array, or hash of ids to delete.

I often add another action to the controller for destroy_multiple -- don't forget to add the collection route as well:

def destroy_multiple
  current_user.entries.destroy_all(:id => params[:user_ids])
  redirect_to entries_url
end
bensie
Good point about the separate action, but `destroy` would suit the needs here just fine since it accepts a single id, or an array.
Doug Neiner
+1 for great answer. Sorry to nit-pick earlier :)
Doug Neiner
A: 

Here's what a friend helped me come up with:

def destroy
 params[:id].split(',').each do |id| 
  @entry = current_user.entries.find(id)
  @entry.destroy
 end

 respond_to do |format|
  format.html { redirect_to(entries_url) }
  format.xml  { head :ok }
 end
end

I'm sure this could all be done much better and more efficiently, but for an Alpha, this'll work. Thanks for the help everyone!

shaunandrews
"inefficient" in that it does a separate call to the DB for each `find` instead of getting them at all at once.
Doug Neiner
I did try the solutions above, and none of them did the trick. I really do appreciate the time and effort you've given in helping me. I honestly had no intention of wasting anyones time. I'm sure the solutions above will come in handy in the future, but for now I need to get something working quickly. I hope you understand and thanks again.
shaunandrews
@shaunandrews Sorry for getting down your throat about trying the solutions. I am deleting my original comment so I can feel like less of a jerk :)
Doug Neiner
A: 

Did you try debugging and checking what is being returned in @entry . I think you might not be getting the entire array . You can try using the ruby-debug plugin to debug your code . On a side note , deleting using GET might not be the best option

NM