Rails: Usando build com uma associação has_one no Rails

143

Neste exemplo, eu crio um usersem profile, e depois crio um profilepara esse usuário. Eu tentei usar o build com uma has_oneassociação, mas isso explodiu. A única maneira de ver esse trabalho é usando has_many. O useré suposto só tem no máximo um profile.

Eu tenho tentado isso. Eu tenho:

class User < ActiveRecord::Base
  has_one :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
end

Mas quando eu faço:

user.build_profile 

Eu recebo o erro:

ActiveRecord::StatementInvalid: Mysql::Error: Unknown column 'profiles.user_id' in 'where clause': SELECT * FROM `profiles` WHERE (`profiles`.user_id = 4)  LIMIT 1

Existe uma maneira nos trilhos de ter 0 ou 1 associação?

espinet
fonte
o que exatamente você tentou? você poderia postar algum código?
Ju Nogueira

Respostas:

359

A buildassinatura do método é diferente para has_onee has_manyassociações.

class User < ActiveRecord::Base
  has_one :profile
  has_many :messages
end

A sintaxe de construção para has_manyassociação:

user.messages.build

A sintaxe de construção para has_oneassociação:

user.build_profile  # this will work

user.profile.build  # this will throw error

Leia a documentação da has_oneassociação para obter mais detalhes.

Harish Shetty
fonte
28
A sintaxe diferente para o has_one sempre me chama a atenção ... caramba!
Galaxy
11
É engraçado como a resposta mais bem avaliada e aceita aqui está respondendo a uma pergunta diferente daquela que o OP fez.
Ajedi32
Supostamente, se o usuário pertencesse ao perfil (o que significa que a tabela do usuário possui uma chave estrangeira profile_id em sua tabela), também a criação de perfil para o usuário funcionará como mencionado acima, isto é, mas para uma nova ação apenas user.build_profile para edição user.build_profile if user.profile.nil? e se você deseja criar um perfil enquanto cria o usuário, escreva accepts_nested_attributes_for :profile-o em Modelo de usuário. e na forma em que usuário está sendo criado, escreva <%= f.simple_fields_for :profile do |p| %>isso e continue.
zelo
mas por que esse comportamento diferente foi mantido para has_one ou has_many? Haveria alguma razão ao projetar, penso e espero.
inquisitivo
@ Ajedi32 a resposta corresponde ao título da pergunta, mas não ao corpo. Dado que este ( build_<association>) é um comportamento bastante estranho e inesperado no Rails, há muito mais pessoas procurando por essa resposta do que a resposta das perguntas reais, se você entende o que quero dizer.
Max Williams
19

Dê uma boa olhada na mensagem de erro. Ele está dizendo que você não possui uma coluna obrigatória user_idna tabela de perfis . Definir os relacionamentos no modelo é apenas parte da resposta.

Você também precisa criar uma migração que adicione a user_idcoluna à tabela de perfis. O Rails espera que isso esteja lá e, se não estiver, você não poderá acessar o perfil.

Para mais informações, consulte este link:

Noções básicas de associação

sosborn
fonte
1
Acabei de descobrir o meu problema. O livro do qual estou aprendendo não explicou muito bem a criação de chave estrangeira. Criei uma nova migração que adiciona uma chave estrangeira ao meu modelo. obrigado.
espinet
Você precisa criar a coluna sempre? Eu tinha a ideia de que isso acontecia automaticamente. Não sei de onde tirei essa ideia.
Rimian
Você pode adicionar a coluna ao gerar um modelo usando a linha de comando, algo como rails g model profile user:references:index address:string bio:text.
duykhoa 25/06
1

Dependendo do caso de uso, pode ser conveniente agrupar o método e criar automaticamente a associação quando não for encontrada.

old_profile = instance_method(:profile)
define_method(:profile) do
  old_profile.bind(self).call || build_profile
end

agora, chamar o #profilemétodo retornará o perfil associado ou criará uma nova instância.

fonte: Ao aplicar um patch a um método, você pode chamar o método substituído da nova implementação?

Shiyason
fonte
1
em trilhos de corrente (testado em 6.0.2.2), você pode simplificar isso: def profile; super || build_profile; end.
glasz 24/04
-14

Deve ser um has_one. Se buildnão estiver funcionando, você pode simplesmente usar new:

ModelName.new( :owner => @owner )

é o mesmo que

@owner.model_names.build
Karl
fonte
11
Não é o mesmo: se você criar um novo model_name com build, quando @owner for salvo, o novo model_name também será salvo. Portanto, você pode usar o build para criar pais e filhos que serão salvos juntos. Este não é o caso se você fizer um nome_do_modelo com .new
Max Williams