[ruby-on-rails] Comment puis-je définir des valeurs par défaut dans ActiveRecord?



11 Answers

Nous mettons les valeurs par défaut dans la base de données via les migrations (en spécifiant l'option :default sur chaque définition de colonne) et laissons Active Record utiliser ces valeurs pour définir la valeur par défaut pour chaque attribut.

À mon humble avis, cette approche est alignée avec les principes de AR: convention sur la configuration, DRY, la définition de la table conduit le modèle, et non l'inverse.

Notez que les valeurs par défaut sont toujours dans le code de l'application (Ruby), mais pas dans le modèle mais dans la (les) migration (s).

Question

Comment puis-je définir la valeur par défaut dans ActiveRecord?

Je vois un article de Pratik qui décrit un morceau de code laid et compliqué: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

class Item < ActiveRecord::Base  
  def initialize_with_defaults(attrs = nil, &block)
    initialize_without_defaults(attrs) do
      setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
        !attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
      setter.call('scheduler_type', 'hotseat')
      yield self if block_given?
    end
  end
  alias_method_chain :initialize, :defaults
end

J'ai vu les exemples suivants googler:

  def initialize 
    super
    self.status = ACTIVE unless self.status
  end

et

  def after_initialize 
    return unless new_record?
    self.status = ACTIVE
  end

J'ai aussi vu des gens le mettre dans leur migration, mais je préfère le voir défini dans le code du modèle.

Existe-t-il une méthode canonique pour définir la valeur par défaut des champs dans le modèle ActiveRecord?




Bien que cela soit déroutant et gênant dans la plupart des cas, vous pouvez également utiliser :default_scope . Découvrez le commentaire de Squil ici .




Les gars de Phusion ont un joli plugin pour ça.




utilise default_scope dans les rails 3

api doc

ActiveRecord masque la différence entre la valeur par défaut définie dans la base de données (schéma) et la valeur par défaut définie dans l'application (modèle). Au cours de l'initialisation, il analyse le schéma de la base de données et note les valeurs par défaut qui y sont spécifiées. Plus tard, lors de la création d'objets, il affecte les valeurs par défaut spécifiées par le schéma sans toucher à la base de données.

discussion




class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end



J'ai trouvé que l'utilisation d'une méthode de validation offre beaucoup de contrôle sur la définition des valeurs par défaut. Vous pouvez même définir des valeurs par défaut (ou une validation échouée) pour les mises à jour. Vous définissez même une valeur par défaut différente pour les insertions et les mises à jour si vous le souhaitez vraiment. Notez que la valeur par défaut ne sera pas définie jusqu'à #valid? est appelé.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

En ce qui concerne la définition d'une méthode after_initialize, des problèmes de performances peuvent survenir, car after_initialize est également appelée par chaque objet renvoyé par: find: guides.rubyonrails.org/…




C'est ce que les constructeurs sont pour! Remplacer la méthode d' initialize du modèle.

Utilisez la méthode after_initialize .




Premières choses d'abord: je ne suis pas en désaccord avec la réponse de Jeff. Il est logique lorsque votre application est petite et votre logique simple. Je suis ici pour essayer de donner un aperçu de la façon dont cela peut être un problème lors de la construction et du maintien d'une application plus grande. Je ne recommande pas d'utiliser cette approche d'abord lors de la construction de quelque chose de petit, mais de le garder à l'esprit comme une approche alternative:

Une question ici est de savoir si ce défaut sur les enregistrements est la logique métier. Si c'est le cas, je serais prudent de le mettre dans le modèle ORM. Puisque le champ ryw mentions est actif , cela ressemble à une logique métier. Par exemple, l'utilisateur est actif.

Pourquoi serais-je prudent de mettre les préoccupations d'affaires dans un modèle ORM?

  1. Cela brise la SRP . Toute classe héritant de ActiveRecord :: Base fait déjà beaucoup de choses différentes, dont la cohérence des données (validations) et la persistance (sauvegarde). Mettre la logique métier, aussi petite soit-elle, avec AR :: Base rompt la SRP.

  2. Il est plus lent à tester. Si je veux tester toute forme de logique qui se passe dans mon modèle ORM, mes tests doivent initialiser Rails pour fonctionner. Ce ne sera pas trop un problème au début de votre application, mais s'accumulera jusqu'à ce que vos tests unitaires prennent beaucoup de temps à courir.

  3. Cela va briser le SRP encore plus bas et de façon concrète. Supposons que notre entreprise nous oblige maintenant à envoyer un courriel aux utilisateurs lorsque les articles deviennent actifs? Nous ajoutons maintenant la logique de messagerie au modèle ORM Item, dont la responsabilité principale consiste à modéliser un élément. Il ne devrait pas se soucier de la logique d'email. Ceci est un cas d' effets secondaires commerciaux . Ceux-ci n'appartiennent pas au modèle ORM.

  4. C'est difficile de diversifier. J'ai vu des applications Rails matures avec des choses comme une base de données init_type: string, dont le seul but est de contrôler la logique d'initialisation. Cela pollue la base de données pour résoudre un problème structurel. Il y a de meilleurs moyens, je crois.

La façon PORO: Bien que ce soit un peu plus de code, cela vous permet de séparer vos modèles ORM et votre logique métier. Le code ici est simplifié, mais devrait montrer l'idée:

class SellableItemFactory
  def self.new(attributes = {})
    record = Item.new(attributes)
    record.active = true if record.active.nil?
    record
  end
end

Ensuite, avec cela en place, la façon de créer un nouvel article serait

SellableItemFactory.new

Et mes tests pourraient maintenant simplement vérifier que ItemFactory est actif sur Item s'il n'a pas de valeur. Pas d'initialisation Rails nécessaire, pas de rupture SRP. Lorsque l'initialisation d'élément devient plus avancée (par exemple, définir un champ d'état, un type par défaut, etc.), ItemFactory peut ajouter ceci. Si nous nous retrouvons avec deux types de valeurs par défaut, nous pouvons créer un nouveau BusinesCaseItemFactory pour le faire.

