关于 Rails 中 time_ago_in_words 的缓存

昨天很 Happy 的把自己的个人项目部署起来了,结果后来发现有个地方出了点问题。就是在生产环境下,views中使用time_ago_in_words会有问题,由于页面是缓存到redis的。这就导致我们得到的类似1分钟之前这个结果始终都是不变的,而且还会引发I18n的问题。因为不同用户使用的语言设置可能不同,如果创建者使用的英文,那么time_ago_in_words在缓存中的就是英文,而中文环境的浏览者看到的也是中文了。虽然可以通过给cache中加入locale来区分不同语言环境保存不同的cache,但是时间显示错误的这个问题还是没法解决。

后来查阅了一些资料之后,发现time_ago_in_words也确实不是best_practice。除去缓存I18n的问题之外,由于time_ago_in_words由于需要服务器来计算时间,所以也会影响性能(虽然应该不大)。因此,这种事情更适合交给前端来做。很多时候缓存都需要和前端JS来配合达到最好的效果。一方面我们尽可能多利用混存,这样能提高服务器的相应速度。另一方面,对于页面用户逻辑较多导致不同用户之间页面差别较大的情况,如果我们对每个用户的页面分别进行缓存,这样内存一定会爆掉。比较合适的方式就是我们对整个完整的页面进行混存,然后在前端根据不同用户的不同权限用JS来调整页面的显示。

回到time_ago_in_words的解决,这里比较好的一种方式是使用time_tag这个view helper,它返回的html类似这样

<time datetime="2014-06-21T09:16:04Z" title="June 21, 2014 09:16"></time>

接着我们使用jQuery的一个叫做timeago的插件来计算距离现在的时间,我们可以自己写一个view helper

def time_ago_tag(time)
    time_tag time, data: { behaviors: 'timeago'}
end

views中,比如有一个comment,那么我们可以这样使用

<%= time_ago_tag comment.created_at %>

生成的html就是下面这样

<time data-behaviors="timeago" datetime="2014-06-21T09:16:04Z" title="June 21, 2014 09:16">about 7 hours ago</time>

这里我们用到了data-behaviors,这是html 5中新的一个属性,对我来说使用它的最大的好处就是css selector写起来会简单很多,比如我们的例子只需要这样

$('[data-behaviors=~timeago]').timeago();

这样还有一个好处就是语义上更加明确,我们只需在命名时候取一个相符合的behavior,那么以后看起来就一目了然,相比css selector易读太多。

至此,我们就将时间的渲染交给了前端,解决了时间混存的问题。但是I18n依旧有问题,因为现在都是英文了。jQuery.timeago当然考虑到这个问题了,去他们的 Git Repo 里面能找到一堆国际化的文件了,我们只需要下载jquery.timegago.zh-CN.jsvendor/assets/javascripts/locacles就可以了。

jquery.timeago.zh-CN.js
jQuery.timeago.settings.strings = {
    prefixAgo: null,
    prefixFromNow: "从现在开始",
    suffixAgo: "之前",
    suffixFromNow: null,
    seconds: "不到1分钟",
    minute: "大约1分钟",
    minutes: "%d分钟",
    hour: "大约1小时",
    hours: "大约%d小时",
    day: "1天",
    days: "%d天",
    month: "大约1个月",
    months: "%d月",
    year: "大约1年",
    years: "%d年",
    numbers: [],
    wordSeparator: ""
};

但是注意我们没必要在application.js中引入这个文件,因为它不是必须的,如果用户使用的是英文环境,那么是不需要引入jquery.timeago.zh-CN.js文件的。

所以我们需要新加一个编译的文件,在app/assets/javascripts目录下新建locales/zh-CN.js

zh-CN.js
//= require locales/jquery.timeago.zh-CN

接着在production.rb中设置要增加的编译文件

config.assets.precompile += %w(locales/zh-CN.js)

最后我们在application.html.erbhead部分判断I18n.locale,如果不是en的话我们就引入相应的locale文件,比如

<% if I18n.locale != 'en' %>
<%= javascript_include_tag "locales/#{I18n.locale}" %>
<% end %>

至此,我们关于time_ago显示的过程中出现的缓存I18n问题都解决了。