web-dev-qa-db-fra.com

Dans Rails, quel est le meilleur moyen de mettre à jour un enregistrement ou d'en créer un nouveau s'il n'existe pas?

J'ai une instruction de création pour certains modèles, mais elle crée un enregistrement dans une table de jointure, que l'enregistrement existe déjà ou non.

Voici à quoi ressemble mon code:

@user = User.find(current_user)
@event = Event.find(params[:id])
for interest in @event.interests
 @user.choices.create(:interest => interest, :score => 4)
end

Le problème est que cela crée des enregistrements, peu importe quoi. Je voudrais qu’il crée un disque seulement si aucun disque n’existe déjà; si un enregistrement existe, j'aimerais qu'il prenne l'attribut de l'enregistrement trouvé et ajoute ou soustrait 1.

J'ai regardé autour de moi et vu quelque chose appelé find_or_create_by. Qu'est-ce que cela fait quand il trouve un disque? Je voudrais qu'il prenne l'attribut actuel :score et ajoute 1.

Est-il possible de trouver ou de créer avec id? Je ne suis pas sûr de l’attribut que je trouverais, car le modèle que j’examine est un modèle de jointure qui ne contient que id clés étrangères et l’attribut score.

J'ai essayé

@user.choices.find_or_create_by_user(:user => @user.id, :interest => interest, :score => 4)

mais j'ai

méthode non définie find_by_user

Que devrais-je faire?

26
ChrisWesAllen

En supposant que le modèle Choice ait un user_id (à associer à un utilisateur) et un interest_id (à associer à un intérêt), quelque chose comme ceci devrait faire l'affaire:

@user = User.find(current_user)
@event = Event.find(params[:id])

@event.interests.each do |interest|
  choice = @user.choices.find_or_initialize_by_interest_id(interest.id) do |c|
    c.score = 0 # Or whatever you want the initial value to be - 1
  end

  choice.score += 1
  choice.save!
end

Quelques notes:

  1. Vous n'avez pas besoin d'inclure la colonne user_id dans le find_or_*_by_*, car vous avez déjà indiqué à Rails de n'extraire que choices appartenant à @user.
  2. J'utilise find_or_initialize_by_*, qui est essentiellement identique à find_or_create_by_*, la seule différence étant que initialize ne crée pas réellement l'enregistrement. Ce serait similaire à Model.new par opposition à Model.create.
  3. Le bloc qui définit c.score = 0 n'est exécuté que si l'enregistrement n'existe pas pas
  4. choice.score += 1 mettra à jour la valeur du score pour l'enregistrement, qu'il existe ou non. Par conséquent, le score par défaut c.score = 0 devrait être la valeur initiale moins un.
  5. Enfin, choice.save! mettra à jour l'enregistrement (s'il existait déjà) ou créera l'enregistrement initié (s'il ne l'était pas).
23
vonconrad
my_class = ClassName.find_or_initialize_by_name(name)

my_class.update_attributes({
   :street_address => self.street_address,
   :city_name => self.city_name,
   :Zip_code => self.Zip_code
})
60
krunal shah

find_or_create_by_user_id sonne mieux

7
apneadiving

De plus, dans Rails 3 vous pouvez faire:

@user.choices.where(:user => @user.id, :interest => interest, :score => 4).first_or_create
3
Zuhaib Ali

Si vous utilisez Rails 4, je ne pense pas qu'il crée les méthodes du Finder comme auparavant, par conséquent, find_or_create_by_user n'est pas créé pour vous. Au lieu de cela, vous le feriez comme ceci: 

@user = User.find(current_user)
@event = Event.find(params[:id])
for interest in @event.interests
 @user.choices.find_or_create_by(:interest => interest) do |c|
  c.score ||= 0
  c.score += 1
 end
end
0
rainkinz

Dans Rails 4 Vous pouvez utiliser find_or_create_by pour obtenir un objet (s'il n'existe pas, il le créera), puis utiliser update pour enregistrer ou mettre à jour l'enregistrement. existe, sinon met à jour l'enregistrement. 

Par exemple

@edu = current_user.member_edu_basics.find_or_create_by(params.require(:member).permit(:school))

if @edu.update(params.require(:member).permit(:school, :majoy, :started, :ended))
0
Albert.Qing