Was ist der richtige Weg, um eine Setter-Methode in Ruby on Rails zu überschreiben?

184

Ich verwende Ruby on Rails 3.2.2 und möchte wissen, ob das Folgende eine "richtige" / "richtige" / "sichere" Methode ist, um eine Setter-Methode für ein my class-Attribut zu überschreiben.

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self[:attribute_name] = value
end

Der obige Code scheint wie erwartet zu funktionieren. Aber ich möchte , wenn, wissen , durch den obigen Code verwendet wird , in Zukunft werde ich Probleme hat , oder zumindest, welche Probleme „soll ich erwarten“ / mit Ruby „könnte passieren“ on Rails . Wenn dies nicht der richtige Weg ist, eine Setter-Methode zu überschreiben, was ist der richtige Weg?


Hinweis : Wenn ich den Code verwende

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self.attribute_name = value
end

Ich erhalte folgende Fehlermeldung:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
quelle
4
Ich liebe die Terminologie "richtig" / "richtig" / "sicher". Wenn Sie es auf drei Arten angeben, wird wirklich sichergestellt, dass es keine Fehlinterpretationen gibt. Gut gemacht!
Jay
5
@ Jay - "Feinheit Italienismus"; -)
Backo
2
Um ganz klar zu sein, bezieht sich die "Stapelebene zu tief" auf die Tatsache, dass es sich um einen rekursiven Aufruf handelt ... um den Aufruf selbst.
Nippysaurus

Antworten:

294

================================================== ========================= Update: 19. Juli 2017

In der Rails-Dokumentation wird nun auch vorgeschlagen, Folgendes zu verwenden super:

class Model < ActiveRecord::Base

  def attribute_name=(value)
    # custom actions
    ###
    super(value)
  end

end

================================================== =========================

Ursprüngliche Antwort

Wenn Sie die Setter-Methoden für Spalten einer Tabelle beim Zugriff über Modelle überschreiben möchten, können Sie dies auf diese Weise tun.

class Model < ActiveRecord::Base
  attr_accessible :attribute_name

  def attribute_name=(value)
    # custom actions
    ###
    write_attribute(:attribute_name, value)
    # this is same as self[:attribute_name] = value
  end

end

Siehe Überschreiben von Standard-Accessoren in der Rails-Dokumentation.

Ihre erste Methode ist also die richtige Methode, um Spaltensetzer in Ruby of Rails-Modellen zu überschreiben. Diese Accessoren werden bereits von Rails bereitgestellt, um auf die Spalten der Tabelle als Attribute des Modells zuzugreifen. Dies nennen wir ActiveRecord ORM-Mapping.

Denken Sie auch daran, dass die attr_accessibleOberseite des Modells nichts mit Accessoren zu tun hat. Es hat eine ganz andere Funktion (siehe diese Frage )

Wenn Sie in reinem Ruby Accessoren für eine Klasse definiert haben und den Setter überschreiben möchten, müssen Sie die folgende Instanzvariable verwenden:

class Person
  attr_accessor :name
end

class NewPerson < Person
  def name=(value)
    # do something
    @name = value
  end
end

Dies wird leichter zu verstehen sein, wenn Sie wissen, was attr_accessortut. Der Code attr_accessor :nameentspricht diesen beiden Methoden (Getter und Setter).

def name # getter
  @name
end

def name=(value) #  setter
  @name = value
end

Auch Ihre zweite Methode schlägt fehl, da sie eine Endlosschleife verursacht, wenn Sie dieselbe Methode attribute_name=innerhalb dieser Methode aufrufen .

Rubyprince
quelle
9
Für Rails 4 einfach überspringen, attr_accessibleda es nicht mehr da ist und es funktionieren sollte
Zigomir
11
Warum nicht anrufen super?
Nathan Lilienthal
1
Ich hatte den Eindruck, dass Accessoren und Autoren, die dynamisch erstellt werden, supermöglicherweise nicht funktionieren. Aber es scheint nicht der Fall zu sein. Ich habe es gerade überprüft und es funktioniert für mich. Auch diese Frage stellen das gleiche
Rubyprince
4
Es gibt eine riesige Gotcha mit write_attribute. Konvertierungen werden übersprungen. Beachten Sie, dass write_attributeZeitzonen-Konvertierungen mit Datumsangaben übersprungen werden, was fast immer unerwünscht ist.
Tim Scott
2
Super wird auch funktionieren, aber es gibt einen Grund, warum Sie es uns vielleicht nicht wollen. Zum Beispiel in Mongoid Gem gibt es einen Fehler, bei dem Sie nicht auf das Array pushen können, wenn Sie die Getter-Methode super. Es ist ein Fehler, weil auf diese Weise das Array im Speicher verwaltet wird. Außerdem gibt der @name auch den eingestellten Wert zurück und ruft dann die Methode auf, die Sie überschreiben. In der obigen Lösung funktionieren beide jedoch einwandfrei.
Newdark-It
44

Verwenden Sie das superSchlüsselwort:

def attribute_name=(value)
  super(value.some_custom_encode)
end

Um den Leser zu überschreiben:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
quelle
1
Bessere Antwort als die akzeptierte IMO, da der Methodenaufruf auf denselben Namen beschränkt bleibt. Dies bewahrt das geerbte überschriebene Verhalten von attribute_name =
Andrew Schwartz
Das Überschreiben der Getter-Methode ist in Rails 4.2 aufgrund dieser Änderung gefährlich geworden: github.com/rails/rails/commit/… Bisher haben Formular-Helfer den untypecast-Wert des Felds aufgerufen und nicht Ihren benutzerdefinierten Getter. Jetzt rufen sie Ihre Methode auf und führen so zu verwirrenden Ergebnissen in Ihren Formularen, je nachdem, wie Sie den Wert überschreiben.
Brendon Muir
16

In Schienen 4

lassen Sie sagen , Sie haben Altes Attribut in der Tabelle

def age=(dob)   
    now = Time.now.utc.to_date
    age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
    super(age) #must add this otherwise you need to add this thing and place the value which you want to save. 
  end

Hinweis: Für Neuankömmlinge in Rails 4 müssen Sie im Modell nicht attr_accessible angeben . Stattdessen müssen Sie Ihre Attribute auf Controller-Ebene mithilfe der Genehmigungsmethode auf die weiße Liste setzen .

Taimoor Changaiz
quelle
3

Ich habe festgestellt, dass (zumindest für ActiveRecord-Beziehungssammlungen) das folgende Muster funktioniert:

has_many :specialties

def specialty_ids=(values)
  super values.uniq.first(3)
end

(Dies erfasst die ersten 3 nicht doppelten Einträge im übergebenen Array.)

Robin Daugherty
quelle
0

Verwenden attr_writerzum Überschreiben des Setters attr_writer: attribute_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
bananaappletw
quelle