Restful updating from link... without a form? How?

On the Products show page, I display all the Images (has_many) that
are associated w/ that product.

My Images model has a boolean field called default, which is used to
signify that which image is the default image for the product.

I want to add a link below each picture with something like "Set as
Default" that will update the default property.

I know how to do this easily by just creating another action inside my
controller but I want to stay restful.

Any ideas?

Route file:

map.resources :images, :member => { :make_default => :update }

Controller:

def make_default
old_default_image = Image.find_by_default(true).update_attribute(“default”,false)
image = Image.find(params[:id])
image.update_attribute(“default”,true)
flash[:notice] = “Image has been made default.”
redirect_to images_path
end

Don’t be too paranoid about staying restful, each controller might have it’s own special method that does something “The Seven” don’t.

You use the update method in the image controller.
The link "Set as Default" should update the image default attribute
and then redirect to the products view

I hope this helps

Stijn

That is what I don't know how to do. I don't know how to use the
update method using a link.

Ryan,

I was thinking that I might need to go down this route (no pun
intended =D ) but I was thinking that there might be a way do this
using the update method.

link_to “Update”, image_path(@image), :method => “put”, :image => { :default => true }

Try that.

This won’t set the current default image to false unless you’re going to specify it in your controller action somewhere.

Ryan, why should he make an additional method if he can use the update
method of the imagecontroller?

The update method expects a PUT (form) and not a GET (link). I suggest
you use a form with a button or imagebutton.

If you are using rails 2.0 this will look like
<% form_for image do |f| %>
<%= f. hidden_field(:default, true) %>
<%= submit_tag "Change to default" %>
<% end %>

Please let me know if this works.

-Stijn

The link will also work, a put request does not always have to be a form. This is the same for DELETE which also works with forms AND links.

Thanks Ryan,

I got it working using your code. I just added the check for an
existing default image and update that in the Update action for the
Image controller:

# inside image_controller.rb
def update
    @image = @product.images.find(params[:id])
    @current_default_image = @product.images.find_by_default(true)
    respond_to do |format|
      if @image.update_attributes(params[:image])
        if @image.default == true
          unless @image.id == @current_default_image.id
            @current_default_image.update_attribute("default",false)
          end
        end

        flash[:notice] = 'Image was successfully updated.'
        format.html { redirect_to([@product, @image]) }
        format.xml { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml { render :xml => @image.errors, :status
=> :unprocessable_entity }
      end
    end

~ mel

I believe that you would want :member => :put, rather
than :member=>:update. The value part of the hash refers to the http
verb that should be used with the member (or collection) action.

Perhaps a better solution, if you want to use a named RESTful route,
would be to use nested routes:

map.resources :products do |product|
  product.resources :images, :collection=>{:set_default=>:put}
end

If you think about the problem more abstractly, what you are trying to
accomplish is to set a default image from among the collection of
images associated with a particular product. The important point to
realize is that, from the 20,000 foot view, you are doing something to
the _collection_, not to a specific object within the collection. If
you organize your resources as above you can choose to refactor your
"default image" process later. Even without considering the
refactoring, though, it's a better solution because it takes into
account that "set_default" with some image id means that you intend to
effect _two_ objects in the collection (the named one and the previous
one).

HTH,
AndyV

This solution puts much too much business logic in the controller.
Consider creating an instance method on your Product that sets the
default image by processing a hash (not coincidentally params is a
hash). Among the benefits, you'll be able to introduce unit testing
to make sure it works in every scenario AND you can reuse the code
more easily (e.g., you decide that the default image should always be
the first image added to the collection until otherwise specified by
the user).

HTH,
AndyV

Andy,

I am actually using nested resources. I ended up using the update
action that already is one of the 7 restful actions. I added code to
check if there is an existing default image and to change that image
to be non-default.

I'm not understanding why this is not a good way to go. Can you help
me understand?