2017-09-16 4 views
1

J'ai une application qui a 3 modèles spécifiques; Utilisateur, Pays et Ville. La relation idéale pour chacun serait comme suit:Ruby Model Relationships - Relation à trois voies

utilisateur

  • has_many: pays
  • has_many: villes, à travers:: pays

Pays

  • has_m tout: cependant, le pays

La plupart de ces relations sont assez simple,: utilisateurs

  • has_many: villes
  • Ville

    • has_many: utilisateurs
    • belongs_to la relation User-to-City me pose un petit problème. Je veux être en mesure d'attribuer une ville à un utilisateur, à condition que l'utilisateur ait été affecté au pays associé à la ville. À l'heure actuelle, je ne suis même pas capable d'attribuer une ville à un utilisateur, et encore moins d'établir la bonne logique pour la restriction.

      Jusqu'à présent, ce que j'ai pu lire et rechercher a conduit à ce qui suit.

      User.rb

      class User < ApplicationRecord 
      
          before_save { self.email = email.downcase} 
          #attr_accessible :username, :email 
          validates_confirmation_of :password 
          has_secure_password 
      
          validates :username, presence: true, length: { maximum: 25 }, uniqueness: {case_sensitive: false} 
          VALID_EMAIL_REGEX = /\A[\w+\-.][email protected][a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i 
          validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX } 
          validates :password, presence: true, confirmation: true, length: { minimum: 6 } 
          validates :password_confirmation, presence: true 
      
          has_many :trips 
          has_many :countries, through: :trips 
          has_many :cities, through: :trips 
      
      end 
      

      Country.rb

      class Country < ApplicationRecord 
      
          has_many :trips 
          has_many :cities 
          has_many :users, through: :trips 
      
      end 
      

      City.rb

      class City < ApplicationRecord 
      
          has_many :trips 
          belongs_to :country 
      
      end 
      

      Trip.rb

      class Trip < ApplicationRecord 
      
          belongs_to :country 
          belongs_to :user 
          belongs_to :city 
      
      end 
      
    +0

    Pourriez-vous simplement créer deux tables de jointures standard: 'country_users' et' cities_users', et écrire une ** validation de modèle ** sur ce dernier pour affirmer que la ville a été assignée à l'utilisateur? –

    +0

    Remarque: Vous avez probablement besoin d'une sorte de logique de pré- ou de post-suppression pour éviter qu'un utilisateur soit ** désaffecté ** d'un pays, mais reste assigné aux villes. (c'est-à-dire soit interdisez de telles suppressions, soit supprimez les autres associations dans le cadre de la transaction.) –

    +0

    Vous ne pouvez pas affecter une ville à un utilisateur car un utilisateur possède plusieurs villes? Un utilisateur doit appartenir à une ville pour pouvoir attribuer une ville à un utilisateur! Ce que vous voulez vraiment, c'est attribuer des visites aux utilisateurs et affecter une ville à une visite. Mettez à jour votre question à celle qui a du sens et peut-être que je peux aider plus loin. Scrappez la simplification c'est vous causer des problèmes et vous faire croire que vous devriez être capable de faire des choses qui cassent vos exigences – jamesc

    Répondre

    0

    veulent être en mesure d'attribuer une ville à un utilisateur, tant que l'utilisateur a été affecté au pays associé à la ville

    Vous pouvez et vous ne devriez pas vouloir.

    Créer un pays. Créez une ville et attribuez-la à un pays. Créez un voyage ou une visite, attribuez cette visite ou un voyage à une ville, puis ajoutez des utilisateurs à ce voyage ou à cette visite. Voila! vous avez la relation dont vous avez besoin! Je pense.

    Une ville belongs_to un pays Un voyage belongs_to une ville Une visite belongs_to une ville A visites has_many utilisateur Un has_many utilisateur voyages Parce que les voyages et les visites appartient à une ville vous pouvez avoir has_many à travers les relations des deux côtés de la jointure donc les tables de visites et de voyages ont besoin de l'identifiant de la ville et de l'utilisateur Et vous avez les relations dont vous avez besoin et la logique dont vous avez besoin. N'essayez pas de faire des choses qui vont à l'encontre de vos exigences, comme de joindre directement des villes à des utilisateurs, car il est impossible d'assigner une ville à un utilisateur. En effet, juste pour vous donner un mal de tête encore plus grand, vous voudrez peut-être même avoir des voyages prenant de nombreuses visites dans de nombreuses villes dans de nombreux pays. Dans ce cas, vous souhaitez que les visites soient attribuées à un voyage et non à une ville.

    Ceci est un problème logique et non un problème de codage.

    MISE À JOUR En réponse aux commentaires

    class User < ApplicationRecord 
        has_many :trips 
        has_many :cities, through: :trips 
    end 
    
    class Trip < ApplicationRecord 
        belongs_to :user 
        belongs_to :city 
    end 
    
    class City < ApplicationRecord 
        has_many :trips 
        has_many :users, through: :trips 
    end 
    

    puis dans la console

    Loading development environment (Rails 5.1.4) 
    2.4.0 :001 > u = User.create(name: "Fred") 
        (0.0ms) begin transaction 
        SQL (0.3ms) INSERT INTO "users" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "Fred"], ["created_at", "2017-09-17 14:24:30.720005"], ["updated_at", "2017-09-17 14:24:30.720005"]] 
        (16.3ms) commit transaction 
    => #<User id: 1, name: "Fred", created_at: "2017-09-17 14:24:30", updated_at: "2017-09-17 14:24:30"> 
    2.4.0 :004 > c = City.create(name: "Leeds") 
        (0.1ms) begin transaction 
        SQL (0.4ms) INSERT INTO "cities" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "Leeds"], ["created_at", "2017-09-17 14:26:19.293166"], ["updated_at", "2017-09-17 14:26:19.293166"]] 
        (33.9ms) commit transaction 
    => #<City id: 1, name: "Leeds", created_at: "2017-09-17 14:26:19", updated_at: "2017-09-17 14:26:19"> 
    2.4.0 :005 > t = Trip.create(name: "T1", user: u, city: c) 
        (0.2ms) begin transaction 
        SQL (0.5ms) INSERT INTO "trips" ("user_id", "city_id", "name", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["user_id", 1], ["city_id", 1], ["name", "T1"], ["created_at", "2017-09-17 14:28:44.656390"], ["updated_at", "2017-09-17 14:28:44.656390"]] 
        (26.0ms) commit transaction 
    => #<Trip id: 1, user_id: 1, city_id: 1, name: "T1", created_at: "2017-09-17 14:28:44", updated_at: "2017-09-17 14:28:44"> 
    2.4.0 :006 > t.user.name# 
    => "Fred" 
    2.4.0 :007 > t.city.name 
    => "Leeds" 
    2.4.0 :008 > t.name 
    => "T1" 
    

    Voilà comment une has_many normale à travers des œuvres, mais vous voulez une des villes has_many sur le modèle de voyages et je vais donc mettre à jour comment OK, pour atteindre la prochaine étape où un voyage a beaucoup de villes et une ville a_many voyages notez le has_many sur les deux côtés de la relation que vous êtes va avoir besoin d'une nouvelle table de référence croisée probablement appelée trip_city_xref qui a une clé primaire composée de city_id et trip_id

    Ceci est dans les termes de Rails est une relation has_and_belongs_to_many que je déteste. C'est mon opinion seulement que has_and_belongs_to_many était sur la seule chose que Rails a eu tort. C'est un raccourci. D'autres ne seront pas d'accord, mais c'est comme cela que je l'établirais dans un langage qui n'aurait pas ce raccourci. Je pense que c'est plus clair et moins ambigu. Je voudrais donc mettre en place quelque chose comme ça

    class Trip < ApplicationRecord 
        belongs_to :user 
        belongs_to: :city_trip_xref and 
        has_many :cities, through: :city_trip_xref 
    end 
    
    class City < ApplicationRecord 
        has_many :trips, through: :city_trip_xref 
        has_many :users, through: :trips 
    end 
    
    class CityTripXref < ApplicationRecord 
        belongs_to :trip 
        belongs_to :city 
    end 
    

    qui est juste mon avis alors ne hésitez pas à aller lire sur has_and_belongs_to_many here Alors que faites-vous avec votre utilisateur maintenant? Encore une fois, je mettrai à jour bientôt

    MISE À JOUR User.cities Ainsi, votre modèle utilisateur pourrait ressembler à ceci

    class User < ApplicationRecord 
        has_many :trips 
        has_many :city_trip_xrefs, through: :trips 
        def cities 
        trips.map(&:city_trip_xrefs).flatten.map(&:city) 
        end 
    end 
    

    Pour obtenir toutes les villes d'un utilisateur visite sur un voyage que vous pouvez aplatir et cartographier les relations comme si u = User.first u.trips.map (&: city_trip_xrefs) .flatten.map (&: ville)

    alors vous pouvez faire une méthode sur votre classe utilisateur pour obtenir les villes en utilisant ce qui précède que montré dans le modèle de l'utilisateur ci-dessus. En ce qui concerne l'attribution d'une ville à un utilisateur, vous ne voudriez pas vraiment le faire directement de toute façon.Vous organisez un voyage, ajoutez des utilisateurs et des villes à ce voyage et vous avez toutes les fonctionnalités dont vous aurez probablement besoin dans un exemple réel à moins que vos exigences ne soient quelque peu différentes

    Mettre tout cela ensemble en utilisant les enregistrements créés ci-dessus dans la console ainsi qu'un enregistrement de la table de city_trip_xref pour rejoindre le nombre de nombreux voyages vers les villes pour une ville existante et voyage, vous pouvez maintenant faire

    d'abord créer la référence externe dans la console

    ctx = CityTripXref.new 
    => #<CityTripXref id: nil, city_id: nil, trip_id: nil, created_at: nil, updated_at: nil> 
    2.4.0 :008 > ctx.city = City.first 
    => #<City id: 1, name: "Leeds", created_at: "2017-09-17 14:26:19", updated_at: "2017-09-17 14:26:19"> 
    2.4.0 :009 > ctx.trip = Trip.first 
    => #<Trip id: 1, user_id: 1, city_id: 1, name: "T1", created_at: "2017-09-17 14:28:44", updated_at: "2017-09-17 14:28:44"> 
    2.4.0 :010 > ctx.save 
    

    suivant get le nom de la première ville lors du premier voyage du premier utilisateur

    2.4.0 :028 > u = User.first 
        User Load (0.1ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] 
    => #<User id: 1, name: "Fred", created_at: "2017-09-17 14:24:30", updated_at: "2017-09-17 14:24:30"> 
    2.4.0 :029 > u.cities.first.name 
        Trip Load (0.1ms) SELECT "trips".* FROM "trips" WHERE "trips"."user_id" = ? [["user_id", 1]] 
        CityTripXref Load (0.1ms) SELECT "city_trip_xrefs".* FROM "city_trip_xrefs" WHERE "city_trip_xrefs"."trip_id" = ? [["trip_id", 1]] 
        City Load (0.1ms) SELECT "cities".* FROM "cities" WHERE "cities"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] 
    => "Leeds" 
    2.4.0 :030 > 
    

    Espérons que cela a du sens.

    +0

    Okay donc pour éviter toute confusion supplémentaire (surtout de ma part) J'ai enlevé le modèle de visite. J'ai édité mon code ci-dessus à ce que je pense que vous suggérez. Cela ne fonctionne toujours pas, je ne peux pas assigner 'user1.cities << [city1]', mais il semble proche. –

    +0

    Bien sûr, vous ne pouvez pas. Vous devez passer par des voyages. Je ne pense pas que vous compreniez vraiment ce que vous voulez faire. Je mettrai à jour ma réponse rapidement avec ce que vous avez mis en place. Mais je ne pense toujours pas que c'est ce que tu veux. Personnellement, je reviendrais là-dessus après avoir écrit un cas d'utilisation UML ou au moins écrit une spécification – jamesc