NOTE: Il pourrait également être avantageux d'utiliser l'injection de dépendance ici pour permettre à l'usine de construire beaucoup de choses actives, mais je l'ai laissé pour plus de simplicité. Le voici: self.new (klass = Item, attributs = {})




Si la colonne se trouve être une colonne de type "status" et que votre modèle se prête à l'utilisation de machines d'état, pensez à utiliser la gemme aasm , après quoi vous pouvez simplement faire

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

Il n'initialise toujours pas la valeur pour les enregistrements non sauvegardés, mais c'est un peu plus propre que de lancer le vôtre avec init ou autre chose, et vous récoltez les autres avantages de aasm tels que les étendues pour tous vos statuts.




Dans Rails 5+, vous pouvez utiliser la méthode d' attribute dans vos modèles, par exemple:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end






J'ai aussi vu des gens le mettre dans leur migration, mais je préfère le voir défini dans le code du modèle.

Existe-t-il une méthode canonique pour définir la valeur par défaut des champs dans le modèle ActiveRecord?

La méthode canonique Rails, avant Rails 5, était en fait de le définir dans la migration, et il suffit de regarder dans le db/schema.rb pour savoir quand les valeurs par défaut sont définies par la base de données pour n'importe quel modèle.

Contrairement à la réponse de @Jeff Perrin (qui est un peu ancienne), l'approche de migration appliquera même la valeur par défaut lors de l'utilisation de Model.new , en raison de la magie de Rails. Travail vérifié dans Rails 4.1.16.

La chose la plus simple est souvent la meilleure. Moins de dette de connaissances et de potentiels points de confusion dans le code de base. Et ça "marche".

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

La valeur null: false n'autorise pas les valeurs NULL dans la base de données et, en plus, elle met également à jour tous les enregistrements de base de données préexistants avec la valeur par défaut pour ce champ. Vous pouvez exclure ce paramètre dans la migration si vous le souhaitez, mais je l'ai trouvé très pratique!

La manière canonique dans Rails 5+ est, comme l'a dit @Lucas Caton:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end





Related