IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Ruby on Rails Discussion :

Utiliser 'polymorphic association' avec has_many


Sujet :

Ruby on Rails

  1. #1
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut Utiliser 'polymorphic association' avec has_many
    Salut à tous! J'ai essayé d'appliquer la technique décrite dans l'API Rails http://api.rubyonrails.org/classes/A...ssMethods.html pour pouvoir utiliser polymorphisme et 'has_many association'.
    J'ai les modèles Project, Engineer, BuildingSite (Chantier). Un Ingénieur peut participer dans un ou plusieurs Projets sous différents 'rôles' en fonctions d'une des compétences dont il possède. Effectivement, dans un Projet peuvent être impliqués un ou plusieurs Ingénieurs.
    Mais le type dans la table de jointure ne change pas en restant toujours celui de la classe de base. Voici mes modèles:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    class Project < ActiveRecord::Base
      has_many :building_sites
      has_many :engineers, :through=>:building_sites  
    end
     
    class Engineer < ActiveRecord::Base
      has_many :building_sites, :as => :engineer
      has_many :projects, :through => :building_sites
    end
     
    class ElectricEngineer < Engineer
    end
     
    class BuildingSite < ActiveRecord::Base
      belongs_to :project
      belongs_to :engineer, :polymorphic => true
     
      def engineer_type=(sType)
           super(sType.to_s.classify.constantize.base_class.to_s)
        end
    end
    Comme expliqué dans l'API (voir le lien ci-dessus) la méthode suivante a du être ajoutée dans BuildingSite pour pouvoir tracer le type d'ingénieur (sa spécialité utilisée dans le projet):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    def engineer_type=(sType)
           super(sType.to_s.classify.constantize.base_class.to_s)
        end
    Tous va presque bien si je le test dans la console:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
     
    Loading development environment (Rails 2.2.2)
     
    >> pr = Project.create(:description=> 'Oak residence')
    pr = Project.create(:description=> 'Oak residence')
     
    => #<Project id: 2, description: "Oak residence", created_at: "2009-02-12 10:44:48", updated_at: "2009-02-12 10:44:48">
     
    >> electric = ElectricEngineer.create(:title=>'Mr',:name=>'Smith')
    electric = ElectricEngineer.create(:title=>'Mr',:name=>'Smith')
     
    => #<ElectricEngineer id: 7, title: "Mr", name: "Smith", created_at: "2009-02-12 10:45:35", updated_at: "2009-02-12 10:45:35">
     
    >> site = BuildingSite.new(:budget=>10000)
    site = BuildingSite.new(:budget=>10000)
     
    => #<BuildingSite project_id: nil, engineer_id: nil, engineer_type: nil, budget: 10000, created_at: nil, updated_at: nil>
     
    >> site.project=pr
    site.project=pr
     
    => #<Project id: 2, description: "Oak residence", created_at: "2009-02-12 10:44:48", updated_at: "2009-02-12 10:44:48">
     
    >> site.engineer = electric
    site.engineer = electric
     
    => #<ElectricEngineer id: 7, title: "Mr", name: "Smith", created_at: "2009-02-12 10:45:35", updated_at: "2009-02-12 10:45:35">
     
    >> site.save
    site.save
     
    => true
     
    >>
    le problème - c'est que le engineer_type reste 'Engineer' au lieu d'être 'ElectricEngineer'. Dois-je avoir la colonne 'type' aussi dans la table 'Engineers' ou non?
    Quand je vérifie la class de mon Electric Engineer que je viens de créer c'est OK:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    >> electric.class
    electric.class
     
    => ElectricEngineer(id: integer, title: string, name: string, created_at: datetime, updated_at: datetime)
     
    >> electric.type
    electric.type
     
    => ElectricEngineer(id: integer, title: string, name: string, created_at: datetime, updated_at: datetime)
     
    (irb):8: warning: Object#type is deprecated; use Object#class
    >>
    Quelqu'un a une idée comment résoudre ce problème?

  2. #2
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    Même si j'ajoute la colonne 'type' dans la table 'Engineers' - ça ne change rien. Le 'engineer_type' sauvegardée dans BuildingSites est toujours 'Engineer'

  3. #3
    Membre éprouvé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    657
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 657
    Points : 910
    Points
    910
    Par défaut
    Ce que tu décrit à l'air conforme à ce que dit l'API.

    Tel que je le comprend il faut que tu regardes la valeur de 'type' sur la table 'Engineer'. Est-elle correcte ?
    Toute la documentation Ruby on Rails : gotapi.com/rubyrails
    Mes articles :
    > HAML : langage de template pour Ruby on Rails

  4. #4
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    J'ai décidé de choisir un autre exemple, plus parlant à mon avis. Donc on a les modèles suivants: Project, Development, Developper. Chaque Developper possède de connaissances dans plusieurs domaines (langages de programmation si vous voulez: Java, Dot.NET, RoR, Hibernate, C++, etc.). Et il peut participer dans un ou plusieurs Projets dans des 'rôles' différents. Bien sur, dans un Project peuvent participer plusieurs développeurs. Voici le schéma:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     
    create_table "developments", :force => true do |t|
        t.integer  "project_id"
        t.integer  "developper_id"
        t.string   "developper_type"
        t.integer  "budget",          :limit => 10, :precision => 10, :scale => 0
        t.datetime "created_at"
        t.datetime "updated_at"
      end
     
      create_table "developpers", :force => true do |t|
        t.string   "type"
        t.string   "name"
        t.datetime "created_at"
        t.datetime "updated_at"
      end
     
      create_table "projects", :force => true do |t|
        t.string   "desc"
        t.datetime "created_at"
        t.datetime "updated_at"
      end
    Les modèles:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     
    class Project < ActiveRecord::Base
      has_many :developments
      has_many :developpers, :through=>:developments
    end
     
    class Development < ActiveRecord::Base
      belongs_to :project
      belongs_to :developper
     
      validates_uniqueness_of :developper_id , :scope => :project_id
     
      def member_type=(sType)
        super(sType.to_s.classify.constantize.base_class.to_s)
      end
    end
     
    class Developper < ActiveRecord::Base
      has_many :developments, :as => :developper
      has_many :projects, :through => :developments
    end
     
    class JavaDevelopper < Developper
    end
    Quand je le teste dans la console:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    Loading development environment (Rails 2.2.2)
     
    >> p =Project.create(:desc=>'web')
    p =Project.create(:desc=>'web')
     
    => #<Project id: 1, desc: "web", created_at: "2009-02-13 08:13:06", updated_at: "2009-02-13 08:13:06">
     
    >> java_developper = JavaDevelopper.create(:name=>'Smith')
    java_developper = JavaDevelopper.create(:name=>'Smith')
     
    => #<JavaDevelopper id: 1, type: "JavaDevelopper", name: "Smith", created_at: "2009-02-13 08:13:48", updated_at: "2009-02-13 08:13:48">
    Je vérifie la table 'Developpers' - le type est bien à 'JavaDevelopper'. Je continue:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    >> p.developpers << java_developper
    p.developpers << java_developper
     
    => [#<JavaDevelopper id: 1, type: "JavaDevelopper", name: "Smith", created_at: "2009-02-13 08:13:48", updated_at: "2009-02-13 08:13:48">]
     
    >>
    Je vérifie la table 'Developments': id=1, project_id=1, developper_id=1, developper_type=NULL.

  5. #5
    Membre éprouvé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    657
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 657
    Points : 910
    Points
    910
    Par défaut
    La colonne developer_type n'est pas remplie car tu n'as pas spécifié :polymorphic => true dans Development.belongs_to :developer

    Dans ton problème il y a une légere ambiguité. Est-ce que tu considère que le "type" du developper dépend du projet ? C'est à dire est-ce que je peux être JavaDevelopper dans le projet X et DotNetDevelopper dans le projet Y ?
    Si ce n'est pas le cas, alors la colonne Developpement.developper_type ne sert à rien, puisque tu as déjà l'information de type dans la table Developper. Autrement, c'est un attribut de ta relation et là effectivement Rails ne le gère pas directement, mais tu peux assez facilement créer les méthodes adéquates toi même.
    Toute la documentation Ruby on Rails : gotapi.com/rubyrails
    Mes articles :
    > HAML : langage de template pour Ruby on Rails

  6. #6
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    Citation Envoyé par Taum Voir le message
    Oui, tout à fait exacte ! On peux être JavaDevelopper dans le projet X et DotNetDevelopper dans le projet Y ? Je vais le tester et poster mes résultats. Merci beaucoup!
    Je crois que répondu assez clairement - je VEUX que le type de Developper soit indiqué dans la table Developments en fonctions de Project. C'est comme tu disait, - on peut être DotNET developper dans un projet X et Java Developper dans un Projet Y. C'est de un, De deux, dans l'exemple vers lequel tu m'as envoyé, chaque modèle représente une sous-class de ActiveRecord. Dans mon cas ce sont des sous-classes de Developper. Est-ce que change quelque chose? Dois-je déclarer dans Developments:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    belongs_to :developper,  :class_name => "Developper", :foreign_key => "developper_id"
    belongs_to :java_developper,  :class_name => "JavaDevelopper", :foreign_key => "developper_id"
    belongs_to :dotnet_developper,  :class_name => "DotnetDevelopper", :foreign_key => "developper_id", etc.
    ou comme JavaDevelopper et les autres héritent de Developper tout cela va se faire automatiquement?

  7. #7
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    J'ai ajouté 'polymorhic=> true' dans le modèle 'Developments' qui resemble maintenant à ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class Development < ActiveRecord::Base
      belongs_to :project
      belongs_to :developper, :polymorphic=> true
      validates_uniqueness_of :developper_id , :scope => :project_id
     
      def member_type=(sType)
        super(sType.to_s.classify.constantize.base_class.to_s)
      end
    end
    Puis j'ai à nouveau testé dans la console:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    >> p = Project.find(2)
    p = Project.find(2)
    => #<Project id: 2, desc: "rails", started: nil, ended: nil, coordinator_id: nil, created_at: "2009-02-15 18:58:29", updated_at: "2009-02-15 18:58:29">
    >> j_devr = JavaDevelopper.create(:title=>'Mr',:lastname=>'Smith')
    j_devr = JavaDevelopper.create(:title=>'Mr',:lastname=>'Smith')
     
    => #<JavaDevelopper id: 3, type: "JavaDevelopper", title: "Mr", lastname: "Smith", firstname: nil, mail: nil, phone: nil, dept: nil, created_at: "2009-02-15 19:08:23", updated_at: "2009-02-15 19:08:23">
     
    >> p.developpers << j_devr
    p.developpers << j_devr
     
    ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have a has_many :through association 'Project#developpers' on the polymorphic object 'Developper#developper'.
     
            from c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/reflection.rb:284:in `check_validity!'
     
            from c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/associations/has_many_through_association.rb:5:in `initialize'
     
            from c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/associations.rb:1297:in `new'
     
            from c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.2.2/lib/active_record/associations.rb:1297:in `developpers'
     
            from (irb):4
    >> devs = Development.new
    devs = Development.new
     
    => #<Development id: nil, project_id: nil, developper_id: nil, developper_type: nil, budget: nil, created_at: nil, updated_at: nil>
     
    >> devs.project = p
    devs.project = p
     
    => #<Project id: 2, desc: "rails", started: nil, ended: nil, coordinator_id: nil, created_at: "2009-02-15 18:58:29", updated_at: "2009-02-15 18:58:29">
     
    >> devs.developper = j_devr
    devs.developper = j_devr
     
    => #<JavaDevelopper id: 3, type: "JavaDevelopper", title: "Mr", lastname: "Smith", firstname: nil, mail: nil, phone: nil, dept: nil, created_at: "2009-02-15 19:08:23", updated_at: "2009-02-15 19:08:23">
     
    >> devs.save
    devs.save
     
    => true
    Le 'developper_type' est toujours à 'Developper' et non pas à JavaDevelopper comme je voudrais bien.
    Ce qui m'enbète - c'est que l'on ne peut pas faire directement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    project.developpers << java_developper ou dot_net_developper, par ex.
    Pourtant cela devrait être tellement logique.

  8. #8
    Membre éprouvé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    657
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 657
    Points : 910
    Points
    910
    Par défaut
    Désolé de te dire ça mais j'ai l'impression que tu n'as rien compris à ce que je t'ai dis plus haut
    Malheureusement je vois pas comment être plus clair, il faut que tu répondes à la question de savoir si l'attribut "type" doit être sur la relation Developpement ou sur l'entité Developper, puis agir en conséquence.
    Tu bénéficiera surement de la lecture de ce post qui explique pourquoi il n'est pas possible d'utiliser has_many :through avec une association polymorphique.
    Toute la documentation Ruby on Rails : gotapi.com/rubyrails
    Mes articles :
    > HAML : langage de template pour Ruby on Rails

  9. #9
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    Bizarrement, ma réponse a été insérée plus haut que je voulais, sorry.

  10. #10
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    C'est ça que je voudrais faire.
    Mais même en utilisant la plugin (ou gem) 'has_many_polymorphs' dans la table de jointure le type reste toujours celui de la class de base, - dans mon cas c'est Developper et non pas JavaDevelopper ou RorDevelopper.

  11. #11
    Membre éprouvé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    657
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 657
    Points : 910
    Points
    910
    Par défaut
    je veux que le type de Developper soit indiqué dans la table Developments en fonctions de Project. C'est comme tu disait, - on peut être DotNET developper dans un projet X et Java Developper dans un Projet Y.
    Du coup, quel est l'intêret d'utiliser une STI? Dans mon sens il n'y a pas de "développeur java" ou de "développeur dotNet", juste des "développeur" dont il se trouve que l'un deux développe en Java pour le projet X et en DotNet pour le projet Y. Ta relation est une simple has_many :through, le type étant une propriété sur le modèle de jointure à gérer "à la main" (et du coup tu ne l'appellera surtout pas "type", qui est reservé pour les STI ).

    Si tu veux récuperer tout les projets où un développeur fait du Java, tu peux faire ça facilement avec un named_scope (sauf erreur de ma part) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    class Developpement < AR::B
      named_scope :java, :conditions => {:techno => 'java'}
    end
     
    class Developper < AR::B
      def java_projects
        self.developpements.java.projects
      end
    end
    Toute la documentation Ruby on Rails : gotapi.com/rubyrails
    Mes articles :
    > HAML : langage de template pour Ruby on Rails

  12. #12
    Membre averti Avatar de Javix
    Inscrit en
    Juin 2007
    Messages
    531
    Détails du profil
    Informations forums :
    Inscription : Juin 2007
    Messages : 531
    Points : 353
    Points
    353
    Par défaut
    Je ne sais pas ce qui m'a pris de me pencher autant sur l'idée de polymorphisme. C'est vrai, on très facilement faire sans: soit comme tu le dit, soit en créant le modèle de 'developer skills', Profile, par exemple, et une rélation has_many-through via 'Developments':
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    Developers:
    has_many :developments
    has_many :profiles, :thorugh=>:developments
    Je ne sais pas pourquoi je pensais qu'il était possible ou même indispensable d'utiliser le polymorphism.
    En tout cas merci a Taum pour ces tuyaux.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. has_many et polymorphic association
    Par l0vers0n dans le forum Ruby on Rails
    Réponses: 2
    Dernier message: 13/08/2010, 17h43
  2. [Liferay] Exception lors d'utilisation des associations avec criteria
    Par lamis2009 dans le forum Portails
    Réponses: 0
    Dernier message: 17/06/2010, 18h09
  3. utiliser des formulaires avec des associations
    Par horkets dans le forum Ruby
    Réponses: 2
    Dernier message: 19/11/2009, 00h30
  4. utilisation de dll avec diverses compilateurs
    Par Thylia dans le forum C++
    Réponses: 30
    Dernier message: 21/10/2004, 16h30
  5. Utiliser Borland C++ avec Emacs sous Windows
    Par Eikichi dans le forum Autres éditeurs
    Réponses: 2
    Dernier message: 02/03/2003, 08h40

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo