ruk·si

🛤️ Ruby on Rails
Helpers

Updated at 2016-01-07 00:12

Helpers are meant to simplify view code by moving all commonly used or complex layout functionality away from the views.

In Ruby on Rails 4, all methods in helper classes at app/helpers are automatically available in all views. Not in models, controllers or anywhere else.

# app/helpers/funny_helper.rb
module FunnyHelper
  def tell_a_joke
    "no."
  end
end
# in any view
<%= tell_a_joke %>

Namespace your helpers to helpers if you need them outside of views. You can access helpers through ActionController::Base.helpers.

class MyPresenterOrViewModelOrSomethingElse
  def do_stuff
    helpers.number_to_currency(amount, :precision => 0)
    # the following would also work:
    # ActionController::Base.helpers.number_to_currency(amount, :precision => 0)
  end

  private
  def helpers
    ActionController::Base.helpers
  end
end

Store custom field definitions in the database. If you have multiple custom fields for you data, avoid just using helpers to render them but store the custom field name, type and Rails partial view name into the database. It makes having a lot of custom field more manageable.

Development Helpers

debug helper is good for development.

<% my_hash = {'first' => 1, 'second' => 'two', 'third' => [1,2,3]} %>
<%= debug(my_hash) %>

Benchmarking execution time of a block in a template.

<% benchmark "Process data files" do %>
  <%= expensive_files_operation %>
<% end %>

Use cache for views fragments. Like menus, lists and other semi-static elements.

<% cache do %>
  <%= render "shared/footer" %>
<% end %>

capture allows extracting a template into a variable. Helps to simplify deeply nested or looping view code.

<% @greeting = capture do %>
  Welcome! The date and time is <%= Time.now %>
<% end %>
<html>
  <head>
    <title>Welcome!</title>
  </head>
  <body>
    <%= @greeting %>
  </body>
</html>

Date Time Helpers

Use human readable interval helpers.

now = Time.current
later = Time.current + 15.seconds

distance_of_time_in_words(now, later)
# => less than a minute
distance_of_time_in_words(now, later, include_seconds: true)
# => less than 20 seconds

time_ago_in_words(3.minutes.from_now)
# => 3 minutes

Asset Helpers

Asset tag helpers allow linking to compiled sources.

image_tag "rails.png"             # public/images
image_url "rails.png"             # public/images
video_tag "movie.ogg"             # public/videos
video_url "movie.ogg"             # public/videos
audio_tag "music.mp3"             # public/audios
audio_url "music.mp3"             # public/audios
javascript_include_tag "common"   # {app,lib,vendor}/assets/javascript
javascript_path "common"          # {app,lib,vendor}/assets/javascript
javascript_url "common"           # {app,lib,vendor}/assets/javascript
stylesheet_link_tag "application" # {app,lib,vendor}/assets/stylesheets
stylesheet_path "application"     # {app,lib,vendor}/assets/stylesheets
stylesheet_url "application"      # {app,lib,vendor}/assets/stylesheets
auto_discovery_link_tag :rss, "http://www.example.com/feed.rss"

By default, Rails links to these assets in the public folder.

# customizing asset host
# config/environments/production.rb
config.action_controller.asset_host = "assets.example.com"
image_tag("rails.png")
# => <img src="http://assets.example.com/images/rails.png" alt="Rails" />

Fingerprint is added if config.assets.digest is true.

ActiveRecord Helpers

Forms can be bind to a model.

# @person variable will have been created in the controller
# e.g. @person = Person.new
<%= form_for @person, url: {action: "create"} do |f| %>
  <%= f.text_field :first_name %>
  <%= f.text_field :last_name %>
  <%= f.submit %>
<% end %>
# submit sends something like this...
{
  "action" => "create",
  "controller" => "people",
  "person" => {"first_name" => "William", "last_name" => "Smith"}
}

You can define what model bound inputs are shown.

label(:article, :title)
check_box(:article, :validated)
file_field(:user, :avatar)
hidden_field(:user, :token)

radio_button(:article, :category, :rails)
radio_button(:article, :category, :java)

text_field(:article, :title)
email_field(:user, :email)
url_field(:user, :url)
date_field(:user, :date_of_birth)
password_field(:login, :pass)
text_area(:comment, :text, size: "20x30")

Use date time input helpers.

date_select(:article, :published_on)        # bound to model attribute
time_select(:order, :submitted)             # bound to model attribute
datetime_select(:article, :published_on)    # bound to model attribute

