ruby-on-rails rails generate - Comment puis-je définir des valeurs par défaut dans ActiveRecord?
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).
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?
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
Les gars de Phusion ont un joli plugin pour ça.
Un moyen encore meilleur / plus propre que les réponses proposées est d'écraser l'accesseur, comme ceci:
def status
self['status'] || ACTIVE
end
Voir "Ecrasement des accesseurs par défaut" dans la documentation ActiveRecord :: Base et plus encore de à l'aide de self .
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?
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.
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.
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.
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 = {})
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
class Item < ActiveRecord::Base
def status
self[:status] or ACTIVE
end
before_save{ self.status ||= ACTIVE }
end
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 .
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/…
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.
utilise default_scope dans les rails 3
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.