RESTful PUT and button_to

I’m working on a RESTful app, and I’ve hit a snag. Here’s a
simplified example of the problem. (Excuse me if this example code
isn’t strictly correct; I’m typing from memory.)

Suppose I have a table which models a simple counter. It has a single
column, “count”:

class CreateCounters < ActiveRecord::Migration
def self.up
create_table :counters do |t|
t.column :count, :integer
end
end

def self.down
drop_table :counters
end

I’ve created a RESTful controller using the scaffold_resource
generator. In the “show” view, I’d like to put a link which
increments the counter by one:

<%= h @counter.content %>
<%= button_to “++”, ??? %>

If this were a traditional rails app, the next step would be obvious.
I’d add an “increment” action to the controller:

CounterController

def increment
@counter = Counter.find(params[:id])
@counter.count += 1
@counter.save
redirect_to :action => “show”
end
end

And I’d link the action to the controller:

<%= h @counter.content %>
<%= button_to “++”, :action => “increment” %>

However, this isn’t RESTful. The REST way, as best I understand, is
that updates to an existing resource are accomplished by PUTing an
updated version of the resource to the resource’s path, as if the user
had opened the “edit” view, manually incremented the count, and
clicked “submit”.

And that’s where I get hung up. How do I create a link (or button) in
the view which will call “update” in this way?

Here’s one attempt I made:

<%= h @counter.content %>
<%= button_to “++”,
counter_path(
:id => @counter,
:counter => { :count => @counter.count + 1 } ),
:method => :put %>

But that’s wrong for two reasons. First, the counter_path helper
flattens the nested hash, so the for a counter with id 1 and
count==42, the URL comes out like “/counters/1?count43” - which won’t
be parsed correctly into the params hash by the “update” action.

Second, and more importantly, it’s not truly RESTful, because it’s
PUTing to “/counters/1?count43”, not “/counters/1”.

As far as I can tell what I really need is a way to add extra hidden
fields to the form that button_to generates. But I can’t find any way
to do that. And from my reading of the button_to (and link_to)
source, there is no way to do this.

So: am I missing something? What’s the right way to do this? Do I
have to construct the form and hidden fields manually, without the
help of button_to? Or is there another way to go about it entirely?

Thanks in advance,


Avdi

Hi –

On Sun, 25 Feb 2007, Avdi G. wrote:

create_table :counters do |t|
increments the counter by one:
@counter = Counter.find(params[:id])

be parsed correctly into the params hash by the “update” action.
have to construct the form and hidden fields manually, without the
help of button_to? Or is there another way to go about it entirely?

That’s an interesting question. It’s also a nice distillation of some
REST-related issues.

I follow what you’re saying about the form fields, and normalizing the
++ request so that it’s really just a specific case of the general
“Reset this number” request where the number being reset to happens to
be the current number plus one. My current thinking, though, is that
if there is no general update process – that is, if PUT counters/:id
always maps exactly to incrementing the counter by one and returning a
representation of it in its new state, then that’s OK because it’s
unambiguous. Even though the number count+1 does not reside in the
representation in the request, I think, at least tentatively, that
that doesn’t matter as long as the semantics of the resource and its
identifiers give the system everything it needs to update the
resource.

I’d even tend to think that it’s OK to have a default behavior in the
update method – i.e., if params[:counter][:count] is absent, assume
that it’s an increment of one. The reasoning here is partly that the
form input is not, in any case, the resource itself; it’s just a way
of telling the server what the resource should look like. So the
absence of HTML form fields should not necessarily mean that, from a
REST perspective, the request is uninterpretable.

Anyway, that’s my current train of thought. Back at ya’ :slight_smile:

David


Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)

unknown wrote:

I’d even tend to think that it’s OK to have a default behavior in the
update method – i.e., if params[:counter][:count] is absent, assume
that it’s an increment of one. The reasoning here is partly that the
form input is not, in any case, the resource itself; it’s just a way
of telling the server what the resource should look like. So the
absence of HTML form fields should not necessarily mean that, from a
REST perspective, the request is uninterpretable.

Thank you for your thoughts. I follow what you’re saying.
Unfortunately for my purpose I’m not sure it helps. I chose the
“counter” example because I thought it distilled the problem at hand
without introducing any distracting factors; my actual app code is more
complex than that. To put it briefly it involves nested topics in an
outline, which need the ability to be indented or outdented with a
single click. In this case there’s no default behaviour - the topic’s
new parent topic depends on whether the user clicks to indent or
outdent.

I guess I just need to manually generate the hidden form if I want to do
it RESTfully. Thanks for confirming that this is a real issue, and not
just something I was overlooking.

Cheers,


Avdi