Rails: Two buttons on a form

Recently I had a create form where one should be able to chose an associated model from a large set of possible values. It wouldn’t be too pleasant to have a drop down listing all these options. My idea was to render paginated radio buttons and a search field to limit the items available.

Submitting a query shouldn’t wipe out your previous inputs. So, both, create and search button, submit to the same action which then must decide by the button pressed what to do. As searching is a common concern I put this into a – surprise – Concern.


Imagine a politics discussion board. You think it would be a nice feature if users could award prizes to politicians to honor their efforts. Maybe someone wants to award a politician for his achievements as a servant of a neoliberal lobby, e.g. the financial sector. So a user creates the „Disguised private investor bailout magician“ prize and needs to select the awardee. There are a lot of candidates, the German Bundestag alone currently already has 631 members. So here is how the form could look like:

Form with entered award name and description

Form with entered award name and description

The labels and inputs for tile and description are regular form elements. Here is the code for the search field and the select options, pagination is done by kaminari:

  1. <%= render layout: 'shared/paginated', locals: {collection: @politicians, entry_name: 'Politicians', params: {action: :new}} do %>
  2.   <div class="input-group">
  3.     <%= text_field_tag :query, params[:query], class: 'form-control', placeholder: 'Search politician...' %>
  4.     <div class="input-group-btn">
  5.       <%= button_tag type: 'submit', class: 'btn btn-default', name: :commit, value: :search_politicians do %>
  6.         Search
  7.       <% end %>
  8.     </div>
  9.   </div>
  10.   <div class="btn-group-vertical form-control-static" role="group" data-toggle="buttons">
  11.     <% @politicians.each do |politician| %>
  12.       <% active = politician.id.to_s == @award.awardee_id %>
  13.       <% classes = 'btn btn-default text-left-important' + (active ? ' active' : '') %>
  14.       <%= label_tag '', class: classes do %>
  15.         <%= f.radio_button 'awardee', politician.id %><%= "#{politician.last_name}, #{politician.first_name}" %>
  16.       <% end %>
  17.     <% end %>
  18.   </div>
  19. <% end %>

The important parts are the text input named query and the button with value search_politicians to submit the query.


Here is what the controller actions for new and create look like – the magic happens in line 4 where the SearchAndRedirect concern is called:

  1. class AwardsController < ApplicationController
  2.   include SearchAndRedirect
  4.   search_and_redirect commit: :search_politicians, redirects: {create: :new}, forwarded_params: [:award, :query]
  6.   def new
  7.     if params[:award]
  8.       @award = Award.new(params.require(:award).permit(:title, :description, :awardee_id))
  9.     else
  10.       @award = Award.new
  11.     end
  12.     @politicians = Politician.all.page(params[:page])
  13.     search = "%#{params[:query]}%"
  14.     @politicians = @politicians.where("first_name LIKE ? OR last_name LIKE ?", search, search) if params[:query].present?
  15.   end
  17.   def create
  18.     @award = Award.new(params.require(:award).permit(:title, :description, :awardee_id))
  19.     if @award.save
  20.       redirect_to :award, notice: 'Award was created.'
  21.     else
  22.       @politicians = Politician.all.page(params[:page])
  23.       render 'new'
  24.     end
  25.   end
  26. end

search_and_redirect is called with three parameters: :commit, :redirects and :forwarded_params.

The :commit parameter tells the concern for which submit actions it should get active. So if a form is submitted with ’search_politicians‘ as commit value it will forward the specified parameters :award (to maintain previous inputs) and :query to the requested action.

In this case this rule applies if a form was submitted to the create action and will be redirected to new. As :redirects takes a hash, you can specify multiple redirects for the same forwarding rule.


Finally, here is the concern’s code:

  1. module SearchAndRedirect
  2.   extend ActiveSupport::Concern
  4.   module ClassMethods
  5.     def search_and_redirect(options)
  6.       options.deep_stringify_keys!
  7.       commits = (options['commits'] || [options['commit']]).map(&:to_s)
  8.       before_filter only: options['redirects'].keys do
  9.         action = params[:action]
  10.         if commits.include?(params[:commit]) && options['redirects'].keys.include?(action)
  11.           forwarded_params = options['forwarded_params'].reduce({}) { |memo, param| memo.tap { |m| m[param] = params[param] } }
  12.           redirect_to({action: options['redirects'][action]}.merge(forwarded_params))
  13.         end
  14.       end
  15.     end
  16.   end
  17. end

Now, we can search for Wolfram without having to rewrite the whole description again:

Form with preserved inputs after searching

Form with preserved inputs after searching

Leave a comment

Your comment