@damien-mathieu 对于使用I18n.localize 显示本地化日期有一个可靠的答案,他的评论提出了一个重要的警告:这会破坏文本输入。从那时起,rails 为我们提供了一个很好的解决方案。
从 Rails 5 开始,您可以使用
Rails attributes API 自定义用户输入如何转换为模型或数据库值。实际上,它是available in Rails 4.2,只是没有完整记录。
通过Sean Griffin's 相当大的努力,现在所有模型的类型都定义为ActiveRecord::Type 对象。这定义了如何处理属性的单一事实来源。类型定义属性如何序列化(从 ruby 类型到数据库类型)、反序列化(从数据库类型到 ruby 类型)和强制转换(从用户输入到 ruby 类型)。这是一件大事,因为过去曾是开发人员应避免的特殊情况的雷区。
首先,浏览attribute 文档以了解如何覆盖属性的类型。您可能需要阅读文档才能理解此答案。
Rails 如何转换属性
这里是 Rails 属性 API 的快速浏览。你可以跳过这一部分,但你不会知道这些东西是如何工作的。有什么好玩的?
了解 Rails 如何处理您的属性的用户输入将使我们只覆盖一种方法,而不是制作更完整的自定义类型。它还可以帮助您编写更好的代码,因为 rails 的代码非常好。
由于您没有提及模型,我假设您有一个带有 :publish_date 属性的 Post(有些人更喜欢名称 :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
# Convert localized date string to Date object. This takes I18n formatted date strings
# from user input and casts them back to Date objects.
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 格式移动到一个选项或常量。
注册您的类型,以便它可以被符号引用:
# config/initializers/types.rb
ActiveRecord::Type.register(:localized_date, LocalizedDate)
用您的自定义类型覆盖 :publish_date 类型:
class Post < ApplicationRecord
attribute :publish_date, :localized_date
end
现在您可以在表单输入中使用本地化值:
# app/views/posts/_form.html.erb
<%= form_for(@post) do |f| %>
<%= f.label :publish_date %>
<%= f.text_field :publish_date, value: (I18n.localize(value, format: :short) if value.present?) %>
<% end %>