jlarry
jlarry

Reputation: 430

Rails: set a value using a link

I need help trying to create a link that submits an edit form.

Let's say I have a list of objects

Object -   Color -   Own? 
Ball   -   Red   -  false   - [button]
Hat    -   Blue  -  true    - [button]
Shoe   -   Green -  false   - [button]

When I click on the [button] I want to set "Own?" to True.

Routes

  resources :toys

Controller

def edit
    @toy = Toy.find(params[:id])
end

def update
  @toy = Toy.find(params[:id])
  if @Toy.update_attributes(params[:toy])
    flash[:notice] = "Toy Updated"
    redirect_to @toy
  else
    render 'edit'
  end
end

View

<h2>Toys</h2>
    <% if @toys %>
    <% @toys.each do |toy| %>
          <%= toy.name %> - <%= link_to 'Set Own', edit_toy_path(:id=>toy.id, :owned=>'true')%>
    <br/>
    <% end %>
<% else %>
    None
<% end %>

Upvotes: 3

Views: 2804

Answers (2)

Andrew
Andrew

Reputation: 43123

This is all about how you setup your controller actions. I'm not totally sure I understand how you want to use yours, but I have a similar case that I'll show you which I think you should be able to adapt to your situation.

In my case, I have a menu button that sets a value in the session to either keep a menu panel open or closed across any views a user looks at.

First, you need a controller action that is going to do the work you're interested in. I created a "SharedController" which handles application-wide things that don't belong to any particular view or other controller.

class SharedController < ApplicationController

  # Used by AJAX links to set various settings in shared views
  def edit
    session[:admin_menu] = params[:admin_menu].to_sym if params[:admin_menu]
    session[:advanced_search] = params[:advanced_search].to_sym if params[:advanced_search]

    render :nothing => true
  end
end

This controller action can set one of two values in the session, either: "admin_menu" (boolean) or "advanced_search" (boolean). Then certain views ask whether the session value for admin_menu or advanced_search is true, and if so they show the view.

You could use the same logic. Something like:

def edit
  object= Object.find(params[:object_id])
  object.own = params[:own]
  object.save
end

To trigger this controller action with a link you need to have a route that accepts GET requests. edit is a logical choice.

resource :shared, :only => [:edit], :controller => 'shared'

Note: I think SharedController makes more sense than SharedsController, and edit_shared_path makes more sense than edit_shareds_path, so I had to specify :controller => 'shared' in my routes.rb.


Then you just need a link to a url with params. To add params onto a path you just add them to the path helper, like so:

edit_shared_path(:key => 'value')

You can retrieve these params in your controller via:

params[:key]

Make this a link like so:

link_to 'Set Own to True for This Object', edit_shared_path(:object_id=>object.id, :own=>'true')

NOTE: It's best to do this via AJAX, so be sure to set :remote=>true. If you don't use AJAX then you need to specify a redirect in your controller for what page should be loaded after this link is triggered.


In the case of my admin menu preference link, I need a link with two possible states. I generate these using a helper:

  # Shows Admin Menu Button
  def admin_toggle_button
    if session[:admin_menu] == :on
      link_to( 'Admin Tools', edit_shared_path(:admin_menu => :off), :remote=>true, :class => 'selected', :id => 'admin_toggle_button', :title => 'Hide Admin Menu' )
    else
      link_to( 'Admin Tools', edit_shared_path(:admin_menu => :on), :remote=>true, :id => 'admin_toggle_button', :title => 'Show Admin Menu' )
    end
  end

In a view I just call this using admin_toggle_button. You can do something similar if you like, but it's optional.

I hope that gets you on the right track, let me know if you have any questions.


EDIT: Based on your comment:

Links issue GET requests, which mean you're going to the EDIT action. See: http://guides.rubyonrails.org/routing.html#crud-verbs-and-actions

A further issue, you have resources :toys instead of resource :shared (which I used for this purpose). This means your link helper is already expecting a specific toy to edit, rather than handling a singular resource. See: http://guides.rubyonrails.org/routing.html#singular-resources

Your link would work if you changed it to be:

link_to 'Set Own', edit_toy_path(@toy, :owned=>'true'), :remote => true

... and set your edit action in your controller to the following:

def edit
  @toy = Toy.find(params[:id])
  @toy.owned = params[:owned]
  if @toy.save!
    head :ok
  else
    head :internal_server_error
  end
end

See: http://guides.rubyonrails.org/layouts_and_rendering.html#using-head-to-build-header-only-responses

Now, be aware, you really should only do this with AJAX links, and you should normally not do it with your "real" controller. The reason is, now this is the only action that can be processed by EDIT, so your normal toys#edit view would no longer work.

You can get around this by create a new action and a new route, for instance:

resources :toys do
  member do
    get 'set_ownership'
  end
end

Then simply take the same method above and call it set_ownership instead of edit. IE:

class ToysController < ApplicationController
  ...
  def set_ownership
    ...
  end 
end

Hope that all makes sense.

Upvotes: 3

salt.racer
salt.racer

Reputation: 22332

The edit_toy_path method that your link_to method is calling is going to the edit action inside your controller. It's not going to the update method that I'm guessing you want.

Your link_to will need to change to something like:

<%= link_to 'Set Own', toy_path(:id=>toy.id, :owned=>'true'), :method => :put %>

But I question this particular approach. I don't think the variable will update correctly in the update action because it is not namespaced to the proper params[:toy] object that update_attributes is expecting. And in my quick and dirty tests I couldn't get it to namespace properly.

When I have a situation like the one that you are describing I usually setup another action, like toggle_ownership and I call that from my link_to with a :remote => true option. Then the controller toggles the attributes as desired.

Thus, my routes looks something like:

resources :toys do
  member do
    put :toggle_ownership
  end
end

And my view looks like

<%= link_to 'Set Own', toggle_ownership_toy_path(toy.id), :method => :put %>

The controller sets the variable and renders back a toggle_ownership.js.erb file that updates the appropriate section of the page.

Hope that helps!

Upvotes: 1

Related Questions