Rails a_and_belongs_to_many migration

120

J'ai deux modèles restaurantet userje souhaite établir une relation has_and_belongs_to_many.

J'ai déjà parcouru les fichiers de modèle et ajouté le has_and_belongs_to_many :restaurantsethas_and_belongs_to_many :users

Je suppose qu'à ce stade, je devrais être capable de faire quelque chose comme avec Rails 3:

rails generate migration ....

mais tout ce que j'ai essayé semble échouer. Je suis sûr que c'est quelque chose de vraiment simple. Je suis nouveau sur les rails, donc j'apprends toujours.

dbslone
la source

Réponses:

260

Vous devez ajouter une table de jointure distincte avec uniquement un restaurant_idet user_id(pas de clé primaire), par ordre alphabétique .

Exécutez d'abord vos migrations, puis modifiez le fichier de migration généré.

Rails 3

rails g migration create_restaurants_users_table

Rails 4 :

rails g migration create_restaurants_users

Rails 5

rails g migration CreateJoinTableRestaurantUser restaurants users

À partir de la documentation :

Il existe également un générateur qui produira des tables de jointure si JoinTable fait partie du nom:


Votre fichier de migration (notez le :id => false; c'est ce qui empêche la création d'une clé primaire):

Rails 3

class CreateRestaurantsUsers < ActiveRecord::Migration
  def self.up
    create_table :restaurants_users, :id => false do |t|
        t.references :restaurant
        t.references :user
    end
    add_index :restaurants_users, [:restaurant_id, :user_id]
    add_index :restaurants_users, :user_id
  end

  def self.down
    drop_table :restaurants_users
  end
end

Rails 4

class CreateRestaurantsUsers < ActiveRecord::Migration
  def change
    create_table :restaurants_users, id: false do |t|
      t.belongs_to :restaurant
      t.belongs_to :user
    end
  end
end

t.belongs_tocréera automatiquement les indices nécessaires. def changedétectera automatiquement une migration en avant ou en arrière, pas besoin de haut / bas.

Rails 5

create_join_table :restaurants, :users do |t|
  t.index [:restaurant_id, :user_id]
end

Remarque: Il existe également une option pour un nom de table personnalisé qui peut être passé en tant que paramètre à create_join_table appelé table_name. À partir des documents

Par défaut, le nom de la table de jointure provient de l'union des deux premiers arguments fournis à create_join_table, par ordre alphabétique. Pour personnaliser le nom de la table, fournissez une option: table_name:

Dex
la source
8
@Dex - Juste par curiosité, pourriez-vous expliquer pourquoi vous utilisez un deuxième index composé, défini avec l'ordre des colonnes inversé? J'avais l'impression que l'ordre des colonnes n'avait pas d'importance. Je ne suis pas DBA, je veux juste approfondir ma propre compréhension. Merci!
plainjimbo
11
@Jimbo Vous n'en avez pas besoin de cette façon, cela dépend vraiment de vos requêtes. Les index sont lus de gauche à droite, le premier sera donc le plus rapide si vous effectuez une recherche restaurant_id. Le second vous aidera si vous recherchez user_id. Si vous recherchez les deux, je pense que la base de données serait suffisamment intelligente pour n'en avoir besoin que d'un. Donc je suppose que le second n'a pas vraiment besoin d'être composé. Ce n'était plus qu'un exemple. C'était une question Rails cependant, donc publier dans la section DB donnerait une réponse plus complète.
Dex
3
Le deuxième index a une certaine redondance - tant que vous interrogez à la fois sur restaurant_id et user_id, leur ordre dans votre SQL n'a pas d'importance et le premier index sera utilisé. Il sera également utilisé si vous interrogez uniquement sur restaurant_id. Le deuxième index doit uniquement être sur: user_id, et serait utilisé dans les cas où vous interrogez uniquement sur user_id (ce que le premier index n'aiderait pas en raison de l'ordre de ses clés).
Yardboy
13
Dans les rails 4, la migration doit se faire rails g migration create_restaurants_userssans table à la fin.
Fa11enAngel
6
Vous pouvez également utiliser les rails g migration CreateJoinTableRestaurantUser utilisateur du restaurant. Lire guides.rubyonrails.org/migrations.html#creating-a-join-table
eKek0
36

Les réponses ici sont assez datées. Depuis Rails 4.0.2, vos migrations utilisent create_join_table.

Pour créer la migration, exécutez:

rails g migration CreateJoinTableRestaurantsUsers restaurant user

Cela générera les éléments suivants:

class CreateJoinTableRestaurantsUsers < ActiveRecord::Migration
  def change
    create_join_table :restaurants, :users do |t|
      # t.index [:restaurant_id, :user_id]
      # t.index [:user_id, :restaurant_id]
    end
  end
end

Si vous souhaitez indexer ces colonnes, décommentez les lignes respectives et vous êtes prêt à partir!

Jan Klimo
la source
2
En général, je décommenterais l'une des lignes d'index et y ajouterais unique: true. Cela évitera la création de relations en double.
Toby 1 Kenobi
26

Lors de la création de la table de jointure, portez une attention particulière à l'exigence selon laquelle les deux tables doivent être répertoriées par ordre alphabétique dans le nom / la classe de migration. Cela peut facilement vous mordre si vos noms de modèles sont similaires, par exemple "abc" et "abb". Si tu devais courir

rails g migration create_abc_abb_table

Vos relations ne fonctionneront pas comme prévu. Tu dois utiliser

rails g migration create_abb_abc_table

au lieu.

shacker
la source
1
Qu'est-ce qui vient en premier? foo ou foo_bar?
B Seven
1
Ran dans une console de rails: ["foo_bar", "foo", "foo bar"]. Sort # => ["foo", "foo bar", "foo_bar"] Le tri Unix revient de la même manière.
Shadoath
6

Pour les relations HABTM, vous devez créer une table de jointure. Il n'y a qu'une table de jointure et cette table ne doit pas avoir de colonne id. Essayez cette migration.

def self.up
  create_table :restaurants_users, :id => false do |t|
    t.integer :restaurant_id
    t.integer :user_id
  end
end

def self.down
  drop_table :restaurants_users
end

Vous devez vérifier ces didacticiels du guide des rails de relation

Mohit Jain
la source
Je ne pense pas que vous ayez besoin d'un modèle et je ne vois rien dans le lien sur le besoin d'un modèle pour une relation HABTM.
B Seven
1
Pour accélérer les requêtes générées, ajoutez des index aux champs id.
Martin Röbert