web-dev-qa-db-fra.com

Comment puis-je faire Rails 3 localiser mes formats de date?

Je travaille sur un projet Rails 3 où il y a de la place pour la date dans un formulaire. Le champ de texte avec la date utilise un sélecteur de date donc il n'y a pas de souci quant à la date entrée dans un format incorrect, cependant la date est affichée au format: db (par exemple 2010-01-21).

(Remarque: ceci est spécifiquement dans les champs de formulaire - par exemple <%= f.text_field :publish_date %>, qui devrait utiliser automatiquement: le format par défaut, et ne devrait pas avoir besoin d'être fourni avec une valeur)

J'ai essayé d'ajouter un environnement local personnalisé qui a la configuration de date suivante:

date:
    formats:
      # Use the strftime parameters for formats.
      # When no format has been given, it uses default.
      # You can provide other formats here if you like!
      default: "%d/%m/%Y"
      short: "%b %d"
      long: "%B %d, %Y"

Et puis définir mes paramètres régionaux à ceci (config.i18n.default_locale = "en-AU") mais cela ne semble pas prendre et ça devient assez frustrant.

L'application prendra éventuellement en charge un certain nombre de paramètres régionaux, donc la mise en place d'un initialiseur pour remplacer les formats de date au démarrage de l'application n'est pas vraiment appropriée, et je sais que cela devrait fonctionner - je suppose que je ' ai manqué quelque chose ici.

Le fichier de paramètres régionaux est: config/locales/en-AU.yml et dans mon application.rb j'inclus:

config.i18n.load_path += Dir[Rails.root.join("config", "locales", "*.yml").to_s]
config.i18n.default_locale = "en-AU"

dans mon fichier application.rb.

41
Matthew Savage

Lors de l'affichage d'une date, vous pouvez utiliser I18n.l

Vous feriez donc:

I18n.l @entry.created_at

Et si vous voulez changer son format:

I18n.l @entry.created_at, :format => :short

Le internationalisation Rails guide documente cela.

85
Damien MATHIEU
7
taelor

@ damien-mathieu a une réponse solide pour afficher des dates localisées avec I18n.localize, et son commentaire soulève une mise en garde importante: cela rompt les entrées de texte. Depuis lors, Rails nous donne une belle solution.

À partir de Rails 5 , vous pouvez utiliser Rails attributes API pour personnaliser la façon dont l'entrée utilisateur est transformée en valeur de modèle ou de base de données . En fait, c'était disponible dans Rails 4.2 , mais pas complètement documenté.

Grâce à Sean Griffin's efforts considérables, tous les types de modèles sont désormais définis comme ActiveRecord::Type objets. Cela définit une seule source de vérité pour la façon dont un attribut est géré. Le type définit la façon dont l'attribut est sérialisé (d'un type Ruby à un type de base de données), désérialisé (d'un type de base de données à un Ruby) et cast (de l'entrée utilisateur à un Ruby). C'est un gros problème, car jouer avec cela était un champ de mines de cas spéciaux que les développeurs devraient éviter.

Commencez par parcourir les documents attribute pour comprendre comment remplacer le type d'un attribut. Vous aurez probablement besoin de lire la documentation pour comprendre cette réponse.

Comment Rails transforme les attributs

Voici une présentation rapide de l'API Attributs Rails. Vous pouvez ignorer cette section, mais vous ne saurez pas comment cela fonctionne. Quel plaisir est-ce?

Comprendre comment Rails gère les entrées utilisateur pour votre attribut nous permettra de remplacer une seule méthode au lieu de créer un type personnalisé plus complet. Cela vous aidera également à écrire un meilleur code, car le code de Rails est assez bon .

Puisque vous n'avez pas mentionné de modèle, je suppose que vous avez un Post avec un :publish_date attribut (certains préféreraient le nom :published_on, mais je m'égare).

Cest quoi ton type?

Découvrez quel type :publish_date est. Peu importe qu'il s'agisse d'une instance de Date, nous devons savoir ce que type_for_attribute renvoie:

Cette méthode est la seule source d'informations valide pour tout ce qui concerne les types d'attributs d'un modèle.

$ Rails c
> post = Post.where.not(publish_date: nil).first
> post.publish_date.class
=> Date
> Post.type_for_attribute('publish_date').type
=> :date

Maintenant, nous connaissons le :publish_date l'attribut est un :date type. Ceci est défini par ActiveRecord :: Type :: Date , qui étend ActiveModel :: Type :: Date , qui étend ActiveModel :: Type :: Value . J'ai lié à Rails 5.1.3, mais vous voudrez lire la source de votre version.

Comment l'entrée utilisateur est-elle transformée par ActiveRecord :: Type :: Date?

Ainsi, lorsque vous définissez :publish_date, la valeur est passée à cast , qui appelle cast_value . Puisque l'entrée de formulaire est une chaîne, elle essaiera fast_string_to_date puis fallback_string_to_date qui utilise Date._parse .

Si vous vous perdez, ne vous inquiétez pas. Vous n'avez pas besoin de comprendre le code de Rails pour personnaliser un attribut.

Définition d'un type personnalisé

Maintenant que nous comprenons comment Rails utilise l'API d'attributs, nous pouvons facilement créer le nôtre. Créez simplement un type personnalisé pour remplacer cast_value pour attendre des chaînes de date localisées:

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

Voyez comment je viens de copier le code de Rails et de faire un petit Tweak. Facile. Vous pourriez vouloir améliorer cela avec un appel à super et déplacer le :short formate une option ou une constante.

Enregistrez votre type afin qu'il puisse être référencé par un symbole:

# config/initializers/types.rb
ActiveRecord::Type.register(:localized_date, LocalizedDate)

Remplacez le :publish_date tapez avec votre type personnalisé:

class Post < ApplicationRecord
  attribute :publish_date, :localized_date
end

Vous pouvez maintenant utiliser des valeurs localisées dans vos entrées de formulaire:

# 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 %>
7
Anson

Ce que j'ai trouvé être la meilleure solution est la suivante:

  • Je localise les formats de date dans mon fichier de paramètres régionaux comme vous le faites
  • Dans mes formulaires, je localise la date en définissant la valeur directement

<%= f.text_field :publish_date, :value => (@model.publish_date.nil? ? nil : l(@model.publish_date)) %>

Ce n'est malheureusement pas parfait, mais au moins de cette façon, je peux utiliser mon formulaire pour les enregistrements nouveaux et existants. L'application restera également compatible avec plusieurs paramètres régionaux par rapport à la modification du format par défaut avec les initialiseurs. Si vous voulez vous conformer pleinement à DRY vous pouvez toujours écrire un assistant personnalisé.

4
mkon

Vous pouvez remplacer vos getters pour le :publish_date attribut.

c'est-à-dire dans votre modèle:

def date( *format)
    self[:publish_date].strftime(format.first || default)
end

et à votre avis, vous pouvez faire

@income.date("%d/%m/%Y")

ou

@income.date

Cela obligerait strftime à utiliser la chaîne de format transmise à moins qu'elle ne soit nulle, auquel cas elle reviendrait à votre chaîne par défaut.

Remarque: J'ai utilisé l'opérateur splat pour ajouter un support pour obtenir la date sans argument. Il peut y avoir une façon plus propre de le faire.

1
aguazales