# all the following take Date, Time, DateTime as the first argument
select_date(Time.today + 6.days)            # generic date select
select_time(Time.now)                       # generic time select
select_datetime(Time.now + 4.days)          # generic date and time select
select_year(Date.today, start_year: 1900, end_year: 2009) # specify between
select_month(Date.today)                    # between 1 and 12
select_day(Time.today + 2.days)             # between 1 and 31
select_hour(Time.now + 6.hours)             # between 0 and 23
select_minute(Time.now + 6.hours)           # between 0 and 59
select_second(Time.now + 16.minutes)        # between 0 and 59
# how to convert back to date
Date.civil(
  params[:start_date][:year].to_i,
  params[:start_date][:month].to_i,
  params[:start_date][:day].to_i)

Use file fields for uploads.

<%= form_for @person do |f| %>
  <%= f.file_field :picture %>
<% end %>

def save_upload
  uploaded_io = params[:person][:picture]
  path = Rails.root.join('public', 'uploads', uploaded_io.original_filename)
  File.open(path, 'wb') do |file|
    file.write(uploaded_io.read)
  end
end

Use collection selection to support model relations.

class Article < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end
collection_select(
  :article,
  :author_id,
  Author.all,
  :id,
  :name_with_initial,
  {prompt: true}
)

Optionally use collection radio buttons to support model relations.

class Article < ActiveRecord::Base
  belongs_to :author
end

class Author < ActiveRecord::Base
  has_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end
collection_radio_buttons(
  :article, :author_id, Author.all, :id, :name_with_initial)

Use collection check boxes for one-to-many relations.

class Article < ActiveRecord::Base
  has_and_belongs_to_many :authors
end

class Author < ActiveRecord::Base
  has_and_belongs_to_many :articles
  def name_with_initial
    "#{first_name.first}. #{last_name}"
  end
end
collection_check_boxes(
  :article, :author_ids, Author.all, :id, :name_with_initial)

Use option groups to group selections.

class Continent < ActiveRecord::Base
  has_many :countries
  # attribs: id, name
end

class Country < ActiveRecord::Base
  belongs_to :continent
  # attribs: id, name, continent_id
end

option_groups_from_collection_for_select(
  @continents, :countries, :name, :id, :name, 3)

For custom options, use options_for_select.

# you have to wrap this call in a regular HTML select tag
options_for_select([ "VISA", "MasterCard" ])

country_select offers a country listing.

country_select(:user, :country)

Time zone should define how users see all dates and times.

time_zone_select(:user, :time_zone)

Generic Form Helpers

Form tag helpers don't bind to ActiveRecord objects. They all end in _tag.

<%= form_tag "/articles" do %>
  <%= submit_tag "Save" %>
  <%= image_submit_tag("login.png") %> <%# clicking image submits the form %>
<% end %>

<%= field_set_tag do %>
  <%= text_field_tag "name" %>
<% end %>

<%= label_tag "name" %>
<%= text_field_tag "name" %>
<%= text_area_tag "article" %>
<%= email_field_tag "email" %>
<%= url_field_tag "url" %>
<%= date_field_tag "date_of_birth" %>
<%= hidden_field_tag "token", "VUBJKB23UIVI1UU1VOBVI@" %>
<%= password_field_tag "pass" %>
<%= check_box_tag "accept" %>
<%= radio_button_tag "gender", "male" %>
<%= select_tag "people", "<option>David</option>" %>

<%= form_tag({action:"post"}, multipart: true) do %>
  <%= file_field_tag "file" %>
  <%= submit_tag %>
<% end %>

<%= javascript_tag "alert('All is good')" %>

Number Helpers

number_with_delimiter(12345678)                 # => 12,345,678
number_with_precision(111.2345, 2)              # => 111.23
number_to_currency(1234567890.50)               # => $1,234,567,890.50
number_to_human_size(1234)                      # => 1.2 KB
number_to_percentage(100, precision: 0)         # => 100%
number_to_phone(1235551234)                     # => 123-555-1234

Sanitization Helpers

Sanitize helper will HTML encode all tags and strip all attributes that aren't specifically allowed.

sanitize @article.body

# allow specific tags and attributes
sanitize @article.body, tags: %w(table tr td), attributes: %w(id class style)

# globally allow tags
class Application < Rails::Application
  config.action_view.sanitized_allowed_tags = 'table', 'tr', 'td'
end
sanitize_css("body { background: red; }")
# => body { background: red; }

strip_links(%q(<a href="http://rubyonrails.org">Ruby on Rails</a>))
# => Ruby on Rails

strip_tags("Strip <i>these</i> tags!")
# => Strip these tags!

Sources