在Rails中使用液体标签的动态用户内容的方法

248 阅读3分钟

当构建接受用户生成的内容的功能时,你可能需要根据用户指定的内容来显示动态内容。想象一下,当用户邀请某人加入他们的账户时,你想让用户能够定制从你的应用程序中发送的欢迎信息。

Rails程序员非常熟悉用动态文本片断编写内容:我们在编写视图模板时一直在这样做。但我们不想让用户编写ERB或HAML字符串,并在我们的应用中执行它们。这既是一个巨大的安全风险,也对用户来说,不得不学习完整的编程语言来改变一些文字,这并不太友好。

另一种方法是使用一些 "神奇的字符串",你可以把特殊字符串的值换成$NAME*|EMAIL|* 。这些有时被称为 "合并标签"。但是实施这种方法通常会导致gsub 、正则表达式和奇怪的边缘案例的汤。

建立这个功能的一个伟大的选择是使用Liquid模板。Liquid是一种非常精简的模板语言,Shopify最常使用它,允许店主定制他们的电子商务商店。

Liquid模板解决了所有这些问题。

它有安全性,因为你可以控制渲染时使用的上下文--这是一种华丽的说法,Liquid模板只能访问你明确传入的数据。语法是最小的,而且它为常见的操作(默认值、大写字母、日期格式等)提供了内置函数。而且该库对输入进行解析,不依赖脆弱的正则表达式--而不是随机破坏,你可以捕捉到无效的语法并进行适当处理。

使用方法

liquid gem添加到你的项目中。

基本操作是两个步骤。

# Create a template from user-input
template = Liquid::Template.parse("Hi {{ customer.name }}!")

# Render the template with the dynamic data
template.render({"customer": {"name": "Matt"}})
#=> "Hi Matt!"

在实践中,你会想在自己的Rails视图助手中加入一些便利的功能。

  • Liquid要求动态数据哈希有字符串键,而Rails应用程序通常使用符号键来表示哈希。你可以在哈希上调用Rails的deep_stringify_keys 方法来转换它们。
  • 调用render! ,而不是render ,会引发一个异常,这样你就可以退回到返回原始用户输入。
  • Liquid提供了strict_variablesstrict_filters 选项,可以将未定义的变量或过滤器变成错误。你可能希望这两个选项都是真的,这样用户就可以找出语法错误,而不是让内容默默地变成空白。

在我的项目中,我们添加了这个辅助方法。

# app/helpers/liquid_helper.rb
module LiquidHelper
  def liquid(text, context: {})
    template = Liquid::Template.parse(text)
    template.render!(context.deep_stringify_keys, {
      strict_variables: true,
      strict_filters: true
    })
  rescue Liquid::Error
    text.to_s
  end
end

然后在你的Rails应用程序的任何视图(或邮件)中,你可以调用liquid 帮助器来显示用户生成的动态内容。

@campaign = Campaign.create!(subject: "Welcome {{ customer.name }}!")
<%= liquid(@campaign.subject, context: { customer: { name: @customer.name } }) %>

注意:如果你通过ActionText 使用富文本,你需要在Liquid插值后调用html_safe ,因为输出是原始HTML。

<%= liquid(@campaign.message, context: { customer: { name: @customer.name } }).html_safe %>

这个助手通过返回原始输入来优雅地处理错误情况。

liquid("Hi {{ missing_value }}", context: {})
#=> "Hi \{\{ missing_value }}"

liquid("Hi {{ foo", context: {})
#=> "Hi \{\{ foo"

如果你在Liquid渲染上下文中使用这些模型(而不是每次都建立上下文哈希),你可能也想给你的模型添加便利方法。

class Customer < ApplicationRecord
  belongs_to :organization

  def to_liquid
    # Expose whatever fields you want to be able to use in liquid templates
    {
      name: name,
      email: email,
      company_name: organization.name
    }
  end
end
liquid("New sign up from {{ customer.company_name}}. Say hi to {{ customer.name }} <{{ customer.email }}>!", context: customer.to_liquid)
#=> New sign up from Arrows. Say hi to Matt <matt@arrows.to>!

甚至更高级的经过检验的功能

如果你想为用户提供特定的应用功能,如{{ customer | avatar_url }}{{ task.due_date | next_business_day }}{{ '#7ab55c' | color_to_rgb }} ,你可以注册你自己的过滤器。

你可以指定resource_limits ,以避免极其缓慢的插值。

这些都超出了本提示的范围,你可以自己去探索。