Search code examples
ruby-on-railsruby-on-rails-3formscontrollerslink-to

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 %>

Solution

  • 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.