ReST PUT with multiple records

I’m writing a Flex/ReST/Rails app and I currently do a GET on
/customers/123/licences.xml to get the licences for customer 123. This
works fine. Now, after updating one or more licences, I wish to update
(PUT) the licences back to the server.

Does this sound reasonable and restful ?

The problem is that map.resources doesn’t add a route for PUT on the
index action, and I haven’t been able to figure out how to specify the
method for a route. It does work to PUT a single licence to eg.
/licences/456.xml

Many thanks for any assistance.

Why not PUT the updated licenses one by one? This saves you a lot of
trouble I think… for example, what to do if one of the 10 updated
licenses will not save because, e.g., validation fails?

On 10/29/07, Gerjan S. [email protected] wrote:

Why not PUT the updated licenses one by one? This saves you a lot of
trouble I think… for example, what to do if one of the 10 updated
licenses will not save because, e.g., validation fails?

Pretty inefficient when you can just slurp all the records in one
request.

Make your create action know what to do when it receives a collection
document instead of an item document. Or break REST a little bit and
write a create_many action. As far as handling errors goes, it
depends entirely on what you need to do. You can run all the creates
in a transaction and return an error code if one fails. You can run
them all and make callbacks somewhere, notifying the source app that
one of the creates failed.

Pat

On 10/29/07, Pat M. [email protected] wrote:

document instead of an item document. Or break REST a little bit and
write a create_many action. As far as handling errors goes, it
depends entirely on what you need to do. You can run all the creates
in a transaction and return an error code if one fails. You can run
them all and make callbacks somewhere, notifying the source app that
one of the creates failed.

I’ve thought about this some in my own app, from the desire to bulk
import items via XML. Here’s my train of thought:

  1. Create a custom rest method. map.resources :items, :collection =>
    {:bucket => :post}

POST /items/bucket.xml # import

  1. Depending on my needs, maybe I’ll want to validate the items and
    let the user verify before actually inserting them into the system.

map.resources :items do |item|
item.resource :buckets
end

POST /items/bucket.xml # import
GET /items/bucket.xml # view current validated items
PUT /items/bucket.xml # confirm and actually import items
DELETE /items/bucket.xml # remove validated items instead of importing
them


Rick O.
http://lighthouseapp.com
http://weblog.techno-weenie.net
http://mephistoblog.com

Thanks for the suggestions & discussion.

I’m toying with my own philosophy that any GET should be able to be PUT
back.
I store my GET as an XML node tree, the Flex controls modify this, then
I put it back to its original URL. Currently this isn’t efficient as
only one field in one licence may have changed, but there is potential
for non-obtrusive optimisation if the XML nodes can track whether they
or their children have changed.

I got the route by logging all routes to a file with :

def self.dump_routes
result = ‘’
ActionController::Routing::Routes.routes.each { |route| result <<
route.to_s }
end

and then modifying my routes until I got the desired output.

The result is now :

map.resources(:customers,{}) do |customer|
customer.resources(:licences,{})
customer.connect ‘/customers/:customer_id/licences.xml/’, :controller
=> ‘licences’, :action => ‘index’, :format => ‘xml’, :conditions => {
:method => :put }
end
map.resources(:licences,{})
map.connect ‘licences.xml’, :controller => ‘licences’, :action =>
‘index’, :format => ‘xml’, :conditions => { :method => :put }

(and it works) for anyone interested.

I’m using a modified generic rest controller from
Google Code Archive - Long-term storage for Google Code Project Hosting. and the index action
looks like :

def index(options = @options)
if request.method==:get
@page_title ||= 'Listing ’ + resources_display_name

  scope_find do
    resources_instance_set resource_model.find(:all, 

options[:find_options])
end

  respond_to do |format|
    format.html { render :action => options[:template] || 'index'}
    format.xml  { render :xml => resources_instance.to_xml }
  end
elsif request.method==:put
  resourcelist = params[resources_name][resource_name] rescue nil
  raise RuntimeError("records not found") unless resourcelist && 

!resourcelist.empty?
resourcelist = [resourcelist] unless resourcelist.is_a? Array
success = true
errors = {}
resourcelist.each do |aResourceHash|
model_instance = resource_model.find_by_id(aResourceHash[‘id’])
success = false unless
model_instance.update_attributes(aResourceHash.filter_include(resource_model.column_names))
errors = model_instance.errors
break if !success
end
if success
render :nothing => true
else
render :xml => errors.to_xml
end
end
end

magpie

Yeah, except that the bulk importing doesn’t seem to work at all…
because rails maps the bulk to params… and params is a hash, which
as we all know means that the keys get overwritten with every update

(ie if we have a collection thus:)



Yes!



No!



Maybe!


then our params looks like this:

params = {:blahs => {:blah => “Maybe!”}}

OMG what happened to the other two blah objects?

Well, they’re in the params hash, and unfortunatley it’s a hash, not
an ordered array of arrays, and so we lost all our key overwrites.

What to do then, Rick?

I thought hmmmm maybe name the blahs

thus:



that’s ugly.

What do you reckon?

Julian.