J'ai un modèle opening_times
qui enregistre les heures d'ouverture des magasins.
before_save :delete_records_with_missing_hours private def delete_records_with_missing_hours if self.morning.blank? or self.evening.blank? self.destroy end end
Ce modèle est mis à jour uniquement via un formulaire imbriqué dans l'action edit
du contrôleur shops
. Je n'ai pas de contrôleur opening_times
.
En gros, ce que je peux faire est assez limité: l'action update
.
Bien que j'aie un problème: quand il y avait des heures d'ouverture pour un jour spécifique, disons mardi, et que l'utilisateur veut que ce jour ne fonctionne pas, l'utilisateur remplit les deux champs matin et
soir
vides.
Je pourrais enregistrer des valeurs vides dans la base de données mais il serait préférable de supprimer réellement l'enregistrement pour mardi.
alors dans le fichier modèle, j'ai défini ceci:
create_table "opening_times", force: :cascade do |t| t.string "day" t.time "morning" t.time "evening" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.bigint "shop_id" t.index ["shop_id"] end
Mais cela ne fonctionne pas.
Existe-t-il un moyen de supprimer un enregistrement destiné à être mis à jour au niveau du modèle?
3 Réponses :
Je pense que vous feriez mieux de créer une sorte de script de maintenance pour cette action. Comme une tâche de râteau ou quelque chose comme ça. Donc, périodiquement, vous exécutez une simple recherche et destruction:
OpeningTime.where(morning:nil, evening:nil).destroy_all
Exécutez simplement ceci sur une base quotidienne ou hebdomadaire pour nettoyer votre base de données et ne vous souciez pas des enregistrements au moment de l'enregistrement.
Je pense que le problème avec votre code actuel est que vous supprimez l'enregistrement dans le before_save, donc cela crée deux problèmes:
1 / Que devraient faire les rails si ce n'est pas persistant record, mais un nouveau? Destroy échouerait et avec lui toute la transaction reviendrait (jetez un œil à votre console)
2 / Même si l'enregistrement était conservé, sa suppression fonctionnerait alors, mais l'action de sauvegarde qui sera effectuée ensuite échouerait, entraînant également une annulation.
Merci pour votre explication sur les raisons pour lesquelles cela échoue, cela revient à chaque fois. Ma solution était de parcourir les (jusqu'à) 7 enfants des modèles de boutique et de vérifier les enregistrements avec des champs matin
ou soir
vides et de les supprimer du contrôleur. Mais cela semble très redondant et laid. @moveson m'a en fait donné la solution dans l'autre réponse (besoin de faire tout cela dans le parent)
Il existe une méthode Rails pour gérer cela. Cela peut être fait très facilement, mais vous devez le faire à partir de votre modèle de boutique. Dans ce modèle, insérez ceci:
class Shop < ApplicationRecord accepts_nested_attributes_for :opening_times, allow_destroy: true, reject_if: :reject_opening_time? def reject_opening_time?(attributes) persisted = attributes[:id].present? time_values = attributes.slice(:morning, :evening).values without_time = time_values.any?(&:blank?) attributes.merge!(_destroy: true) if persisted and without_time without_time && !persisted # Return false so as to reject new opening_time if any time attributes are empty end end
Maintenant, pour chaque enregistrement imbriqué opening_time
, Rails évaluera les attributs de temps. Si une valeur de temps est vide, il traitera l'enregistrement de manière appropriée. Si l'enregistrement est persistant, il ajoutera un attribut _destroy
, qui détruira l'enregistrement imbriqué lorsque vous sauvegarderez le parent. Si l'enregistrement n'est pas conservé, il sera rejeté (ignoré) lorsque vous enregistrez le parent.
Merci beaucoup. C'est exactement ce dont j'avais besoin. Je ne comprends pas la quatrième ligne avec la fusion car c'est une syntaxe assez élaborée pour moi ... mais je vais creuser un peu.
Est-ce vraiment la voie des rails? Je ne pense pas que reject_if devrait être utilisé pour manipuler les attributs d'enregistrement. De plus, si vous ajoutez plusieurs modèles à la méthode Accept_nested_attributes_for, cela échouera. Cela devrait être indépendant du modèle. De plus, je pense que ce n'est pas une tâche pour le modèle Shop, car il s'agit d'un modèle différent. Cela devrait probablement déjà être corrigé dans la vue, en définissant _destroy: true
sur l'enregistrement dont les heures d'ouverture ont été supprimées.
@ bo-oz Cette méthode fonctionne, elle utilise les outils Rails inclus et ne nécessite pas de travail cron. Définir _destroy: true
dans une vue semble contre-productif car la logique devrait être dupliquée par tout client qui soumet des attributs imbriqués. L'ajout d'un autre modèle imbriqué pourrait (et devrait) être fait avec une ligne accepte_nested_attributes_for
distincte. Je suis ouvert à la définition de _destroy: true
dans un rappel sur le modèle parent ou enfant, mais je n'ai pas testé pour voir ce qui fonctionnerait.
Si vous préférez ne pas avoir de champs avec des valeurs vides pour matin
et soir
, pourquoi ne pas simplement valider l'existence de ces champs? Ce serait beaucoup plus propre que d'utiliser des méthodes externes pour valider cette situation.
Premièrement, vous devriez mettre ceci dans le modèle Shop
:
validate :morning_and_evening_validation def morning_and_evening_validation morning.present? || evening.present? end
Ce code s'assurera que votre association est validée avant d'être insérée / mise à jour
Vous pouvez maintenant mettre une validation dans le modèle OpeningTime
, comme ceci:
validates :morning, :evening, presence: true, on: :update
Et si vous voulez juste faire cela dans l'action de mise à jour, vous pouvez même faire:
validates :morning, :evening, presence: true
Et si vous vouliez au moins un des pour être présentes, vous pouvez également utiliser:
validates_associated :opening_times
Je pense que c'est surtout beaucoup plus propre et lisible.