Day21. 用 Rails helper 省去更多开发时间

身为一介Rails工程师,我们必须懂一些Rails特化的写法,可以让我们写code 更方便、优雅。

link_to

link_toRails工程师必须会的helper之一,它的功用为制造超连结,并且搭配btn btn-primary等等的bootstrap class,就会像是按钮一般,以下为按下按下重新动作的按钮。

link_to '重新动作', update_invoice_status_manually_admin_return_order_path(return_order),
                   class: 'ml-4 btn btn-primary btn-sm', method: :post, style: 'height: 32px',
                   data: { confirm: "请注意!点击「确定」後状态将切换,无法复原!" }

该按钮的身份为连结,按下去会触发/admin/return_orders/102/update_invoice_status_manually,并且方法为post的路径。

<a class="ml-4 btn btn-primary btn-sm" style="height: 32px" data-confirm="请注意!点击「确定」後状态将切换,无法复原!" rel="nofollow" data-method="post" href="/admin/return_orders/102/update_invoice_status_manually">
  重新折让
</a>

data-confirm

当我们使用data-confirm,Rails 就会自动生成 确定/取消Javascript 对话窗。

= link_to '顾客亲取', "#", class: 'ml-4 btn btn-primary btn-sm', method: :post,
          data: { confirm: "是否确定顾客亲自取货\n..." }

当我们按下取消,就会自动不执行动作。这块在Rails 就已经帮我们做好,毋需写额外的程序码处理

https://ithelp.ithome.com.tw/upload/images/20210917/20115854pt3tGX0ccv.png

disabled with

这也是Rails 附赠的方法。当我们将按钮按下送出之後,在打到後端的等待时间,就会被转为载入中。

button_tag "出货", type: 'submit', class: 'btn btn-primary',
            data: { confirm: "是否确定要送出编辑?\n请注意!送出後无法复原!", disable_with: "载入中..." })

https://ithelp.ithome.com.tw/upload/images/20210917/20115854ec47igZI0s.png

data-remote=true

有了 data-remote=true 属性,表单会透过 Ajax 提交,而不是浏览器平常的提交机制。这是在 Rails 附带的一个对於打Ajax极为方便的工具。日後再介绍Stimulus的篇章会再提及

checkbox

以下为checkboxlabel 组合再一起的写法

<%= label_tag do %>
  <%= check_box_tag 'category[]', '1', false %>
  Community
<% end %>
= f.input :custom_field, :label => false do 
  = check_box_tag :some_name
= label_tag nil, class: 'mb-0' do
  = check_box_tag 'return_order[:return_failed]', 'yes', false, class: 'mx-1'
  = tag.strong '全数不同意退货', style: 'font-size: 14px;'

tag

tag 是一个非常实用的 helper

# 用法
tag.<tag name>(optional content, options)

下列为底层的code

def tag(name = nil, options = nil, open = false, escape = true)
  if name.nil? 
    tag_builder
  else
    "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
  end
end

以下为 tag 的用法对应上方底层程序码的if判断是内的用法 ➡️ tag_builder

https://ithelp.ithome.com.tw/upload/images/20210917/20115854ZrJtO5efjf.png

底层使用的else用法为Rails的旧写法,会被未来的Rails版本所弃用deprecated

https://ithelp.ithome.com.tw/upload/images/20210917/20115854cOgEsgh9Ux.png

⭐️ tag helper可以用来写table

table.table.table-hover.table-striped.table-sm
  thead.thead
    tr
      th(colspan="7" class="table-active" style="font-size:16px") 订单列表
    tr
      th.col-2 订单编号
      th.col-2 订单品牌
      th.col-1 订单状态
      th.col-1 物流状态
      th.col-1 商品数量
      th.col-2 出货店柜
      th.col-3
  = tag.tbody do
    - order.sub_orders.each do |sub_order|
      = tag.tr do
        = tag.td do
          = link_to sub_order.number, admin_sub_order_path(sub_order)
        = tag.td sub_order.brand.title
        = tag.td process_i18n(t("sub_orders.status.#{sub_order.status}"))
        = tag.td process_i18n(t("sub_orders.shipping_status.#{sub_order.shipping_status}"))
        = tag.td sub_order.order_items.count
        = tag.td [sub_order.brand.title, sub_order.store.title_zh].compact.join('-')
        = tag.td do
          - if related_brands(sub_order)
            - if sub_order.return_order
              = link_to '退货明细', admin_return_order_path(sub_order.return_order),
                        class: 'btn btn-primary btn-sm mr-2', target: '_blank'
            = link_to '子订单明细', admin_sub_order_path(sub_order),
                      class: 'btn btn-primary btn-sm', target: '_blank'

⭐️ 基本排版的时候也会用到tag helper

.container-xxl
  .row
    .col-12.col-lg-6
      = card do
        = card_header(title: "订单状态")
        = card_body do
          = grid_div do
            = tag.strong '订单状态'
            = tag.div process_i18n(t("orders.status.#{order.status}"))
            = tag.strong '订单编号'
            = tag.div order.number
            = tag.strong '购买日期'
            = tag.div order.created_at.strftime('%F %T')
      = card do
        = card_header(title: "配送资讯")
        = card_body do
          = grid_div do
            = tag.strong '配送方式'
            = tag.div process_i18n t("orders.shipping_type.#{order.shipping_type}")
            = tag.strong '配送地址'
            = tag.div order.address_info
            = tag.strong '收件人姓名'
            = tag.div order.receiver_name
            = tag.strong '电话'
            = tag.div order.receiver_phone

