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 autour:
def initialize
super
self.status = ACTIVE unless self.status
end
et
def after_initialize
return unless new_record?
self.status = ACTIVE
end
J'ai également 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 un moyen canonique de définir la valeur par défaut des champs dans le modèle ActiveRecord?
Réponses:
Il existe plusieurs problèmes avec chacune des méthodes disponibles, mais je pense que la définition d'un
after_initialize
rappel est la voie à suivre pour les raisons suivantes:default_scope
initialisera les valeurs des nouveaux modèles, mais cela deviendra alors l'étendue sur laquelle vous trouverez le modèle. Si vous voulez simplement initialiser certains nombres à 0, ce n'est pas ce que vous voulez.initialize
peut fonctionner, mais n'oubliez pas d'appelersuper
!after_initialize
est obsolète à partir de Rails 3. Lorsque je remplaceafter_initialize
dans rails 3.0.3, j'obtiens l'avertissement suivant dans la console:Par conséquent, je dirais écrire un
after_initialize
rappel, qui vous permet de définir des attributs par défaut en plus de vous permettre de définir des valeurs par défaut sur des associations telles que:Maintenant, vous n'avez qu'un seul endroit où chercher pour l'initialisation de vos modèles. J'utilise cette méthode jusqu'à ce que quelqu'un en trouve une meilleure.
Mises en garde:
Pour les champs booléens, faites:
self.bool_field = true if self.bool_field.nil?
Voir le commentaire de Paul Russell sur cette réponse pour plus de détails
Si vous ne sélectionnez qu'un sous-ensemble de colonnes pour un modèle (c'est-à-dire, en l'utilisant
select
dans une requête commePerson.select(:firstname, :lastname).all
), vous obtiendrez unMissingAttributeError
si votreinit
méthode accède à une colonne qui n'a pas été incluse dans laselect
clause. Vous pouvez vous prémunir contre ce cas comme ceci:self.number ||= 0.0 if self.has_attribute? :number
et pour une colonne booléenne ...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
Notez également que la syntaxe est différente avant Rails 3.2 (voir le commentaire de Cliff Darling ci-dessous)
la source
initialize
, semble vraiment compliquée pour quelque chose qui devrait être clair et bien défini. J'ai passé des heures à parcourir la documentation avant de chercher ici parce que je supposais que cette fonctionnalité était déjà là quelque part et je n'en avais tout simplement pas conscience.self.bool_field ||= true
, car cela forcera le champ à true même si vous l'initialisez explicitement à false. Au lieu de celaself.bool_field = true if self.bool_field.nil?
.MissingAttributeError
. Vous pouvez ajouter un contrôle supplémentaire comme indiqué:self.number ||= 0.0 if self.has_attribute? :number
Pour booléens:self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
. Il s'agit de Rails 3.2+ - pour une utilisation antérieure,self.attributes.has_key?
vous devez utiliser une chaîne au lieu d'un symbole.initialize
parreturn if !new_record?
pour éviter les problèmes de performances.Rails 5+
Vous pouvez utiliser la méthode d' attribut dans vos modèles, par exemple:
Vous pouvez également passer un lambda au
default
paramètre. Exemple:la source
Value
et aucun transtypage ne sera effectué.store_accessor :my_jsonb_column, :locale
vous pouvez ensuite définirattribute :locale, :string, default: 'en'
nil
. S'ils ne peuvent pas êtrenil
DBnot null
+ DB par défaut + github.com/sshaw/keep_defaults sont le chemin à parcourir à partir de mon expérienceNous plaçons les valeurs par défaut dans la base de données lors des migrations (en spécifiant l'
:default
option 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 sur les principes de la réalité augmentée: convention sur la configuration, SEC, la définition de la table pilote le modèle, et non l'inverse.
Notez que les valeurs par défaut sont toujours dans le code d'application (Ruby), mais pas dans le modèle mais dans la ou les migrations.
la source
Certains cas simples peuvent être traités en définissant une valeur par défaut dans le schéma de base de données, mais cela ne gère pas un certain nombre de cas plus délicats, y compris les valeurs calculées et les clés d'autres modèles. Pour ces cas, je fais ceci:
J'ai décidé d'utiliser l'after_initialize mais je ne veux pas qu'il soit appliqué aux objets qui ne sont trouvés que ceux nouveaux ou créés. Je pense qu'il est presque choquant qu'un rappel after_new ne soit pas fourni pour ce cas d'utilisation évident, mais je l'ai fait en confirmant si l'objet est déjà persisté indiquant qu'il n'est pas nouveau.
Après avoir vu la réponse de Brad Murray, cela est encore plus propre si la condition est déplacée vers la demande de rappel:
la source
:before_create
?Le modèle de rappel after_initialize peut être amélioré en procédant simplement comme suit
Cela présente un avantage non trivial si votre code init doit traiter avec des associations, car le code suivant déclenche un subtil n + 1 si vous lisez l'enregistrement initial sans inclure celui associé.
la source
Les gars de Phusion ont un joli plugin pour ça.
la source
:default
valeurs des migrations de schéma de `` fonctionner simplement '' avecModel.new
.:default
valeurs des migrations `` fonctionnent '' avecModel.new
, contrairement à ce que Jeff a dit dans son article. Travail vérifié dans Rails 4.1.16.Un moyen potentiel encore meilleur / plus propre que les réponses proposées est de remplacer l'accesseur, comme ceci:
Voir «Écraser les accesseurs par défaut» dans la documentation ActiveRecord :: Base et plus de StackOverflow sur l'utilisation de self .
la source
attributes
. Testé sur rails 5.2.0.J'utilise le
attribute-defaults
bijouÀ partir de la documentation: exécutez
sudo gem install attribute-defaults
et ajoutezrequire 'attribute_defaults'
à votre application.la source
Des questions similaires, mais toutes ont un contexte légèrement différent: - Comment créer une valeur par défaut pour les attributs dans le modèle de Rails activerecord?
Meilleure réponse: dépend de ce que vous voulez!
Si vous voulez que chaque objet commence par une valeur: utilisez
after_initialize :init
Vous souhaitez que le
new.html
formulaire ait une valeur par défaut à l'ouverture de la page? utilisez https://stackoverflow.com/a/5127684/1536309Si vous voulez que chaque objet ait une valeur calculée à partir de l'entrée utilisateur: utilisez
before_save :default_values
Vous voulez que l'utilisateur entreX
et ensuiteY = X+'foo'
? utilisation:la source
C'est à ça que servent les constructeurs! Remplacez lainitialize
méthode du modèle .Utilisez la
after_initialize
méthode.la source
after_initialize
méthode à la place.Les gars Sup, j'ai fini par faire ce qui suit:
Fonctionne comme un charme!
la source
Cela a été répondu depuis longtemps, mais j'ai souvent besoin de valeurs par défaut et je préfère ne pas les mettre dans la base de données. Je crée une
DefaultValues
préoccupation:Et puis utilisez-le dans mes modèles comme ceci:
la source
La manière canonique de Rails, avant Rails 5, était en fait de le définir dans la migration, et de simplement chercher dans le
db/schema.rb
pour chaque fois que vous voulez voir quelles valeurs par défaut sont définies par la base de données pour n'importe quel modèle.Contrairement à ce que dit @Jeff Perrin (qui est un peu ancien), l'approche de la migration appliquera même la valeur par défaut lors de l'utilisation
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 points de confusion potentiels dans la base de code. Et cela «fonctionne tout simplement».
Ou, pour un changement de colonne sans en créer un nouveau, effectuez l'une des opérations suivantes:
Ou peut-être encore mieux:
Consultez le guide RoR officiel pour les options de méthodes de changement de colonne.
Le
null: false
n'autorise pas les valeurs NULL dans la base de données et, comme avantage supplémentaire, il est également mis à jour afin que tous les enregistrements de base de données préexistants qui étaient auparavant nuls soient également définis 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 voie canonique dans Rails 5+ est, comme l'a dit @Lucas Caton:
la source
Le problème avec les solutions after_initialize est que vous devez ajouter un after_initialize à chaque objet que vous recherchez hors de la base de données, que vous accédiez ou non à cet attribut. Je suggère une approche paresseuse.
Les méthodes d'attribut (getters) sont bien sûr des méthodes elles-mêmes, vous pouvez donc les remplacer et fournir une valeur par défaut. Quelque chose comme:
À moins que, comme quelqu'un l'a souligné, vous devez faire Foo.find_by_status ('ACTIVE'). Dans ce cas, je pense que vous auriez vraiment besoin de définir la valeur par défaut dans vos contraintes de base de données, si la base de données le prend en charge.
la source
J'ai rencontré des problèmes pour
after_initialize
donner desActiveModel::MissingAttributeError
erreurs lors de recherches complexes:par exemple:
"recherche" dans le
.where
hachage de conditionsJ'ai donc fini par le faire en remplaçant initialize de cette façon:
L'
super
appel est nécessaire pour s'assurer que l'objet s'initialise correctementActiveRecord::Base
avant de faire mon code de personnalisation, ie: default_valuesla source
def initialize(*); super; default_values; end
dans Rails 5.2.0. De plus, la valeur par défaut est disponible, même dans le.attributes
hachage.la source
Bien que faire cela pour définir des valeurs par défaut soit déroutant et gênant dans la plupart des cas, vous pouvez également l'utiliser
:default_scope
. Découvrez le commentaire de Squil ici .la source
La méthode after_initialize est obsolète, utilisez plutôt le rappel.
cependant, utiliser : default dans vos migrations est toujours le moyen le plus propre.
la source
after_initialize
méthode n'est PAS obsolète . En fait, le rappel de style macro vous donne un exemple de SI obsolète . Détails: guides.rubyonrails.org/…J'ai trouvé que l'utilisation d'une méthode de validation fournit 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 échouer la validation) 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 tant que #valid? est appelé.
Concernant la définition d'une méthode after_initialize, il pourrait y avoir des problèmes de performances car after_initialize est également appelé par chaque objet retourné par: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find
la source
new
action soit réutilisée.Si la colonne se trouve être une colonne de type «état» et que votre modèle se prête à l'utilisation de machines à états, envisagez d'utiliser la gemme aasm , après quoi vous pouvez simplement faire
Il
init
n'initialise toujours pas la valeur des enregistrements non enregistrés, mais c'est un peu plus propre que de rouler le vôtre avec ou autre chose, et vous récoltez les autres avantages d'un aasm tels que les étendues pour tous vos statuts.la source
https://github.com/keithrowell/rails_default_value
la source
Je suggère fortement d'utiliser le joyau "default_value_for": https://github.com/FooBarWidget/default_value_for
Il existe des scénarios délicats qui nécessitent à peu près la priorité sur la méthode d'initialisation, ce que fait ce joyau.
Exemples:
Votre valeur par défaut db est NULL, votre valeur par défaut définie par le modèle / ruby est "une chaîne", mais vous voulez réellement définir la valeur sur nil pour une raison quelconque:
MyModel.new(my_attr: nil)
La plupart des solutions ici ne parviendront pas à définir la valeur à zéro et la définiront par défaut.
OK, alors au lieu de prendre l'
||=
approche, vous passez àmy_attr_changed?
...MAIS imaginez maintenant que votre valeur par défaut db est "une chaîne", votre valeur par défaut définie par le modèle / ruby est "une autre chaîne", mais dans un certain scénario, vous souhaitez définir la valeur sur "une chaîne" (la valeur par défaut db):
MyModel.new(my_attr: 'some_string')
Cela se traduira par
my_attr_changed?
être faux car la valeur correspond à la valeur par défaut db, qui à son tour déclenchera votre code par défaut défini en rubis et définira la valeur sur "une autre chaîne" - encore une fois, pas ce que vous vouliez.Pour ces raisons, je ne pense pas que cela puisse être accompli correctement avec juste un hook after_initialize.
Encore une fois, je pense que le joyau "default_value_for" adopte la bonne approche: https://github.com/FooBarWidget/default_value_for
la source
Voici une solution que j'ai utilisée et qui m'a un peu surpris n'a pas encore été ajoutée.
Il y a deux parties. La première partie définit la valeur par défaut dans la migration réelle, et la deuxième partie ajoute une validation dans le modèle garantissant que la présence est vraie.
Vous verrez donc ici que la valeur par défaut est déjà définie. Maintenant, dans la validation, vous voulez vous assurer qu'il y a toujours une valeur pour la chaîne, alors faites simplement
Cela vous permet de définir la valeur par défaut pour vous. (pour moi, j'ai "Bienvenue dans l'équipe"), puis cela ira un peu plus loin et s'assurera qu'il y a toujours une valeur présente pour cet objet.
J'espère que cela pourra aider!
la source
la source
utiliser default_scope dans les rails 3
api doc
ActiveRecord masque la différence entre la définition par défaut définie dans la base de données (schéma) et la définition par défaut effectuée dans l'application (modèle). Lors de l'initialisation, il analyse le schéma de la base de données et note toutes les valeurs par défaut qui y sont spécifiées. Plus tard, lors de la création d'objets, il attribue les valeurs par défaut spécifiées au schéma sans toucher à la base de données.
discussion
la source
default_scope
. Ainsi, toutes vos requêtes ajouteront cette condition au champ que vous avez défini. Ce n'est presque JAMAIS ce que vous voulez.Depuis les docs api http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Utilisez la
before_validation
méthode dans votre modèle, elle vous donne les options de création d'une initialisation spécifique pour créer et mettre à jour des appels, par exemple dans cet exemple (encore une fois le code est pris à partir de l'exemple api docs) le champ numéro est initialisé pour une carte de crédit. Vous pouvez facilement l'adapter pour définir les valeurs que vous souhaitezSurpris que son n'a pas été suggéré ici
la source