Trying to follow REST principles - should I use Create or Update?

I have an Upload resource which takes care of user-uploaded files. I now have a requirement for users to be able to ‘save’ some of these uploads to a library.

Instead of adding nonRESTful actions to the Uploads controller I decided to try a RESTful approach.

Enter the SavedUploadsController and routes. It works great and doesn’t pollute the Uploads controller with unRestful actions.

My only issue is that I can’t get it work with #create - it will only work with #update.

Conceptually I see it as creating a saved upload, not updating:

# saved_uploads_controller.rb

class SavedUploadsController < ApplicationController
  before_filter :authenticate_user!

  def update # <- feels like this should be #create
    @upload = Upload.find(params[:id])
    @upload.saved = true
    @upload.save

    render :update
  end

  def destroy
    @upload = Upload.find(params[:id])
    @upload.saved = false
    @upload.save

    render :destroy
  end

end

Am I going about this the right way? Should I be looking at this as a nested resource under Uploads? Currently it’s not nested.

I feel you are going about it the right way. It seems create is the right action. My initial guess is that the form builder is using a patch/put method instead of a post. If you are explicit with the method (method: :post) on the form it should hit create instead.

This doesn’t feel correct to me. When you ‘save’ an upload, it’s really just modifying an existing upload. You’re not storing a new kind of resource.

I think saving should be a PATCH to the existing resource, e.g.

= form_for(@upload, method: :patch) do
  = f.hidden_field "saved", "true"
  = f.submit "Save"

The action would look something like:

def update # PUT and PATCH both map to `update`
  @upload = Upload.find(params[:id])
  @upload.update(params[:upload])
  # ...
end

where params[:upload] is a hash, e.g. { saved: true }.

This should mean if in future you want to have some other attribute, such as ‘favourite’ then there should be no need to modify the controller.

Thanks for the replies guys. @frank-west-iii - I was trying to get it working with #create when @andyw8 chipped in and made me rethink things.

It’s true, I’m not creating a new kind of resource, but I’m still not clear how a ‘saved’ upload is different to an ‘accepted’ invitation (one of the examples in the Upcase repo). I guess there are subtleties involved that I’m still grappling with.

You’re totally right about favoriting uploads too - that’s definitely going to happen in the future.

I should point out that saved uploads are treated as a different kind of resource on the user side of the app. Regular uploads are just added to one post as it’s created and are then basically forgotten. Saved uploads, in contrast, are kept in the users library for reuse at any time.

This gave the #index action in the SavedUploadsController an obvious job - to display the user’s library.

I also liked the fact I could do a simple ‘remove from library’ action like this:

<%= link_to "Remove", saved_upload_path(upload), confirm: "Remove this upload from your library?", method: :delete, remote: true %>

without having to create a form with hidden fields to achieve the same thing. I need a confirmation on this action so I would have to hijack to form submission with JS and display a dialog before submitting the form.

My basic library upload item looks like this:

The ‘Rename’ action is actually a form but I switch it in and out with the name text with JS when the user clicks ‘Rename’, so I guess having a form for the ‘Remove’ action is no more complicated.

@andyw8 - do you still think I should go with updating the existing Upload resource?

@weavermedia

A couple scattered thoughts:

I am torn @andyw8 on the create/update thing and could see it go either way.

I think you’re right when you liken this to Upcase’s “acceptances” in Upcase (mentioned on the recent Weekly Iteration video about REST). Have you considered nesting your “save” resource like the Upcase “download” resource (see 22:40 in the video)?

resources :uploads do 
  resource :save, only: [:update]
end

# PATCH /uploads/:upload_id/save
# PUT /uploads/:upload_id/save
# this will route to "saves#update"

# OR maybe better!

resources :uploads do 
  resource :save, only: [:create] 
end

# POST /uploads/:upload_id/save

It seems to me that a “save” could be thought of like a save in a video game. It’s not the game itself but it is a concrete thing related to the game - a verb and the thing created by the verb. In your case, even though you’re not creating a new item in the database, they point out in that video that there doesn’t have to be a 1:1 correlation between models and routes. The save is a resource/concept/thing in your app.