Rails: criar na associação has_one

100

Olá (grande novato em Rails aqui), eu tenho os seguintes modelos:

class Shop < ActiveRecord::Base
  belongs_to :user
  validates_uniqueness_of :title, :user_id, :message => "is already being used"
end

e

class User < ActiveRecord::Base
  has_one :shop, :dependent => :destroy
end

Quando estou prestes a criar uma nova loja, recebo o seguinte erro:

private method `create' called for nil:NilClass

Este é meu controlador:

@user = current_user
@shop = @user.shop.create(params[:shop])

Eu tentei variações diferentes lendo guias e tutoriais aqui e ali, mas estou mais confuso do que antes e não consigo fazer funcionar. Qualquer ajuda seria muito apreciada.

Neko
fonte
Título da pergunta editado para refletir a pergunta. Duplicado de Usar build com associação has_one em trilhos
Marc-André Lafortune
1
você também pode usar@user.build_shop(params)
ImranNaqvi

Respostas:

123

Em primeiro lugar, aqui está como fazer o que você deseja:

@user = current_user
@shop = Shop.create(params[:shop])
@user.shop = @shop

Veja por que sua versão não funcionou:

Você provavelmente pensou que este trabalho poder, porque se o usuário tinha uma has_manyrelação Shop, @user.shops.create(params[:shop]) iria trabalhar. No entanto, há uma grande diferença entre has_manyrelações e has_onerelações:

Com uma has_manyrelação, shopsretorna um objeto de coleção ActiveRecord, que possui métodos que você pode usar para adicionar e remover lojas de / para um usuário. Um desses métodos é create, que cria uma nova loja e a adiciona ao usuário.

Com uma has_onerelação, você não recebe de volta esse objeto de coleção, mas simplesmente o objeto Loja que pertence ao usuário - ou nulo se o usuário ainda não tiver uma loja. Uma vez que nem objetos Shop nem nil têm um createmétodo, você não pode usar createessa forma com has_onerelações.

sepp2k
fonte
Obrigado pela sua resposta, sepp2k. Agora vejo porque meu código não funcionou.
Neko de
118
Você também pode usar @user.create_shop(params[:shop]). Veja os métodos adicionados por has_one .
nates de
A resposta escolhida funciona, mas a solução @nates também funciona. +1 para vocês dois.
nfriend21
+1 na resposta porque estava me perguntando o mesmo, +1 na resposta para explicar o porquê e +1 no comentário por dar a melhor solução.
divido
224

Uma maneira mais concisa de fazer isso é com:

@user.create_shop(params[:shop])

Veja os métodos adicionados por has_one nos guias do Ruby on Rails.

Nates
fonte
6
Esta é definitivamente uma abordagem mais melhor
Magnum
7
Esteja ciente de que se você criar_shop mais de uma vez, a loja anterior será excluída. Por exemplo, se você executá- @user.create_shop(params[:shop_one_info])lo, criará shop_one, MAS se você executar, @user.create_shop(params[:shop_two_info])ele excluirá a primeira loja e criará a segunda.
ecoding5
O comentário acima sobre deletar a loja anterior é para Rails 3.2.18, não sei sobre versões mais recentes. Não é possível editar o comentário após 5 min -_-
ecoding5
Encontrei uma solução, não defini exclusividade no modelo associado, portanto, certifique-se de fazer como está configurado no modelo de Loja deste exemplo.
ecoding5
você também pode usar@user.build_shop(params)
ImranNaqvi
7

Mais duas maneiras, se você quiser, em savevez de create:

shop = @user.build_shop
shop.save

shop = Show.new
shop.user = @user
shop.save
Companheiro estranho
fonte
1

Apenas para adicionar às respostas acima -

@user.create_shop(params[:shop])

A sintaxe acima cria um novo registro, mas subsequentemente exclui um registro existente semelhante.

Como alternativa, se você não quiser acionar o callback delete

Shop.create(user_id: user.id, title: 'Some unique title')

Este tópico pode ser útil. Clique aqui

Rais
fonte