Rails: Utilisation de build avec une association has_one dans les rails

143

Dans cet exemple, je crée un useravec non profile, puis je crée plus tard un profilepour cet utilisateur. J'ai essayé d'utiliser build avec une has_oneassociation mais ça a explosé. La seule façon dont je vois ce fonctionnement est d'utiliser has_many. Le userest censé n'en avoir au plus qu'un profile.

J'ai essayé ceci. J'ai:

class User < ActiveRecord::Base
  has_one :profile
end

class Profile < ActiveRecord::Base
  belongs_to :user
end

Mais quand je fais:

user.build_profile 

J'obtiens l'erreur:

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

Y a-t-il un moyen dans les rails d'avoir 0 ou 1 association?

espinet
la source
qu'avez-vous essayé exactement? pourriez-vous, s'il vous plaît, poster un code?
Ju Nogueira

Réponses:

359

La buildsignature de la méthode est différente pour les associations has_oneet has_many.

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

La syntaxe de construction pour l' has_manyassociation:

user.messages.build

La syntaxe de construction pour l' has_oneassociation:

user.build_profile  # this will work

user.profile.build  # this will throw error

Lisez la documentation de l' has_oneassociation pour plus de détails.

Harish Shetty
la source
28
La syntaxe différente du has_one me surprend toujours ... bon sang!
Galaxie
11
C'est drôle de voir comment la réponse la mieux notée et acceptée ici répond à une question différente de celle posée par l'OP.
Ajedi32
Soi-disant si l'utilisateur appartenait au profil (ce qui signifie que la table utilisateur a une clé étrangère profile_id dans sa table), la création d'un profil pour l'utilisateur fonctionnera comme mentionné ci-dessus, c'est-à-dire, mais pour une nouvelle action uniquement user.build_profile pour modifier user.build_profile if user.profile.nil? et si vous souhaitez créer un profil lors de la création de l'utilisateur, écrivez accepts_nested_attributes_for :profile-le dans Modèle utilisateur. et sous la forme de quel utilisateur est créé, écrivez <%= f.simple_fields_for :profile do |p| %>ceci et continuez.
zèle
mais pourquoi ce comportement différent a-t-il été conservé pour has_one ou has_many? Il y aurait une raison lors de la conception, je pense et j'attends.
curieux
@ Ajedi32 la réponse correspond au titre de la question mais pas au corps. Étant donné que ceci ( build_<association>) est un comportement assez étrange et inattendu dans Rails, il y a beaucoup plus de gens à la recherche de cette réponse que la réponse aux questions réelles, si vous voyez ce que je veux dire.
Max Williams
19

Jetez un œil au message d'erreur. Cela vous indique que vous n'avez pas de colonne obligatoire user_iddans la table de profil . La définition des relations dans le modèle n'est qu'une partie de la réponse.

Vous devez également créer une migration qui ajoute la user_idcolonne à la table de profil. Rails s'attend à ce que ce soit là et si ce n'est pas le cas, vous ne pouvez pas accéder au profil.

Pour plus d'informations, veuillez consulter ce lien:

Bases de l'association

sosborn
la source
1
Je viens de comprendre mon problème. Le livre dont j'apprends n'a pas très bien expliqué la création de clé étrangère. J'ai créé une nouvelle migration qui ajoute une clé étrangère à mon modèle. Merci.
espinet
Avez-vous besoin de créer la colonne vous-même à chaque fois? J'ai eu cette idée que cela se produisait automatiquement. Je ne sais pas d'où j'ai eu cette idée.
Rimian
Vous pouvez ajouter la colonne lors de la génération d'un modèle en utilisant la ligne de commande, quelque chose comme rails g model profile user:references:index address:string bio:text.
duykhoa le
1

Selon le cas d'utilisation, il peut être pratique d'encapsuler la méthode et de créer automatiquement l'association lorsqu'elle n'est pas trouvée.

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

maintenant appeler la #profileméthode retournera le profil associé ou créera une nouvelle instance.

source: Lorsque le singe corrige une méthode, pouvez-vous appeler la méthode remplacée à partir de la nouvelle implémentation?

Shiyason
la source
1
dans des rails de courant (testé sur 6.0.2.2) , vous pouvez simplifier à: def profile; super || build_profile; end.
glasz le
-14

Cela devrait être un has_one. Si cela buildne fonctionne pas, vous pouvez simplement utiliser new:

ModelName.new( :owner => @owner )

est le même que

@owner.model_names.build
Karl
la source
11
Ce n'est pas la même chose: si vous créez un nouveau nom_modèle avec build, lorsque @owner est enregistré, le nouveau nom_modèle sera également enregistré. Ainsi, vous pouvez utiliser build pour créer un parent et des enfants qui seront enregistrés ensemble. Ce n'est pas le cas si vous créez un nom_modèle avec .new
Max Williams