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?