tag.div可以搭配区块使用

= grid_div do
  = tag.strong '订单异动纪录'
  = tag.div do
    = modified_log(order.versions, modified_order_genre, 'orders')
  = tag.strong '发票异动纪录'
  = tag.div do
    = modified_log(order.invoice&.versions, modified_invoice_genre, 'invoices')

tag.div 可以搭配 data, style 使用。

= tag.div class: 'form_group', data: { 'admin--order_target': 'storeBlock' }, 
          style: "display: #{f.object.cvs? ? 'block' : 'none'};"

_tag

_tag,这种以tag作为後缀的helper也很好用,以下举几个例子带读者来认识

⭐️ button_tag 的用法即为按钮,一共有reset, button, reset

button_tag '清除重填', 
  type: 'reset', form: 'export-orders', 
  class: 'btn btn-secondary btn-sm ml-2 mb-2',
  onchange: "handleChange(this)",
  data: { action: 'click->orders#reset', 'orders-target': 'resetBtn' }

⭐️ content_tag 的用法与上面所提到的 tag 用法相同。下列两两一组的意思一样

# span
content_tag(:span, '新增')
tag.span '新增'

# strong
content_tag(:strong, '母订单')
tag.strong '母订单'

# div
content_tag(:div, class: 'modal-body') { yield if block_given? }
tag.div class: 'modal-body' { yield if block_given? }

⭐️ check_box_tagcheckbox

label_tag nil, class: 'mb-0' do
  # check_box_tag <name 属性>, <value>, [填满/不填满], class|data|style...
  check_box_tag('return_order[disapprove_all]', 'yes', false, class: 'mx-1',
     data: { action: "admin--return-order-apply-form#disapproveAll" }) +
  tag.strong('全数不同意退货', style: 'font-size: 14px;')  
end
<label class="mb-0">
  <input type="checkbox" name="return_failed" id="return_failed" 
         value="yes" class="mx-1"
         data-action="admin--return-order-apply-form#disapproveAll">
  <strong style="font-size: 14px;">全数不同意退货</strong>
</label>

https://ithelp.ithome.com.tw/upload/images/20210917/20115854mHeAWX6hmJ.png

⭐️ select_tag 为下拉式选单

在我的专案里面,当name 属性空值,代表为为使用 Javascript 传值用,因此不用name 属性进行传值

# select_tag(<name 属性>, 选项, class|data|style...)
select_tag(nil, options_for_select(options, '请选择动作'), class: 'form-control')

options_for_select 为自己包的方法

https://ithelp.ithome.com.tw/upload/images/20210918/20115854AOnpM3Dpjk.png

⭐️ file_field_tag 为上传档案的输入框,若没有特别调整样式,预设长相如下。

https://ithelp.ithome.com.tw/upload/images/20210918/20115854tSaT6Kdpeq.png

⭐️ submit_tagbutton_tag type: 'submit' 意思相同

⭐️ date_field_tag

#===== 范例1
date_field_tag 'date', nil, class: 'special_input'
#=> <input class="special_input" id="date" name="date" type="date" />

#===== 范例2
date_field_tag(
  'ransack_search[created_at_gteq]', '2021-09-18', 
   class: 'form-control', style: 'height: 100%; max-width: 220px;',
   disabled: options[:disabled] || false)

下列的开始时间至结束时间的样式,可以参考前一天提到的input group,而date_field_tag 对应的表单内容 ➡️ <input type="date" name="..."></input>

https://ithelp.ithome.com.tw/upload/images/20210918/201158540W6TbZFINB.png

我们在Rails填入的时间要是2021-09-18的形式,或者类别为Date

Day7 提到了一些时间相关的用法,读者们可以参考

# 可以用
Date.today - 1.year  # 开始时间 Fri, 18 Sep 2020
Date.today + 1.day   # 结束时间 Sun, 19 Sep 2021
(Date.today + 1.day).class  #=> Date

# 不能用
1.year.ago      # 去年
1.year.from_now # 明年
1.year.from_now.class #=> ActiveSupport::TimeWithZone

# 可以用
1.year.ago.strftime('%F') # 去年

结语

Rails 的 helper 很多,以上为我常用的写法。基本上上面的用法就已经很够用了,所以也把我自己常用的基本helper跟大家分享,表单的部分会在 Day23, Day24 分享。

明天要介绍如何自定义 helper

参考资料


<<:  GCP 挂载X磁碟X快照

>>:  电子书阅读器上的浏览器 [Day21] 翻译功能 (III) Google Translate

追求JS小姊姊系列 Day27 -- 从哪里BOM出来的青梅竹马!

前情提要: 工具人揭露了残酷事实,JS有一个青梅竹马的存在! 我: 到底是怎麽样的人物? 工具人们:...

DAY 15 Big Data 5Vs – Variety(速度) Glue(3) Glue Studio

在资料分析的过程中,花最多时间的事就是在理出资料处理的逻辑,要花很多时间与资料互动,就像第二天提到资...

[Day12] Storybook - Writing Docs

DocsPage DocsPage 是由 Storybook Docs 所提供的页面,无需任何的设定...

自动化测试,让你上班拥有一杯咖啡的时间 | Day 18 - 如何提交表单

此系列文章会同步发文到个人部落格,有兴趣的读者可以前往观看喔。 今天要跟大家分享如何测试提交表单,...

【从零开始的Swift开发心路历程-Day14】打造自己的私房美食名单Part3(完)

昨天已经能让TableViewCell显示餐厅资料了 但....好像有点单调,让我们来加点餐厅的图片...