Skip to content

Instantly share code, notes, and snippets.

@dhh
Created May 19, 2011 19:27
Show Gist options
  • Star 45 You must be signed in to star a gist
  • Fork 10 You must be signed in to fork a gist
  • Save dhh/981520 to your computer and use it in GitHub Desktop.
Save dhh/981520 to your computer and use it in GitHub Desktop.
bulk api
# 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
@gilesbowkett
Copy link

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.

@adamcrown
Copy link

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.

@grimen
Copy link

grimen commented May 24, 2011

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.

@grimen
Copy link

grimen commented May 24, 2011

...though I got mixed feelings on the show vs. index, as I used edit action for "edit multiple". =S

@andriusch
Copy link

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

@grimen
Copy link

grimen commented May 25, 2011

@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. :)

@andriusch
Copy link

@grimen what I wanted was to return all records when params[:ids] is empty and selected records when it's present.

@grimen
Copy link

grimen commented May 25, 2011

@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.

@jmorton
Copy link

jmorton commented May 27, 2011

@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

@jamesarosen
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment