22

I am wondering what is the best practice for allowing bulk edit / update in the controller. I really can't find articles or guides on the subject.

Chris Ledet
  • 11,458
  • 7
  • 39
  • 47
  • Running a bulk action, will take a while and could cause your app to timeout. The best way is to use delayed job to run the bulk action. http://railscasts.com/episodes/171-delayed-job – Joseph Le Brech Nov 01 '11 at 12:18
  • I am not interesting in running this asynchronously. I'm already calling this from Javascript using AJAX. Scaling is not an issue right now. – Chris Ledet Nov 01 '11 at 12:23

5 Answers5

41

I see that you tagged your question with REST.

To do it RESTfully, you need to think about the collection or the update itself as the resource.

Say you are working with Product objects.

You might PUT to /product_batches/[some identifier], which would call ProductBatchesController#update, but then you are stuck wondering what goes in [some identifier]. You could make ProductBatch a singular resource and then you wouldn't need an id.

Better might be to POST to /product_bulk_updates, which would call ProductBulkUpdatesController#create

class ProductBulkUpdatesController < ApplicationController

  def create
    # your magic here
    # - update_all if you are making the same change to all Products
    # - looping through the hashes in params[products] if you are passing in distinct changes to each.
  end

end

Here's another thread: Bulk Collection Manipulation through a REST (RESTful) API

Community
  • 1
  • 1
Robert Head
  • 583
  • 4
  • 9
  • 1
    I think this is great. – Ziggy Dec 20 '13 at 14:29
  • Great answer! Why did you use the word batch to name the first controller and bulk for the second? Do you think ProductBulksController is not correct? I'm not english native, and in this situations I never know which word is the most appropriate. – Mario Jul 22 '17 at 17:23
  • 2
    I politely disagree. Defining the operation as a resource is not a very RESTful approach. What you're about to do is to update a collection resource, which is already defined by `/products` (to stick to the example). So `PUT|PATCH /products` appears to be more appropriate (depending on whether it's supposed to be idempotent or not). Having resources representing an operation (like Update in your example) collides with REST methods. Besides, it suggests you can create, delete, and update an "Update". – Andreas Baumgart Oct 26 '17 at 10:10
12

I don't think there is a standard way. You may use update_attributes (example with PostsController):

def update_bulk
  @posts = Post.where(:id => params[:ids])

  # wrap in a transaction to avoid partial updates (and move to the model)
  if @posts.all? { |post| post.update_attributes(params[:post]) }
    redirect_to(posts_url)
  else
    redirect_to(:back)
  end
end

Or use update_all, but notice that neither callbacks nor validations will be called:

def update_bulk
  Post.where(:id => params[:ids]).update_all(params[:post])
  redirect_to(posts_url)
end
tokland
  • 66,169
  • 13
  • 144
  • 170
  • This is something similar to what I've been doing already. Glad I'm not the only one. – Chris Ledet Nov 01 '11 at 12:26
  • I prefer the Rails 3 ActiveRecord::Relation#update_all which constructs an actual SQL update statement to use... when its use is applicable. I only wish we had it sooner. :) – coderjoe Nov 01 '11 at 17:31
  • @coderjoe: added snippet with update_all. However, update_all does not trigger callbacks/validations. – tokland Nov 01 '11 at 17:45
6

If you're fortunate enough to be working in Rails 3 then you should make sure to check out ActiveRecord::Relation#update_all or ActiveRecord::Base#update_all in Rails 2:

It's much bettter to construct a single SQL update statement than do a full SQL round trip to update the elements.

Important Note: This is truely a bulk update using a SQL update statement. It will not instantiate any ActiveRecord objects since the update is performed purely in SQL. As such ActiveRecord callbacks and validations will not be called.

Examples from the URL above:

# Update all customers with the given attributes
Customer.update_all :wants_email => true

# Conditions from the current relation also works
Book.where('title LIKE ?', '%Rails%').update_all(:author => 'David')

Note: As far as I can tell from posts on the internet, this functionality was somewhere between buggy and broken in Rails 3.0.3, 3.0.7 - 3.0.9.

I didn't discover the feature until 3.1.0, so I can't corroborate.

coderjoe
  • 11,129
  • 2
  • 26
  • 25
  • 1
    I use Rails 3.0.x right now. #update_all seems to be ignoring the scope and just updating ALL records for that model. I think thats what you were saying though. – Chris Ledet Nov 01 '11 at 17:51
  • Yep, that's what I'm referring to. Thanks for adding the note! It's a very important one for people to see. :) – coderjoe Nov 01 '11 at 18:57
  • FWIW a similar method existed in Rails 2... I just didn't know about it. Adding a reference to my description. – coderjoe Nov 01 '11 at 19:28
3

Some minor additions to the @Robert Head's answer. In the Rails official reference there's a chapter "What is REST?" http://guides.rubyonrails.org/v2.3.11/routing.html#what-is-rest

This chapter references the REST's author Roy Fielding’s doctoral thesis. In Chapter "5.2.1.1 Resources and Resource Identifiers" of this thesis we can read:

http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_2_1_1

The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on.

So defining here a ProductCollection resource - with its 7 own RESTful actions - would nicely fit into the whole REST concept.

prograils
  • 2,248
  • 1
  • 28
  • 45
0

I have not tried this, and it is not truely model level bulk operation, but nevertheless it seems interesting.

The batch_api gem allows one to accumulate requests and send them in one go. Facebook has similar mechanism too.

lulalala
  • 17,572
  • 15
  • 110
  • 169