使用 Chinese_pinyin + Friendly_id 为中文标题生成 Slug

在许多项目中,我们可能都会遇到需要为数据生成 slug 的场景,这些场景类似于:

  • 基于商品名称生成 slug
  • 基于文章标题生成 slug

至于为什么需要生成 slug,而不是使用比如 Rails 中默认自增的主键也就是数据的 id,原因其实很简单:

  • 使用自增 id 容易暴露数据,比如通过订单 id 可能导致遍历所有订单,不信,你看这里就有个例子
  • 增加 URL 友好性,/products/18376 这样的链接肯定没有比 /products/apple-watch-gold 这样的链接更招人喜欢

friendly_id

friendly_id 是用来生成 slug 的 ruby gem,假设我们有一个产品模型 Product,使用 friendly_id 为商品名称(name)生成 slug 的示例代码如下:

1
2
3
class Product < ActiveRecord::Base
  friendly_id :name, use: :slugged
end

上面的代码理论上来说已经完成我们所需要的工作了,但是如果 title 包含中文的话,生成的 slug 就有点类似 30f175f4-1e56-4e3a-823d-a7c1a5d32b29 这样的乱码了,实际上这个 slug 对应的原来的 title 是 测试产品。这样的 slug 虽然避免了自增 id 的弊端,但是却丧失了友好性。如果 slug 能够基于汉语拼音生成,岂不更好?

自己控制 slug 生成方式

阅读 friendly_id 的源码可以找到以下代码:

1
2
3
4
5
6
7
8
9
10
11
module FriendlyId
  module Slugged
    # ...

    def normalize_friendly_id(value)
      value.to_s.parameterize
    end

    # ...
  end
end

这段代码便是 friendly_id 基于输入生成 slug 的核心代码,使用 ActiveSupport 扩展后的 String 类的 parameterize 方法,此方法会将除了英文字母、数字、短横线以及下划线之外的字符转换为 -,所以不适用于中文的情况,我们需要重写该方法,以满足我们的需求。

中文拼音利器——chinese_pinyin

中文生成中文拼音的工具,我选择了黄志敏先生写的 chinese_pinyin 这个 gem,推荐理由就是简单够用。

以下是单独使用这个 gem 时的示例:

1
2
3
4
2.2.0 :009 > Pinyin.t("中国人")
 => "zhong guo ren"
2.2.0 :010 > Pinyin.t("Hello, 李雷")
 => "Hello  li lei"

组装!!!

根据 friendly_id 的注释,如果你只需要为单独一个 model 定制 slug 的生成逻辑,那么建议你只在相关的 model 中定义同名方法即可。但是由于我是需要为多个 model 定制中文的 slug 生成逻辑,所以我选择了直接重定义 FriendlyId::Slugged 模块中的这个方法:

1
2
3
4
5
6
7
8
9
# config/initializers/friendly_id/slugged.rb
module FriendlyId
  module Slugged
    # 重定义 friendly_id 方法,实现 slug 从中文到拼音,非中文不受影响
    def normalize_friendly_id(value)
      Pinyin.t(value.to_s).parameterize
    end
  end
end

这样的定义方式使得新的 normalize_friendly_id 方法对所有依赖 friendly_id 的代码都生效。

最后通过新的方法为我们的产品生成新的 slug,现在“测试产品”得到的 slug 变为 ce-shi-chan-pin 了:

1
product.update(slug: nil)  # 显式清空 slug, friendly_id 在 save 时会自动重新生成 slug

最后产品的链接已变为 /products/ce-shi-chan-pin,比起 /products/30f175f4-1e56-4e3a-823d-a7c1a5d32b29,可真是叫人心旷神怡。

Comments