@damien-mathieu提供了一个稳定的方法来使用I18n.localize
显示本地化日期,他的评论提到了一个重要的注意点:这会破坏表单文本输入。自那时以来,Rails为我们提供了一个好的解决方案。
从Rails 5开始,您可以使用Rails属性API来自定义用户输入如何转换为模型或数据库值。实际上,在Rails 4.2中就已经可用,只是没有完全记录。
通过Sean Griffin的大力支持,所有模型类型现在都被定义为ActiveRecord::Type
对象。这定义了一个处理属性的真正来源。该类型定义了属性如何序列化(从ruby类型到数据库类型),反序列化(从数据库类型到ruby类型)和强制类型转换(从用户输入到ruby类型)。这非常重要,因为之前搞乱了开发人员应该避免的特殊情况。
首先,快速浏览attribute
文档,了解如何覆盖属性的类型。您可能需要阅读文档才能理解本答案。
Rails如何转换属性
这里是Rails属性API的快速介绍。您可以跳过此部分,但那样您就不会知道这些东西是如何工作的。有什么乐趣呢?
了解Rails如何处理属性的用户输入将使我们只覆盖一个方法而不是创建更完整的自定义类型。它还将帮助您编写更好的代码,因为Rails的代码非常好。
由于您没有提到模型,我假设您有一个名为Post
的模型,其中有一个:publish_date
属性(有些人可能更喜欢名称:published_on
,但我离题了)。
你的类型是什么?
找出:publish_date
的类型。我们不关心它是Date
的实例,我们需要知道type_for_attribute返回的内容:
该方法是与模型属性类型相关的任何信息的唯一有效来源。
$ rails c
> post = Post.where.not(publish_date: nil).first
> post.publish_date.class
=> Date
> Post.type_for_attribute('publish_date').type
=> :date
现在我们知道
:publish_date
属性是一个
:date
类型。这是由
ActiveRecord :: Type :: Date定义的,它扩展了
ActiveModel :: Type :: Date ,该类又扩展了
ActiveModel :: Type :: Value。我链接了rails 5.1.3版本,但您需要阅读您版本的源代码。
ActiveRecord :: Type :: Date如何转换用户输入?
因此,当您设置:publish_date
时,该值将传递给cast,该方法调用cast_value。由于表单输入是字符串,因此它将尝试fast_string_to_date,然后尝试 fallback_string_to_date将使用Date._parse。
如果您感到困惑,不要担心。您无需理解rails的代码即可自定义属性。
定义自定义类型
现在我们了解了Rails如何使用属性API,我们可以轻松地创建自己的自定义类型。只需创建一个自定义类型以覆盖cast_value
以期望本地化日期字符串:
class LocalizedDate < ActiveRecord::Type::Date
private
def cast_value(value)
if value.is_a?(::String)
return if value.empty?
format = I18n.translate("date.formats.short")
Date.strptime(value, format) rescue nil
elsif value.respond_to?(:to_date)
value.to_date
else
value
end
end
end
看我刚刚是如何复制Rails的代码并进行小调整的。很简单。你可能想通过调用super
并将:short
格式移动到选项或常量中来改进它。
注册您的类型,以便可以通过符号引用:
ActiveRecord::Type.register(:localized_date, LocalizedDate)
使用自定义类型覆盖:publish_date
类型:
class Post < ApplicationRecord
attribute :publish_date, :localized_date
end
现在您可以在表单输入中使用本地化值:
<%= form_for(@post) do |f| %>
<%= f.label :publish_date %>
<%= f.text_field :publish_date, value: (I18n.localize(value, format: :short) if value.present?) %>
<% end %>