Ruby on Rails 3 View Form Helper, File Upload & Download

Form

Post back to the current page

<% form_tag do %>

Generates <form action="/current_controller/current_action" method="post">

HTTP Get

<% form_tag(account_path, :method => "get") do %>
...
<% end %>

Generates <form action="/account" method="get">

Form tag with query parameters

form_tag(:controller => "home", :action => "index", :q1 => "v1", :q2 => "v2")

Generates <form action="/home/index?q1=v1&q2=v2" method="post">

Reading Form Field

Submit a HTML form

View
<!-- Submit the form to action 'edit' in the controller -->
<% form_tag :action => 'edit' do %>

 <p>value:<%= text_field_tag(:name) %></p>
 <%= submit_tag 'Change' %>

<% end %>

Edit the controller to handle the HTML form data

  def edit
    # Retrieve the parameter as :name
    @value = params[:name]
  end

For HTML code,

<input id="account_name" name="account[name]" type="text"/>

Read the value in a controller as

params[:account][:name]

Field Tag

<%= label_tag(:lb, "My Label") %>
<%= check_box_tag(:cb) %>
<%= radio_button_tag(:rb, "value1") %>
<%= text_area_tag(:ta, "text", :size => "50x5") %>
<%= password_field_tag(:password) %>
<%= hidden_field_tag(:hf, "value") %>
<%= select_tag(:city, options_for_select([['Los Angeles', 1], ['San Francisco', 2], ...], 2) %>
<%= select_tag(:city, options_from_collection_for_select(City.all, :id, :name) %>
<%= select_date Date.today, :prefix => :end_date %>

Form Generated by Rails Scaffolding

Scaffolding Command

rails generate scaffold Account user_name:string description:text premium:boolean income:integer
                                      ranking:float fee:decimal birthday:date login_time:time

Code Generated For Create Account

Controller
def new
  @account = Account.new
end
new.html.erb/edit.html.erb
<h1>New account</h1>

<% form_for(@account) do |f| %>
  <%= f.error_messages %>

  <p>
    <%= f.label :user_name %><br />
    <%= f.text_field :user_name %>
  </p>
  <p>
    <%= f.label :description %><br />
    <%= f.text_area :description %>
  </p>
  <p>
    <%= f.label :premium %><br />
    <%= f.check_box :premium %>
  </p>
  <p>
    <%= f.label :income %><br />
    <%= f.text_field :income %>
  </p>
  <p>
    <%= f.label :ranking %><br />
    <%= f.text_field :ranking %>
  </p>
  <p>
    <%= f.label :fee %><br />
    <%= f.text_field :fee %>
  </p>
  <p>
    <%= f.label :birthday %><br />
    <%= f.date_select :birthday %>
  </p>
  <p>
    <%= f.label :login_time %><br />
    <%= f.datetime_select :login_time %>
  </p>
  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

DB Column type & corresponding HTML form field

DB Column HTML Form Field
string text_field
text text_area
boolean check_box
integer text_field
float text_field
decimal text_field
date date_select
time date_time_select

Form View Code

<% form_for(@account) do |f| %>

Based on the values on @account, form_for will generate different HTML code to create or edit a account

If @account is no persisted, form_for generated HTML code to edit new account

<form action="/accounts" class="new_account" id="new_account" method="post">
<div style="margin:0;padding:0">
  <input name="authenticity_token" type="hidden" value="7dP6DSmy5CdiN7pVpEo8vgawc5MQeF1/W99k1Ld24+g=" />
</div>
  • form_tag generate the authenticity token code to prevent cross-site request forgery
  • If you're writing a form manually, use form_authenticity_token method to generate the token to avoid the cross-site request forgery

Otherwise, generated HTML code to edit existing account

<form action="/accounts/1" class="edit_account" id="edit_account_1" method="post">
<div style="margin:0;padding:0">
   <input name="_method" type="hidden" value="put" />
   <input name="authenticity_token" type="hidden" value="7dP6DSmy5CdiN7pVpEo8vgawc5MQeF1/W99k1Ld24+g=" />
</div>
  • Because most browser does not support HTTP put or delete method, Rails use the hidden field "_method" to indicate the intended Restful method
  • When parsing POST method, Rails will treat _method parameter as the HTTP method if it is present

Form Field

Field Attributes

To override the default field attributes

<%= f.text_area :description, :cols => 80, :rows => 10 %>

Field Label

<%= f.label :user_name%>

To specify display text

<%= f.label :user_name, "Member Name" %

Text/Text Area View Code

  <p>
    <%= f.label :user_name %>
    <%= f.text_field :user_name %>
  </p>
  <p>
    <%= f.label :description %>
    <%= f.text_area :description %>
  </p>

Generated HTML code

  <p>
    <label for="account_user_name">User name</label><br />
    <input id="account_user_name" name="account[user_name]" size="30" type="text" />
  </p>
  <p>
    <label for="account_description">Description</label><br />
    <textarea cols="40" id="account_description" name="account[description]" rows="20"></textarea>
  </p>

Check box

<%= f.check_box :premium%>

To override the checkbox value

<%= f.check_box :premium, {}, "yes", "no" %>

Radio Button

<% shippment = { 'USPS' => 'USPS Method', 'FedEx' => 'FedEx Method'}%>
<p>
    <b>Shippment</b><br />
    <% shippment.each_pair {|key, value| %>
        <%= f.radio_button :shipment, key %> <%= h value %><br />
    <% } %>
</p>

Select Option

<%= f.select (:shipment, ['USPS', 'FedEx'])%>

To supply the display text

<%= f.select (:shipment, [['Postal Service', 'USPS'], ['FedEx']])%>

Hidden field

  <%= f.hidden_field :fee, :value=>10 %>

Password field

<%= f.password_field :password %>

Form with Model Object

Create a new Account
form_for(:account, @account, :url => accounts_path)
Edit a new Account
form_for(:article, @article, :url => account_path(@account), :method => "put")
Short form
form_for(@account)
  • Rails uses new_record? to determine whether the form is creating or editing the @account object
<% form_for :account, @account, :url => {:action => "create"}, :html => {:class => "new_form"} do |f| %>
  <%= f.text_field :user_name %>
  ...
<% end %>

Generates

<form action="/accounts/create" method="post" class="new_form">
  <input id="account_user_name" name="account[user_name]" size="30" type="text" />
  ...
</form>
  • :account is the name of the model
  • @account is the object being edited
  • HTML options are passed in the :html hash

Nested objects in form

<% form_for :account, @account, :url => {:action => "create"}, :html => {:class => "new_form"} do |f| %>
  <%= f.text_field :name %>
  <%= fields_for @account.contact do |f2| %>
    <%= f2.text_field :phone %>
  <% end %>
<% end %>

Rails Form Builder

Use form builder to extend the built-in field builder, for example, repeating form field code

<!-- Out of the box field builder -->
<%= f.select (:shipment, [['Postal Service', 'USPS'], ['FedEx']])%>

can be replaced by a simpler custom form builder

<!-- Equivalent custom field builder -->
<%= f.shippment_select :shipment %>

Create a Form Builder

Create

app/helpers/my_builder.rb
class MyBuilder < ActionView::Helpers::FormBuilder

  # Define a select options for shipment
  def shipment(method, options={}, html_options={})
    select(method, [['FedEx', 'FedEx'],['Postal Service', 'USPS']], options, html_options)
  end

end

Use a Form Builder

<!-- Configure a new form builder -->
<% form_for(:photo, @photo, :url => {:action=>'create'}, :builder => MyBuilder) do |f| %>

  <%= f.error_messages %>

  <p>
    <%= f.label :shipment %><br />
    <%= f.shipment_select :shipment %>
  </p>

  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Builder Options

Passing Options to Builders

<%= f.label_for :description, :hi_light=true %><br />

Custom Builder

  def label_for(method, options={})
    wrap(label(method,options), options)
  end

  def wrap(text, options={})
    if options[:hi_light]
      return "<div class='high_lite'>" + text + "</div>"
    end
    text
  end

File Upload using Rails

Scaffold

rails generate scaffold Photo filename:string

View for Uploading a Photo

new.html.erb
<!-- Invoke the Photo Controller's "create" action -->
<% form_for(:photo, @photo, :url => {:action=>'create'}, :html=> {:multipart=>true}) do |f| %>

  <%= f.error_messages %>

  <p>
    <%= f.label "data" %><br />
    <!-- Upload file and invoke "load_photo_file" method in the Controller to process the upload file -->
    <%= f.file_field :load_photo_file %>
  </p>

  <p>
    <%= f.submit 'Create' %>
  </p>
<% end %>

Saving Upload File

Use Photo Model to save the upload file

Photo.rb
class Photo < ActiveRecord::Base
   # Root directory of the photo public/photos
   PHOTO_STORE = File.join RAILS_ROOT, 'public', 'photos'

   # Invoke save_photo method when save is completed
   after_save :save_photo

   # "f.file_field :load_photo_file" in the view triggers Rails to invoke this method
   # This method only store the information
   # The file saving is done in after_save
   def load_photo_file=(data)
     # Record the filename
     self.filename = data.original_filename
     # Store the data for later use
     @photo_data = data
   end

   # Called when save is completed
   def save_photo
     if @photo_data
       # Write the data out to a file
       name = File.join PHOTO_STORE, self.filename
       File.open(name, 'wb') do |f|
         f.write(@photo_data.read)
       end
       @photo_data = nil
     end
   end
end

File Download

File download with send_data

require "prawn"
class DataController < ApplicationController
  # Generates a PDF document and send it back
  def download
    send_data pdf,
              :filename => "file.pdf",
              :type => "application/pdf"
  end

  private

  def pdf
    Prawn::Document.new do
      text "testing"
    end.render
  end

File download with send_file

class FileController < ApplicationController
  # Stream a file to client
  def download
    send_file("/doc/file.pdf",
              :filename => "file.pdf",
              :type => "application/pdf")
  end
end

Support multiple format using a Rails Controller

class DataController < ApplicationController
  def show
    ...

    respond_to do |format|
      format.html
      format.pdf { render :pdf => pdf }
    end
  end
end

To register PDF request handling

config/initializers/mime_types.rb
Mime::Type.register "application/pdf", :pdf