views:

284

answers:

2

I have frequently run into the situation where I want to update many records at once - like GMail does with setting many messages "read" or "unread".

Rails encourages this with the 'update' method on an ActiveRecord class - Comment.update(keys, values)

Example - http://snippets.dzone.com/posts/show/7495

This is great functionality, but hard to map to a restful route. In a sense, I'd like to see a :put action on a collection. In routes, we might add something like

map.resources :comments, :collection => { :update_many => :put }

And then in the form, you'd do this...

<% form_for @comments do |f| %>
  ...

This doesn't work on many levels. If you do this: :collection => { :update_many => :put }, rails will submit a post to the index action (CommentsController#index), I want it to go to the 'update_many' action. Instead, you can do a :collection => { :update_many => :post }. This will at least go to the correct action in the controller.

And, instead of <% form for @comments ... you have to do the following:

<% form_for :comments, :url => { :controller => :comments, :action => :update_many } do |f| %>

It will work OK this way

Still not perfect - feels a little like we're not doing it the 'Rails way'. It also seems like :post, and :delete would also make sense on a collection controller.

I'm posting here to see if there's anything I missed on setting this up. Any other thoughts on how to restfully do a collection level :post, :put, :delete?

+1  A: 

I often add collection-based update_multiple and destroy_multiple actions to an otherwise RESTful controller.

Check out this Railscast on Updating Through Checkboxes. It should give you a good idea how to approach it, come back and add to this question if you run into troubles!

bensie
Thanks for this - it looks like using the 'form_for' does not work with the :put action. When I switched to 'form_tag' (as Ryan B. demonstrates), it is now going to the right action and working correctly. I didn't try to figure out why.One small thing - I like using the 'fields_for' block to generate the input fields for each record ( f.text_field ). Ryan does not use fields_for in his screencast (maybe because it predates the helper) and uses the _tag methods. 'fields_for' is OK to use here.
Swards
+1  A: 

I've run into a few situations like you describe. The first couple of times I've implemented form almost identical to the one you suggest.

About the third time I hit this problem I realized that every item I'm updating has a common belongs_to relationship with something else. Usually a user. That's exactly the epiphany you need to make sense of this RESTfully. It will also help you clean clean up the form/controller.

Don't think of it as updating a bunch of messages, think of it as updating one user.

Here's some example code I've used in the past to highlight the difference. Assuming that we we want bulk operations on messages that belong to the current_user...

As of rails 2.3 we can add

 accepts_nested_attributes_for :messages

to the user model. Ensure messages_attributes is part of attr_accessible, or is not attr_protected.

Then create the route:

 map.resources :users, :member => {:bulk_message_update, :method => :put}

Then add the action to the controller. With AJAX capabilities ;)

 def bulk_message_update
   @user = User.find(params[:id])
   @user.update_attributes(params[:user])
   if @user.save 
     respond_to do |format|
       format.html {redirect}
       format.js {render :update do |page|
          ...
       } 
     end       
   else
     ....
 end

Then your form will look like this:

<% form_for current_user, bulk_message_update_user_url(current_user), 
     :html => {:method => :put} do |f| %>
  <% f.fields_for :messages do |message| %>
    form for each message
  <% end %>
  <%= sumbit_tag %>
<% end %>
EmFi