Updating multiple RESTful records in one request

I used to be able to destroy multiple records in my restful controller by using the following:

in my view:

  <%= form_tag product_path, :method => :delete %>     <% for product in @products %>       <td><%= product.title %></td>       <td><%= check_box_tag ('id', product.id) %></td>     </tr>     <% end %> <%= end_form_tag %>

and in my products_controller:

  def destroy     begin       # params[:id] may either be a single id, or an array of id's       @products = Product.find(params[:id])     rescue       logger.warn("ERROR: #{$!}")     end

    if @products and Product.destroy(@products)       flash[:notice] = "Product successfully deleted"     else       flash[:warning] = "An error was encountered while attempting to delete the product(s)"     end   end

however, after upgrading to the most recent edge rails (6207 as of yesterday), this no longer works.. I get an error

"product_url failed to generate from {:controller=>"products", :action=>"show"} - you may have ambiguous routes, or you may need to supply additional parameters for this route."

I solved this by using the following:

in routes.rb:

  map.resources :products, :collection => {     :delete => :delete   }

in my view:

  <%= form_tag delete_products_path, :method => :delete %>        blah blah...   <%= end_form_tag %>

and then I use the same code in my controller, however, I alias the delete method to destroy by using the following in my controller:

  alias delete destroy

so am I bastardizing RESTful standards? I'm sure I am, but I can't think of a better way around this. If anyone else has any ideas, please let us know! Also, why was I previously able to do this using the standard restful routes (ie multiple id's could be passed to an action), but this no longer seems to be the case?

Mike

I'd be interested in seeing how DHH intends to implement this in ActiveResource ... if it's still being developed. I checked today and ActiveResource hasn't been touched in a very, very long time.

It seems logical to expect methods to be able to act upon more than one object ... in a batch. I cringe at the thought of iterating over every object in a collection just to do a batch update or create. I'd love to be able to upload an XML file to a RESTful URL and have it know whether it's a single object or a collection of objects. It seems to be that it would be as simple as determining whether the root entity was singular or plural:

If I want to work with a single entity, I'd hit http://www.myapplication.com/people/ with... <person>   <id>...</id>   ... </person>

and if I want to work with more than one person, I'd hit the same URL with... <people>   <person>     <id>...</id>     ...   </person>   <person>     <id>...</id>     ...   </person> </people>

I'm trying to let a couple of applications talk together using REST interfaces but, without this kind of functionality, it's proving to be very difficult.

Am I thinking about this incorrectly or does something like this seem possible?

Thanks, Chris

I’d be interested in seeing how DHH intends to implement this in ActiveResource … if it’s still being developed. I checked today and ActiveResource hasn’t been touched in a very, very long time.

That’s odd; where’d you check? There’s regular, though not high-volume, activity.

It seems logical to expect methods to be able to act upon more than one object … in a batch. I cringe at the thought of iterating over every object in a collection just to do a batch update or create. I’d

love to be able to upload an XML file to a RESTful URL and have it know whether it’s a single object or a collection of objects. It seems to be that it would be as simple as determining whether the root entity was singular or plural:

There’s nothing stopping you, but it’s not baked in yet. XML params are just read in as nested hashes, after all.

Try it, see what works best, extract the solution and post a patch!

jeremy

Yeah, I forgot to mention that I'm gonna start working on this and see what I can come up with. I'm just a little surprised that it wasn't baked-in from the beginning. :wink:

Great! It’s not baked in because nobody ordered that entree yet.

“if it’s still being developed. I checked today and ActiveResource hasn’t been touched in a very, very long time”

Still curious where you checked :wink:

jeremy

I don't know what I was looking at but, since checking the official Trac (thought I was there before), I'm finding myself wrong on this one. Thanks for keeping me honest! :wink:

I forgot to mention...

After searching around a bit, I stumbled upon some code that would probably be a great base for what we've been talking about. It straps a from_xml method onto ActiveRecord::Base and has the logic to determine whether the root is the singular or plural version of the current class. I'm not sure how resilient the code is when it comes to nested associations and such but it's worth a look.

The code is here: http://riftor.g615.co.uk/content.php?view=50&type=1

-Chris

Hi Chris,

I'd be interested in seeing how DHH intends to implement this in ActiveResource ... if it's still being developed. I checked today and ActiveResource hasn't been touched in a very, very long time.

It seems logical to expect methods to be able to act upon more than one object ... in a batch. I cringe at the thought of iterating over every object in a collection just to do a batch update or create. I'd love to be able to upload an XML file to a RESTful URL and have it know whether it's a single object or a collection of objects. It seems to be that it would be as simple as determining whether the root entity was singular or plural:

This is pretty much the exact opposite direction that I instinctively
head in.

I cringe at the thought of littering my controller methods with case/ if statements to cover the scenarios of getting "no id", "one id",
"many ids". It seems like a step backwards.

In my projects I completely sidestep the issues you guys have been
having (i.e. calling POST, PUT, DELETE for a batch) by treating the
"subset" that I want to operate on as something at a different
RESTful URL.

So rather than overloading the idea expressed by /comments with both
"the collection of comments" and "a batch-subset of comments" I would
have both /comments and /comments/batch - they are, after all, two
different things.

Admittedly, I haven't looked at ActiveResource in a while and I
haven't (yet) had to build anything beyond a toy ARes client.
However, I don't see *anything* missing on the server side when doing
this in-browser.

Regards, Trevor

Trevor,

I can see the logic from both sides. Conditionals do suck and, to avoid them and really encapsulate the behavior, I think the code would need to be within ActiveRecord itself (maybe not core ::Base but perhaps in a plugin that modifies AR). I've not had the time to study all of the pertinent files and process flows but I think it can be done without changing controller code too much.

Before I go any further, I may be totally wrong about how Rails is doing some things behind the scenes because I've not had the time to thoroughly study the behind-the-scenes code. So, if I get something wrong, just let me know. :wink:

Given a RESTful URL, it seems logical to be able to POST/PUT collections just like we do singular items. Currently, if an XML file reaches an action (e.g. create or update), it's processed and reconstituted as the 'params' hash (see: param_parsers in base.rb and parsing logic within cgi_methods.rb -- both part of ActionController). The XML data is then accessible via params[:xml_root][:some_key] (note: prior to Rails 1.1, the XML root was not part of the params hash). Anyways, this is great because, whether our params hash was built from form parameters or a posted XML file, our controllers can act on the params hash the same exact way (for singular items):

(code taken from the AWDwR book) # POST /articles # POST /articles.xml def create   @article = Article.new(params[:article])   ... end

So, we're just passing the params hash to the model for processing. The conditional logic to support collections, at this point (not taking ActiveRecord into account), would seem to be fairly easy:

(excuse the pseudo-code, it's late and I don't want to go api hunting) def create   @articles = ( if params[:articles] exists, we process as a collection of articles ... if not, we process as a singular article )   ... end

The conditional code may not even need to be in the controller. Perhaps the heavy-lifting would be offloaded to the appropriate class methods (e.g. Article.new()) in ActiveRecord.

I don't know ... I may be thinking too simply but it seems like this would be possible. From there, the model would be responsible for determining whether it was passed a singular item or a collection.

Anyways, I do want to look at all of the Rails code that's involved in this stuff and then try to take a whack at it. I'm too tired to think right now, so I'm going to cut this shorter than I had expected (sorry).

-Chris

Hey Chris,

comments inline:

<snip>

Given a RESTful URL, it seems logical to be able to POST/PUT collections just like we do singular items. Currently, if an XML file reaches an action (e.g. create or update), it's processed and reconstituted as the 'params' hash (see: param_parsers in base.rb and parsing logic within cgi_methods.rb -- both part of ActionController). The XML data is then accessible via params[:xml_root][:some_key] (note: prior to Rails 1.1, the XML root was not part of the params hash). Anyways, this is great because, whether our params hash was built from form parameters or a posted XML file, our controllers can act on the params hash the same exact way (for singular items):

I agree, reconstitution as a params hash is desirable. Careful about
that "(for singular items)" thing - it's not quite true, you can have
multiple params and even though a param is a key/value pair, there's
nothing saying a 'value' can't be composite.

I'm pointing this out because I think you're getting hung up on
detecting when a given param is a single object or a list of
objects. See below:

(code taken from the AWDwR book) # POST /articles # POST /articles.xml def create   @article = Article.new(params[:article])   ... end

So, we're just passing the params hash to the model for processing. The conditional logic to support collections, at this point (not taking ActiveRecord into account), would seem to be fairly easy:

(excuse the pseudo-code, it's late and I don't want to go api hunting) def create   @articles = ( if params[:articles] exists, we process as a collection of articles ... if not, we process as a singular article )   ... end

Blech. :slight_smile:

Even those who lines look ugly and you haven't yet included results
checking.

The conditional code may not even need to be in the controller. Perhaps the heavy-lifting would be offloaded to the appropriate class methods (e.g. Article.new()) in ActiveRecord.

Article.new returns *one* Article so no, that's not the place.

If you want to create a bunch of articles in one go, that's something
like Article.create_zero_or_more_from_this_hash.

But that method (when you give it an accurate name) looks pretty
smelly and my personal preference is for classes to be really good at
doing as little as possible.

So I'd create an ArticleBatch class that was really good at
maintaining a list of Articles and would forward create/update/delete
requests to each object it's referencing.

And suddenly the whole need to detect a singular article vs many
articles goes away - it's not articles, it's *one* article_batch.

batch = ArticleBatch.new(params[:article_batch]) batch.can_save_all? batch.errors batch.save_all batch.destroy_all batch.each_article batch.to_xml

So, if you review my previous mail - you have /articles and /articles/ batch (or /articles_batch - it doesn't matter).

And you have controller methods (maybe in a different _controller.rb
file, maybe not) that do *one* thing really well.

In the end it's a matter of taste and smell. I've seen people
complain about having to create extra classes for their RESTful nouns
and I've seen people opine that REST breaks down a bit in more
complicated systems because you're trying to shoe-horn the concept
(implying that noun classes will litter your project).

Hooey! More classes (that are really good at *one* thing) will set
you free. :slight_smile:

Give it a try, you may even end up saying "actually, I need an
ArticleBatch and an ArticleFactory because handling both new and
existing records leads to way too many conditionals".

HTH, Trevor

Yup, I like what you've got here, too.

Thanks for bringing up the 'singular item' point, too. I was aware of what you're talking about but it's good for others who may be following along.

I like your abstraction of the batch functionalities into a dedicated Batch class. It makes sense, removes the need for conditionals strewn throughout controller methods and is probably a lot easier to create tests for.

I'm going to start working on the general case Batch class and see if we can't get something working.

Thanks for the thoughts!

-Chris