Action View - Helpers 方法

    Rails中,Helper指的是可以在Template中使用的辅助方法,主要用途是可以将资料转化成输出用的HTML字串,例如我们已经用过了Rails内建的方法,它可以将字串变成超连结。_Rails还内建了许多Helper方法,可以让我们建构HTML更为容易。我们在一章中将介绍其中较为常用的几个方法。

    另一个使用Helper的理由是可以简化Template中的复杂结构,将Template中较为复杂的程式都用Helper包装起来,最好让Template只包含单纯的变量以及最简单的条件逻辑和循环,这样就算是不会程式的网页设计师,也能够轻易了解套版甚至修改Template样板。

    使用Rails内建的静态档案(Assets)辅助方法有几个好处:

    • Rails会合并StylesheetJavasSript档案,可以加速浏览器的下载。
    • Rails会编译SassCoffeeScript等透过Assets template engine产生的StylesheetJavasSript
    • Rails会在静态档案网址中加上时间序号,如果内容有修改则会重新产生。这样的好处是强迫用户的浏览器一定会下载到最新的版本,而不会有浏览器快取到旧版本的问题。
    • 变更Assets host主机位址时,可以一次搞定,例如上CDN时。透过HelpersRails可以帮所有的Assets加上静态档案服务器网址。

    几个常用的方法:

    • javascript_include_tag
    • auto_discovery_link_tag
    • favicon_link_tag
    • image_tag
    • video_tag
    • audio_tag

    格式化辅助方法

    \n换行字符换成HTML<br>标籤。在表单输入的<textarea></textarea>中,换行其实是\n控制字符,因此输出在网页上时,\n代表的是在HTML原始码中换行,因此我们经常需要将\n再换成<br>标籤,这样浏览器看到的画面才有换行的呈现。

    truncate

    撷取前几个字符

    1. <%= truncate("Once upon a time in a world far far away") %>
    2. # 输出 "Once upon a time in a world..."
    3. <%= truncate("Once upon a time in a world far far away", length: 17) %>
    4. # 输出 "Once upon a ti..."

    strip_tags

    移除HTML标籤

    移除HTML超连结标籤

    distance_of_time_in_words

    输出很潮的时间距离,例如

    1. distance_of_time_in_words(Time.now, Time.now + 60.minutes)
    2. => "about 1 hour"

    distance_of_time_in_words_to_now

    1. distance_of_time_in_words_to_now(Time.now - 1.second)
    2. => "less than a minute"

    输出HTML5时间标籤

    1. time_tag(Time.now)
    2. => "<time datetime=\"2014-11-03T23:55:11+08:00\">November 03, 2014 23:55</time>"

    number_with_delimiter

    1. number_with_delimiter(1234567)
    2. => "1,234,567"

    number_with_precision

    1. number_with_precision(123.4567, precision: 2)
    2. => "123.46"

    URL辅助方法

    • link_to 文字超连结

    除了学过的 <%= link_to '超连结文字', xxx_path %>用法之外,如果超连结文字很多甚至有图片,可以用 block 的方式改写,例如:

    1. <%= link_to user_path(user) do %>
    2. <%= image_tag user.avatar.url %> <%= user.display_name %>
    • mail_to E-mail
    • currentpage?(url) 是否目前是_url这个页面,通常是在layout上搭配tab样式做active效果

    除了使用Rails内建的Helper,我们可以建立自定的Helper,只需要将方法定义在app/helpers/目录下的任意一个档案就可以了。在产生Controller的同时,Rails就会自动产生一个同名的Helper档案,照惯例该Controller下的Template所用的Helper,就放在该档案下。如果是全站使用的Helper,则会放在app/helpers/application_helper_rb,例如:

    module ApplicationHelper
        def gravatar_url(email)
         gravatar_email = Digest::MD5.hexdigest(email.downcase)
         return "http://www.gravatar.com/avatar/#{gravatar_email}?s=48"
        end
    end
    

    如此便可以在Template中这样使用:

    <%= image_tag gravatar_url(user.email) %>
    

    如果想要写出 Helper 可以传 Block 参数,例如上述的 link_to 传 block,像这样:

    <%= my_helper do %>
      blah
    <% end %>
    

    则可以这样定义 Helper:

    def my_helper(&block)
      tmp = capture(&block)
      "header #{tmp} footer"   # 最后输出 header blah foobar
    end
    

    另外,Controller里面定义的方法,也可以用helpermethod曝露出来当作_Helper,例如

    class ApplicationController < ActionController::Base
      #...
      helper_method :current_user
    
      protected
    
      def current_user
        @current_user = User.find(session[:user_id]) if session[:user_id]
      end
    end
    

    如何安全地处理HTML逸出问题?

    Rails在输出任何内容在网页上时,为了安全性都会作HTML逸出,例如会将<符号变成&lt;。也就是说如果使用者在表单中输入了

    <script>alert("Hack you!");</script>
    

    那么Rails在输出<%= @event.description %>时,并不会乖乖地显示一模一样的<script>alert("Hack you!");</script>,因为如果如此的话,每个浏览这个网页的使用者,就会去执行这个JavaSCript而被迫跳出一个alert视窗。

    Rails会逸出HTML输出成为:

    &lt;script&gt;alert(&quot;Hack you!&quot;);&lt;/script&gt;
    

    这样使用者就会看到 <script>alert("Hack you!");</script>,而不是去执行<script>alert("Hack you!");</script>

    是很常见的网站攻击手法,攻击者可以以此植入一些恶意的JavaScript让其他使用者不经意执行,包括窃取Cookie和盗用权限等等。Rails默认采用了全部逸出的方式来防睹这个安全性。不过,也因此开发者必须了解如何正确地关闭这个功能,当你想要显示出HTML不要逸出的时候。

    如果你的表单允许使用者输入HTML,那么你会想要在输出的时候,可以开放一些白名单不要做HTML逸出,这时候就使用sanitize这个辅助方法,例如:

    <%= sanitize( @event.description ) %>
    

    默认允许的HTML标籤和属性如下:

    ActionView::Base.sanitized_allowed_tags
    => #<Set: {"strong", "em", "b", "i", "p", "code", "pre", "tt", "samp", "kbd", "var", "sub", "sup", "dfn", "cite", "big", "small", "address", "hr", "br", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "dl", "dt", "dd", "abbr", "acronym", "a", "img", "blockquote", "del", "ins"}>
    ActionView::Base.sanitized_allowed_attributes
    => #<Set: {"href", "src", "width", "height", "alt", "cite", "datetime", "title", "class", "name", "xml:lang", "abbr"}>
    

    如果需要增加,可以在config/application.rb中新增,例如:

    config.action_view.sanitized_allowed_tags = %w[table tr td]
    config.action_view.sanitized_allowed_attributes = "rel"
    

    当然,如果表单输入资料的地方,只限于后台有权限可信任使用者,我们也可以完全放行不要逸出:

    <%= raw( @event.description ) %>
    

    <%= @event.description.html_safe %>
    

    自订 Helper 的技巧

    当你想要自订一个Helper组合一些标籤和变量的时候,你可能第一次会尝试这样写:

    def user_link(user)
      "<div>" +
        link_to(user.name, user_path(user)) + "<br>" + user.description +
      "</div>"
    end
    

    这里我们试图组合一个字串是<div>包超连结和user.description变量。不过输出在画面上的时候,Rails还是会做了HTML逸出,造成显示不正确,变成了:

    &lt;div&gt;&lt;a href=&quot;/conferences/ae98a23f8fa23a3b060f&quot;&gt;foo&lt;/a&gt;&lt;br&gt;bar&lt;/div&gt;
    

    这是因为Rails默认会将字串判断成尚未逸出,如果一个未逸出的字串跟一个逸出的安全字串相加,就会脏掉也变成一个未逸出的字串。

    为了正确显示,接下来你可能会尝试关闭HTML逸出:

    def user_link(user)
      str = "<div>" +
        link_to(user.name, conference_path(user)) + "<br>" + @event.description +
      "</div>"
    
      str.html_safe # 或 raw(str)
    end
    

    这样画面上就显示正确了。不过这却是一个错误的作法,因为让整个str不要逸出,却让其中的@event.description变成一个安全上的漏洞。

    一个办法是我们小心翼翼的帮每个不用逸出的字串加上:

    def user_link(user)
      safe_join([ "<div class='user'>".html_safe,
        link_to(user.name, conference_path(user)),
        "<br>".html_safe,
        @event.description,
        "</div>".html_safe])
    end
    

    另一个比较漂亮程式化的作法,则是善用Rails内建的contenttagtag方法来产生_HTML

    def user_link(user)
      content_tag(:div,
          link_to(user.name, conference_path(user)) + tag(:br) + @event.description,
        :class => 'user' )
    end
    

    其中contenttag(:div, "YOUR_CONTENT", :class => "YOUR_CSS_CLASS" )可以产生<div class="YOUR_CSS_CLASS"> YOUR_CONTENT </div>的_HTMLtag(:br)会产生<br />

    表单辅助方法

    对网页应用程式来说,表单是非常重要的用户输入接口。Rails在这方面也提供了很多好用的Helper方法。基本上,Rails处理表单分成两种类型:

    一种是对应到Model物件的新增、修改,我们会使用formfor这个_Helper。它的好处在于透过传入Model物件,可以在修改的时候自动帮你将默认值带入。例如我们已经在Part1使用过的event表单:

    <%= form_for @event do |f| %>
        <%= f.text_field :name %>
        <%= f.submit %>
    <% end %>
    

    另一种是就是没有对应Model的表单,我们使用form_tag这个方法。例如:

    <%= form_tag "/search" do %>
        <%= text_field_tag :keyword %>
        <%= submit_tag %>
    <% end %>
    

    formfor有些类似,但是其中不需要传_Block变量f,其中的字段Helper需要多加_tag结尾。不像formfor的字段名称一定要是_Model的属性之一,在form_tag之中的字段名称则完全不受限。

    几个常用的表单字段辅助方法:

    • label
    • text_field
    • text_area
    • radio_button
    • check_box
    • file_field
    • select
      • 使用 select 有一个坑:如果你要加 class 的话,必须这样写 f.select :xxx, {}, :class => "your-class-name",多出来的 {} 是因为这个 select API 的最后两个参数都是 Hash,必须多包一个 {} 才能让 :class 挤到最后的参数去。
    • select_date
    • select_datetime
    • hidden_field
    • submit

    搭配ActiveRecord关联的辅助方法:

    • collection_select
    • collection_radio_buttons
    • collection_check_boxes

    一些HTML5的辅助方法

    • color_field
    • date_field
    • email_field
    • month_field
    • number_field
    • url_field
    • range_field
    • search_field

    以下这些属性可以设成true加到表单方法方法里面

    disabled readonly multiple checked autobuffer autoplay controls loop selected hidden scoped async defer reversed ismap seamless muted required autofocus novalidate formnovalidate open pubdate itemscope allowfullscreen default inert sortable truespeed typemustmatch
    

    例如

    <%= f.text_field :name, :required => true, :autofocus => true, :placeholder => "Please enter your name" %>
    

    会产生出这样的HTML5标籤,浏览器会检查必填、有PlaceholderAuto focus

    <input placeholder="Please enter your name" required="required" autofocus="autofocus" class="form-control" type="text" name="event[name]" id="event_name">
    

    使用form_for时,其中的字段必须是Model有的属性,那如果数据库没有这个字段呢?这时候你依需要在Model程式中加上存取方法,例如:

    class Event < ApplicationRecord
    
      #...
      def custom_field
          # 根据其他属性的值或条件,来决定这个字段的值
      end
    
      def custom_field=(value)
          # 根据value,来调整其他属性的值
      end
    
    end
    

    这样就可以在form_for里使用custom_field了。

    <%= form_for @event do |f| %>
        <%= f.text_field :custom_field %>
        <%= f.submit %>
    <% end %>
    

    记得把:customfield也加到_Strong Parameters清单里,这样按下送出后,就可以跟着@event本来的字段一起处理了。

    资料验证错误时的处理

    <%= form_for(@person) do |f| %>
      <% if @person.errors.any? %>
        <div id="error_explanation">
          <h2><%= pluralize(@person.errors.count, "error") %> prohibited this person from being saved:</h2>
    
          <ul>
          <% @person.errors.full_messages.each do |msg| %>
            <li><%= msg %></li>
          <% end %>
          </ul>
        </div>
      <% end %>
    
      <%= f.text_field :name %>
      <%= f.submit %>
    <% end %>
    

    透过检查我们可以把所有的错误讯息显示出来。除了这种作法,我们也可以把错误讯息放在输入框的旁边:

    <%= form_for(@person) do |f| %>
      <%= f.text_field :name %>
      <% if @person.errors[:name].presence %>
          <%= @person.errors[:name].join(", ") %>
      <% end %>
    
      <%= f.submit %>
    <% end %>
    

    更多线上资源