Rails has_and_belongs_to_many migration

119

Eu tenho dois modelos restauranteuser desejo realizar um relacionamento has_and_belongs_to_many.

Já entrei nos arquivos de modelo e adicionei o has_and_belongs_to_many :restaurantsehas_and_belongs_to_many :users

Presumo que neste ponto devo ser capaz de fazer algo como com Rails 3:

rails generate migration ....

mas tudo o que tentei parece falhar. Tenho certeza de que é algo muito simples. Sou novo em trilhos, então ainda estou aprendendo.

dbslone
fonte

Respostas:

260

Você precisa adicionar uma tabela de junção separada com apenas um restaurant_ide user_id(sem chave primária), em ordem alfabética .

Primeiro execute suas migrações e, em seguida, edite o arquivo de migração gerado.

Rails 3

rails g migration create_restaurants_users_table

Trilhos 4 :

rails g migration create_restaurants_users

Rails 5

rails g migration CreateJoinTableRestaurantUser restaurants users

Dos documentos :

Há também um gerador que produzirá tabelas de junção se JoinTable fizer parte do nome:


Seu arquivo de migração (observe o :id => false; é o que impede a criação de uma chave primária):

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_toirá criar automaticamente os índices necessários. def changeirá detectar automaticamente uma migração para frente ou para trás, sem necessidade de up / down.

Rails 5

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

Nota: Também existe uma opção para um nome de tabela customizado que pode ser passado como um parâmetro para criar_join_tabela chamada table_name. Dos documentos

Por padrão, o nome da tabela de junção vem da união dos dois primeiros argumentos fornecidos para create_join_table, em ordem alfabética. Para personalizar o nome da tabela, forneça uma opção: table_name:

Dex
fonte
8
@Dex - Só por curiosidade, você poderia explicar por que está usando um segundo índice composto, definido com a ordem reversa das colunas? Tive a impressão de que a ordem das colunas não importava. Não sou DBA, apenas quero aprofundar meu próprio entendimento. Obrigado!
plainjimbo,
11
@Jimbo Você não precisa assim, depende muito das suas dúvidas. Os índices são lidos da esquerda para a direita, então o primeiro será mais rápido se você estiver pesquisando restaurant_id. O segundo ajudará se você estiver procurando user_id. Se você estiver pesquisando em ambos, acho que o banco de dados seria inteligente o suficiente para precisar de apenas um. Então eu acho que o segundo realmente não precisa ser combinado. Este foi mais apenas um exemplo. Essa era uma pergunta do Rails, portanto, postar na seção DB renderia uma resposta mais completa.
Dex
3
O segundo índice tem alguma redundância - contanto que você esteja consultando em restaurant_id e user_id, a ordem deles em seu SQL não importa e o primeiro índice será usado. Ele também será usado se você estiver apenas consultando em restaurant_id. O segundo índice só precisa estar em: user_id, e seria usado nos casos em que você está apenas consultando user_id (o que o primeiro índice não ajudaria devido à ordem de suas chaves).
Yardboy de
13
Nos trilhos 4 a migração deve ser rails g migration create_restaurants_userssem mesa no final.
Fa11enAngel
6
Você também pode usar o usuário do restaurante CreateJoinTableRestaurantUser do rails g migration. Leia guias.rubyonrails.org/migrations.html#creating-a-join-table
eKek0
36

As respostas aqui são bastante antigas. A partir do Rails 4.0.2, suas migrações fazem uso de create_join_table.

Para criar a migração, execute:

rails g migration CreateJoinTableRestaurantsUsers restaurant user

Isso irá gerar o seguinte:

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

Se você deseja indexar essas colunas, descomente as respectivas linhas e pronto!

Jan Klimo
fonte
2
Eu geralmente descomente uma das linhas de índice e adiciono unique: truea ela. Isso evitará que relacionamentos duplicados sejam criados.
Toby 1 Kenobi
26

Ao criar a tabela de junção, preste muita atenção ao requisito de que as duas tabelas precisam ser listadas em ordem alfabética no nome / classe de migração. Isso pode incomodar você facilmente se os nomes de seus modelos forem semelhantes, por exemplo, "abc" e "abb". Se você fosse correr

rails g migration create_abc_abb_table

Suas relações não funcionarão como esperado. Você deve usar

rails g migration create_abb_abc_table

em vez de.

shacker
fonte
1
O que vem primeiro? foo ou foo_bar?
B Sete
1
Executado em um console Rails: ["foo_bar", "foo", "foo bar"]. Sort # => ["foo", "foo bar", "foo_bar"] A classificação Unix surge da mesma forma.
Shadoath de
6

Para relacionamentos HABTM, você precisa criar uma tabela de junção. Existe apenas uma tabela de junção e essa tabela não deve ter uma coluna de id. Experimente esta migração.

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

Você deve verificar estes tutoriais de guia de trilhos de relacionamento

Mohit Jain
fonte
Não acho que você precisa de um modelo e não vejo nada no link sobre a necessidade de um modelo para um relacionamento HABTM.
B Sete
1
Para agilizar as consultas geradas, adicione índices aos campos de id.
Martin Röbert