2
votes

Détruire l'enregistrement au lieu de mettre à jour sous forme de modèle imbriqué

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?


0 commentaires

3 Réponses :


1
votes

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.


1 commentaires

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)



1
votes

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.


3 commentaires

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.



0
votes

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.


0 commentaires