@ damien-mathieu имеет четкий ответ для отображения локализованных дат с помощью I18n.localize
, и его комментарий вызывает важное предостережение: это нарушает ввод текста. С тех пор рельсы дают нам хорошее решение.
Начиная с Rails 5 , вы можете использовать
API-атрибуты Rails для настройки преобразования пользовательского ввода в значение модели или базы данных. На самом деле, это было доступно в Rails 4.2 , но не полностью документировано.
Благодаря значительным усилиям Шона Гриффина все типы моделей теперь определены как ActiveRecord::Type
объекты. Это определяет единственный источник правды для того, как обрабатывается атрибут. Тип определяет способ сериализации (от типа ruby до типа базы данных), десериализации (от типа базы данных до типа ruby) и приведения (от ввода пользователя к типу ruby). Это большое дело, потому что возиться с этим раньше было минным полем, которого разработчики должны избегать.
Сначала просмотрите документы attribute
, чтобы понять, как переопределить тип атрибута. Вам, вероятно, нужно прочитать документы, чтобы понять этот ответ.
Как Rails преобразует атрибуты
Вот краткий обзор API атрибутов Rails. Вы можете пропустить этот раздел, но тогда вы не будете знать, как это работает. Что это весело?
Понимание того, как 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
# 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
Посмотрите, как я только что скопировал код рельсов и сделал небольшую настройку. Легко. Возможно, вы захотите улучшить это с помощью вызова 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 %>