-
-
Save dhh/981520 to your computer and use it in GitHub Desktop.
# Bulk API design | |
# | |
# resources :posts | |
class PostsController < ActiveController::Base | |
# GET /posts/1,4,50,90 | |
# post_url([ @post, @post ]) | |
def show_many | |
@posts = Post.find(params[:ids]) | |
end | |
# PUT /posts/1,4,50,90 | |
def update_many | |
@posts = Post.find(params[:ids]) | |
Post.transaction do | |
@posts.each do |post| | |
post.update_attributes!(params[:posts][post.id]) | |
end | |
end | |
end | |
# POST /posts/many | |
def create_many | |
end | |
# DELETE /posts/1,4,50,90 | |
def destroy_many | |
end | |
end |
I think _multiple makes more sense than _many, since you can use the API with two IDs, but two widgets is not very many widgets.
Is it just me, or does anybody else get the feeling that this could lead to some very unDRY code? It seems that show_many
and index
will typically wind up being copies of each other except that they pull from different parameters. Any any sort of logic added to a create
, update
or destroy
will have to be duplicated in it's _many
counterpart. It seems like the code will typically be identical across single or bulk actions, only their responses seem like they need to be handled differently. And even then how would a failed validation on a create or update be handled? Would it render a new_many
or edit_many
? I'm still fairly new to Rails so maybe I'm not understanding how exactly this will be implemented.
For the record: In my impl. I had something like:
GET /posts/1,2,3,4,... => index
GET /posts/1,2,3,4,.../edit => edit
PUT /posts/1,2,3,4,... => update
POST /posts => create
DELETE /posts/1,2,3,4,... => destroy
So I agree that the proposal is a bit overkill unless I've missed something.
...though I got mixed feelings on the show vs. index, as I used edit action for "edit multiple". =S
grimen's solution seems much cleaner to me. Some changes on Relation methods would also help:
Relation#all(params[:ids]) - would do same as find, but if you pass nil it would return all records. This is to avoid:
@posts = Post.scoped
@posts = @posts.where(:id => params[:ids]) if params[:ids]
Relation#update_attributes (and similar methods) - this could be used instead of:
Post.transaction do
@posts.each do |post|
post.update_attributes!(params[:posts][post.id])
end
end
@sinsiliux, the first case could be avoided by having params[:ids]
to be equivalent to params[:id].split(',')
. That's how I handled it at least, but maybe I was playing with fire. :)
@grimen what I wanted was to return all records when params[:ids] is empty and selected records when it's present.
@sinsiliux I just noticed where(:id => params[:ids])
!= where(:id => nil)
- I think this was not the case in Rails 2.3.x (old finders). Anyway, my basic point was that for me params[:ids]
should be same as params[:id]
to avoid singular vs. plural getting in the way.
@grimen I too wonder if handling the one-or-many cases using a single action makes sense. I'm experimenting with this before_filter:
def separate_bulk_ids
if params[:id].present?
params[:ids] = params[:id].split(',')
params[:id] = params[:ids].first
end
end
Is it surprising that params[:id] is always the first of many IDs? Aside from the obvious case of overwriting an explicitly passed :ids parameter, when would this cause problems? Anyhow, here's how I make use of it in a trivial #update example.
def update
# Gracefully ignores invalid IDs
@posts = Post.where(:id => params[:ids])
@posts.each do |post|
# Enforce policy if desired on each post...
post.update_attributes(params[:posts][post.id.to_s])
end
# What results are returned considering some updates may have failed?
end
I've created a little Rails app with some different examples and tests.
https://github.com/jmorton/bulk-example/blob/tolerant/app/controllers/posts_controller.rb
https://github.com/jmorton/bulk-eaxample/blob/strict/app/controllers/posts_controller.rb
If I recall correctly, Richardson's Restful Web Services recommends ,
for dimensional things (like /locations/lat,long/
) and ;
for lists of things (like /posts/1;2;3
). Allamaraju's Restful Web Services Cookbook, on the other hand, doesn't express a preference between the two.
I agree with @Gimi.
/posts/many
doesn't look consistent to me.