views:

147

answers:

2

I'm having troubling completing a task the RESTful way. I have a "tasks" controller, and also a "complete_tasks" controller.

I have this in the complete_tasks_controller create action:

def create
   @task = Task.find(params[:id])
   @task.completed_at = Time.now
   @task.save
end

I tried calling this:

<%=link_to "Complete task", new_task_complete_task_path(@task), :method => :post %>

..but I'm getting errors on that mentioning that "Only get, put, and delete requests are allowed."

Do you know what I'm doing wrong?

+1  A: 

Each map.resources statement routes.rb creates a common RESTful routes for use with the specified resource. The appeal of REST is that is uses the request type and url to determine which action to take. Out of the four verbs associated with HTTP, each one has a specific use.

POST => Create
GET => Retrieve
PUT => Update
DELETE => Destroy

The reason you're getting an error about only get, put, and delete requests being allowed, is that you're using a post request. Essentially you're telling Rails you want to create a task with an id of one. However you cannot create an item that already exists. Which is why posts are not allowed. Instead you want to use put, because you're updating an existing record.

You can do it by changing post, to put in your link_to call. <%=link_to "Complete task", new_task_complete_task_path(@task), :method => :put %>

Have a read through the routing guide and the resources documentation, it will help you understand the difference between HTTP requests, as well as provide some insight into how Rails handles those requests.

EmFi
Awesome! Thanks so much EmFi (and Radar)! Works perfectly :)
andy
Man, this is soooo wrong. I'm not saying you are using Rails wrong, it is just the way Rails tries to do REST is wrong. Doing a PUT should not allow partial updates this way. PUT is a complete replacement of a resource. Whatever the client sends in the body of a PUT is what should be the complete contents of the resource.GPPD really does not match to CRUD and it is a real shame that there are so many references that say it does.
Darrel Miller
Darrel, I'm still trying to come to grips with this: which verb is correct for a partial update then? Or are partial updates just considered a bad idea in RESTful applications?
Mike Buckbee
@Mike PATCH - http://tools.ietf.org/html/draft-dusseault-http-patch-10Until that becomes an accepted standard, the only other options are POST to a processing endpoint, or doing a PUT to a different resource with a formalized change request. It is an unfortunate situation but one we need to live with if there is no way to avoid partial updates.
Darrel Miller
PUT *is* for a partial update. The update action uses it and can edit only part of a resource. Please don't go flaunting things that are not in Rails yet, as it will confuse those who are new.
Ryan Bigg
@radar Let's play that game... Ohhhh no it isn't. I don't care what Rails CAN do with a PUT, as far as the authors of the HTTP 1.1 spec are concerned PUT does a complete replacement, it does not do a partial update. One of the primary REST constraints, referred to as "Uniform Interface" says that you MUST use the verbs consistently as per their specification. You are not allowed to change their meaning. It is completely irrelevant what the server framework is capable of doing.
Darrel Miller
+3  A: 

It would make more sense to move this into an action called complete in your controller:

def complete
  @task = Task.find(params[:id])
  @task.complete!
end

To access this action using RESTful routing you'll need to define a new member route like this in config/routes.rb:

map.resources :tasks, :member => { :complete => :put }

Adding :member => { :complete => :put } to the end of any pre-existing map.resources :tasks will do the trick also, you should only ever have one map.resources :tasks line, unless it's nested. The routing guide explains this better than I ever could.

To get to it from the view:

link_to "Complete this task", complete_task_path(@task), :method => :put

The method complete! would then be defined in your model like so:

def complete!
  self.completed_at = Time.now
  save!
end

The reason for this is that it puts the model logic where it belongs: in the model.

Ryan Bigg
This absolutely the correct way of doing things. I was skimming the code, and missed the bit about using a second controller.
EmFi