Forum Programmation.ruby [Rails] Problème stack level too deep et relation récursive.

Posté par  .
Étiquettes : aucune
0
2
avr.
2010

Bonjour à tous.

Contexte : je veux modéliser des bus de connection entre périphériques externes.

Je crée pour celà une table Buses :

class CreateBuses < ActiveRecord::Migration
def self.up
create_table :buses do |t|
t.column :name, :string
t.column :version, :string
t.column :speed, :integer
end
end

def self.down
drop_table :buses
end
end


Je veux pouvoir gérer la compatibilité entre les divers bus: par exemple je suppose qu'un bus SCSI1 est compatible avec un bus SCSI2. Je dois donc gérer une relation récursive sur ma table (un bus peut être compatible avec plusieurs autres bus) Cependant, lorsque j'ajoute un bus d'un certain type dans ma liste de compatibilité, il faut que la réciproque soit vraie (exemple : un bus SCSI2 est compatible avec SCSI1, et vice-versa. Notez que je ne parle pas de format de connecteur, je parle juste en terme de bus). Autrement dit, lorsque j'ajoute scsi2 à la liste des bus compatibles avec scsi1, je ne dois pas ajouter scsi1 à la liste des bus compatibles à SCSI2, c'est censé se faire tout seul.

Dans un premier temps, pour gérer la relation, j'ai créé une table buses_compatibles de la façon suivante :


class CreateBusesCompatibles < ActiveRecord::Migration
def self.up
create_table :buses_compatibles do |t|
t.column :bus_id, :integer
t.column :compatible_id, :integer
end
end

def self.down
drop_table :buses_compatibles
end
end



Puis je modifie mon modèle ainsi:

class Bus < ActiveRecord::Base
has_many :idiskslotmodels

has_and_belongs_to_many :compatibles,
:class_name=> "Bus",
:join_table => "buses_compatibles",
:association_foreign_key =>"compatible_id",
:foreign_key => "bus_id",
:after_add => :be_compatibly_to_compatible,
:after_remove => :no_more_compatible

def be_compatibly_to_compatible(compatible)
compatible.compatibles << self unless compatible.compatibles.include?(self)
end
def no_more_compatible(compatible)
compatible.compatibles.delete(self) rescue nil

end
end


Lorsque je crée un bus, et que j'ajoute des autres bus dans la liste des bus compatibles, ça se passe bien, mais c'est lorsque je sauve que j'ai un problème. J'ai l'impression que les bus demandent aux bus compatibles de se sauvegarder, et les bus compatibles font la même chose donc rappellent le bus "originel" qui lui rappelle la méthode de sauvegarde du bus compatible, etc ... et ce infiniment.
Ci-dessous un exemple :

Loading development environment.
>> b1=Bus.new
=> #nil, "version"=>nil, "speed"=>nil}>
>> b2=Bus.new
=> #nil, "version"=>nil, "speed"=>nil}>
>> b2.compatibles<
=> [#nil, "version"=>nil, "speed"=>nil}>], @new_record=t
rue, @attributes={"name"=>nil, "version"=>nil, "speed"=>nil}>]
>> b1.save
SystemStackError: stack level too deep

[... ]


Voyez-vous un moyen d'empêcher ça ?

Merci d'avance pour votre aide.
  • # Ce que j'ai constaté :

    Posté par  . Évalué à 2.

    Déjà une erreur dans mon code :
    class CreateBusesCompatibles < ActiveRecord::Migration
      def self.up
        create_table :buses_compatibles, id=>false  do |t|
          t.column  :bus_id, :integer
          t.column  :compatible_id, :integer
        end
      end
    
      def self.down
        drop_table :buses_compatibles
      end
    end
    
    Ensuite je me suis rendu compte que : - lorsque je crée deux instances de ma classe Bus, que je les rends compaibles entre eux et que je sauvegarde, ça part en vrille. - lorsque je crée mes instance, je les sauvegarde puis les rend compatibles entre eux, je ne peux plus sauvegarder via la methode save, mais plutot via update, et là il ne semble pas y avoir de problème. J'ai un peu de mal à comprendre ce qui se passe, toute aide serait la bienvenue.
  • # début de piste

    Posté par  . Évalué à 3.

  • # début de piste

    Posté par  . Évalué à -1.

  • # Donc

    Posté par  . Évalué à 1.

    Je vais pas rentrer dans les détails, mais plusieurs détails me font penser que tu pars sur une mauvaise approche
    • Tu essayes de modéliser un problème sans abstraction, en le pliant sur l'implémentation de base, c'est un peu un jeu risqué.
    • Tu présentes dans ton journal en commençant par les migrations. Concrètement, ca veut dire que tu raisonne de bas en haut au lieu de procéder dans l'autre sens.
    • Tes callbacks donc, ont un effet de bord assez pervers. Faire un rocher.moules << Moule.last a pour conséquence de sauver ta dernière moule au passage, donc de déclencher les callbacks de celle-ci et ainsi de suite. Bref, c'est dans la doc !
    Ce que je pense que tu devrais faire :
    • Ecrire des tests, genre RSpec, UnitTest, Shoulda, ce que tu veux, mais écris des tests, Ruby sans tests c'est comme faire voler des avions sans simulation, ca va voler pendant un moment, et tu sauras pas pourquoi ca va se péter la gueule ( et ouaip, c'est dynamique à mort, c'est facile à shamaniser sans faire gaffe ).
    • Ton problème finalement, c'est de gérer un graphe non-orienté, t'as soit
      • Des libs pour ça, jte laisser chercher.
      • un tout petit morceau d'abstraction à écrire
    • Relis bien la doc, tout ce qui tourne autour des collections, c'est toujours un peu sensible quand ça doit être sauvé ou pas, il faut bien maitriser chacune des méthodes.
    En espérant t'avoir aidé.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.