Schienen: Aktualisieren Sie das Modellattribut, ohne Rückrufe aufzurufen

75

Ich habe ein Benutzermodell mit dem Attribut: Credits. Ich möchte eine einfache Schaltfläche, die den Credits des Benutzers 5 über eine Route mit dem Namen "add" hinzufügt, sodass / users / 3 / add 5 Credits der Benutzer-ID = 3 hinzufügt.

def add
    @user = User.find(params[:id])
    @user.credits += 5
    redirect_to root_path
end

Das ist der relevante Teil meines Controllers. Das Problem ist, dass ich @ user.save nicht aufrufen möchte, da ich einen before_save-Rückruf habe, der das Kennwort des Benutzers basierend auf der aktuellen UTC-Zeit neu verschlüsselt. Ich möchte einfach nur 5 zum Attribut hinzufügen und den Rückruf vermeiden. Ich hätte nie gedacht, dass so eine einfache Sache so schwer sein könnte.

BEARBEITEN:

Ich habe den Rückruf geändert in: before_create, hier ist mein neuer Controller-Code (relevanter Teil):

  def add
    @user = User.find(params[:id])
    @user.add_credits(5)
    @user.save
    flash[:success] = "Credits added!"
    redirect_to root_path
  end

und hier ist mein Code im Modell:

 def add_credits(num)
    self.credits = num
 end

EDIT 2:

Ok, es war ein Validierungsproblem, das dazu führte, dass die Änderungen in "EDIT" nicht funktionierten, aber ich würde trotzdem gerne eine Antwort auf die ursprüngliche Frage der Aktualisierung ohne Rückrufe erhalten!

Sam Stern
quelle
Ich habe einen Link mit einer Liste der Methoden bereitgestellt, die keine Rückrufe auslösen, und sowohl Finbarr als auch ich haben vorgeschlagen, einen bedingten Rückruf zu verwenden. Welche zusätzlichen Lösungen suchen Sie?
Dave Newton

Antworten:

140

Rails 3.1 eingeführt update_column, das ist das gleiche wie update_attribute, aber ohne Validierungen oder Rückrufe auszulösen:

http://apidock.com/rails/ActiveRecord/Persistence/update_column

cvshepherd
quelle
Ich würde dies gerne verwenden, aber ich kann nicht sauber auf Rails 3.1 upgraden, oder zumindest weiß ich nicht wie.
Sam Stern
1
Welche Rails-Version verwenden Sie gerade?
cvshepherd
13

Als allgemeine Antwort ist dies in Rails 4 eine einfache Möglichkeit, Attribute zu aktualisieren, ohne Rückrufe auszulösen:

@user.update_column 'credits', 5

Wenn Sie mehrere Attribute aktualisieren müssen, ohne Rückrufe auszulösen:

@user.update_columns credits: 5, bankrupt: false  

Es gibt andere Optionen hier in den Rails Guides, wenn Sie es vorziehen, aber ich fand diesen Weg am einfachsten.

Matt
quelle
4
user.update_column Credits: 5 funktioniert nicht. Es sollte user.update_column 'Credits' sein, 5
Richard H Fung
Ich benutze Rails 4.2 und das funktioniert definitiv für mich. Diese Zeile generiert die folgende Abfrage:UPDATE "users" SET "credits" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["credits", 5], ["updated_at", "2017-04-01 21:34:52.746626"], ["id", 1]]
Matt
8

Um mehrere Attribute ohne Rückruf zu aktualisieren, können Sie update_all in Ihrem Modell folgendermaßen verwenden:

self.class.update_all({name: value, name: value}, self.class.primary_key => id)

Wenn Sie wirklich möchten, können Sie sogar eine update_columns-Methode ausprobieren und diese mit Ihrer aktiven Datensatzbasisklasse mischen.

Um ein Attribut zu aktualisieren, können Sie update_column verwenden. Darüber hinaus finden Sie einige spezifische Methoden in den Schienenhandbüchern http://guides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks

Matt Smith
quelle
6

Für Mongoid habe ich schließlich http://mongoid.org/en/mongoid/docs/persistence.html verwendet. Insbesondere können Sie Folgendes verwenden:

person.set(name:"Robert Pulson")

und es wird kein Rückruf ausgegeben. So cool.

hb922
quelle
seine person.set (: Name, "Robert Pulson")
Jude Calimbas
2
@ JudeCalimbas nein, ist es nicht, ArgumentError: falsche Anzahl von Argumenten (gegeben 2, erwartet 1)
Rept
3

Ich denke, Sie sollten in diesem Fall die Methode update_counters verwenden . Verwenden Sie es so in Ihrer Controller-Aktion:

def add
  User.update_counters params[:id], :credits => 5
  redirect_to root_path
end
DanneManne
quelle
2

Sie sollten update_all verwenden können , um das Auslösen von Rückrufen zu vermeiden.

def add
 @user = User.find(params[:id])
 User.where(:id=>@user.id).update_all(:credits => @user.credits+5)
 redirect_to root_path
end

Ich würde es vorziehen, diese Logik in das Modell aufzunehmen, aber dies sollte funktionieren, um Ihr ursprüngliches Problem zu lösen, wie es in der Steuerung angegeben ist.

miked
quelle
1

Möglicherweise sollte Ihr anderer before_save-Hook prüfen, ob sich das Kennwort des Benutzers tatsächlich geändert hat, bevor Sie es erneut verschlüsseln.

Finbarr
quelle
1

Sie haben eine Reihe von Optionen, einschließlich der Änderung des von Ihnen verwendeten Rückrufs, z after_create.

Sie können Spalten aktualisieren, ohne Rückrufe auszulösen (siehe Überspringen von Rückrufen im AR-Handbuch). Löst beispielsweise update_columnkeine Rückrufe aus. Der vorherige Link listet nicht auslösende Funktionen auf.

Sie können auch eines der bedingten Rückrufformulare (oder sogar einen Beobachter) verwenden, wenn das Kennwort geändert wird. Siehe ActiveModel :: Dirty , z @user.password_changed?.

Dave Newton
quelle
Ok, ich habe es in "after_create" geändert, aber es wird immer noch nicht funktionieren! In meiner Bearbeitung finden Sie meinen neuen Code. Ich kann keine Credits zum Ändern erhalten.
Sam Stern
@hatboysam Nun, es macht etwas anderes - bevor Sie hinzugefügt haben, setzen Sie jetzt. Verwenden save!Sie diese Option, um festzustellen, ob eine Ausnahme vorliegt (und / oder überprüfen Sie die Protokolle und / oder entfernen Sie die Schalldämpfer für die Rückverfolgung).
Dave Newton
0

Sie können dabei eine Spalte aktualisieren

User.where (Name: 'Lilie') .update_all (Alter: '10')

Abdul Monam
quelle