454 Stimmen

Geländer: Wie kann ich Standardwerte in ActiveRecord setzen?

Wie kann ich einen Standardwert in ActiveRecord festlegen?

Ich sehe einen Beitrag von Pratik, der ein hässliches, kompliziertes Stückchen Code beschreibt: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

class Item < ActiveRecord::Base  
  def initialize_with_defaults(attrs = nil, &block)
    initialize_without_defaults(attrs) do
      setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
        !attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
      setter.call('scheduler_type', 'hotseat')
      yield self if block_given?
    end
  end
  alias_method_chain :initialize, :defaults
end

Beim Herumgoogeln habe ich die folgenden Beispiele gesehen:

  def initialize 
    super
    self.status = ACTIVE unless self.status
  end

und

  def after_initialize 
    return unless new_record?
    self.status = ACTIVE
  end

Ich habe auch schon Leute gesehen, die es in ihre Migration eingebaut haben, aber ich würde es lieber im Modellcode definiert sehen.

Gibt es einen kanonischen Weg, um Standardwert für Felder in ActiveRecord-Modell festlegen?

585voto

Jeff Perrin Punkte 7974

Es gibt verschiedene Probleme mit jeder der verfügbaren Methoden, aber ich glaube, dass die Definition einer after_initialize Callback ist aus den folgenden Gründen der richtige Weg:

  1. default_scope initialisiert Werte für neue Modelle, aber das wird dann der Bereich, in dem Sie das Modell finden. Wenn Sie nur einige Zahlen auf 0 initialisieren wollen, ist dies no was Sie wollen.
  2. Die Festlegung von Standardwerten in Ihrer Migration funktioniert auch teilweise... Wie bereits erwähnt wurde, wird dies no funktionieren, wenn Sie einfach Model.new aufrufen.
  3. Übergeordnetes Recht initialize kann funktionieren, aber vergessen Sie nicht, die super !
  4. Die Verwendung eines Plugins wie das von phusion wird ein bisschen lächerlich. Dies ist Ruby, brauchen wir wirklich ein Plugin, nur um einige Standardwerte zu initialisieren?
  5. Übergeordnetes Recht after_initialize ist veraltet ab Rails 3. Wenn ich überschreiben after_initialize in Rails 3.0.3 erhalte ich die folgende Warnung in der Konsole:

DEPRECATION WARNING: Base#after_initialize ist veraltet, bitte verwenden Sie stattdessen die Methode Base.after_initialize :. (aufgerufen von /Users/me/myapp/app/models/my_model:15)

Deshalb würde ich sagen, schreiben Sie eine after_initialize Callback, mit dem Sie Attribute voreinstellen können zusätzlich zu die es Ihnen ermöglicht, Standardwerte für Assoziationen wie diese festzulegen:

  class Person < ActiveRecord::Base
    has_one :address
    after_initialize :init

    def init
      self.number  ||= 0.0           #will set the default value only if it's nil
      self.address ||= build_address #let's you set a default association
    end
  end    

Jetzt haben Sie nur einer Ort, an dem Sie nach der Initialisierung Ihrer Modelle suchen sollten. Ich verwende diese Methode, bis jemand mit einer besseren kommt.

Vorbehalte:

  1. Für boolesche Felder tun:

    self.bool_field = true if self.bool_field.nil?

    Siehe Paul Russells Kommentar zu dieser Antwort für weitere Details

  2. Wenn Sie nur eine Teilmenge von Spalten für ein Modell auswählen (z. B. mit select in einer Abfrage wie Person.select(:firstname, :lastname).all ) erhalten Sie eine MissingAttributeError wenn Ihr init Methode greift auf eine Spalte zu, die nicht in der select Klausel. Sie können sich gegen diesen Fall folgendermaßen schützen:

    self.number ||= 0.0 if self.has_attribute? :number

    und für eine boolesche Spalte...

    self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?

    Beachten Sie auch, dass die Syntax vor Rails 3.2 anders ist (siehe den Kommentar von Cliff Darling weiter unten)

150voto

Lucas Caton Punkte 2731

Rails 5+

Sie können die Attribut Methode innerhalb Ihrer Modelle, z. B.:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

Sie können auch ein Lambda an die Funktion default Parameter. Beispiel:

attribute :uuid, :string, default: -> { SecureRandom.uuid }

Das zweite Argument ist der Typ und kann z. B. auch eine benutzerdefinierte Typklasseninstanz sein:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }

50voto

Laurent Farcy Punkte 710

Wir setzen die Standardwerte in der Datenbank durch Migrationen (durch Angabe der :default Option bei jeder Spaltendefinition) und lassen Sie Active Record diese Werte verwenden, um den Standard für jedes Attribut festzulegen.

IMHO entspricht dieser Ansatz den Prinzipien von AR: Konvention vor Konfiguration, DRY, die Tabellendefinition bestimmt das Modell und nicht umgekehrt.

Beachten Sie, dass die Standardwerte immer noch im Anwendungscode (Ruby) enthalten sind, allerdings nicht im Modell, sondern in der/den Migration(en).

42voto

Joseph Lord Punkte 6236

Einige einfache Fälle können durch die Definition einer Vorgabe im Datenbankschema behandelt werden, aber das reicht nicht für eine Reihe schwierigerer Fälle, einschließlich berechneter Werte und Schlüssel anderer Modelle. Für diese Fälle mache ich Folgendes:

after_initialize :defaults

def defaults
   unless persisted?
    self.extras||={}
    self.other_stuff||="This stuff"
    self.assoc = [OtherModel.find_by_name('special')]
  end
end

Ich habe beschlossen, die after_initialize verwenden, aber ich will nicht, dass es auf Objekte, die nur die neuen oder erstellt gefunden werden angewendet werden. Ich finde es fast schockierend, dass für diesen offensichtlichen Anwendungsfall kein after_new-Callback vorgesehen ist, aber ich habe mich damit begnügt, zu bestätigen, ob das Objekt bereits persistiert ist, was bedeutet, dass es nicht neu ist.

Nachdem ich die Antwort von Brad Murray gesehen habe, ist dies sogar noch sauberer, wenn die Bedingung in die Rückrufanforderung verschoben wird:

after_initialize :defaults, unless: :persisted?
              # ":if => :new_record?" is equivalent in this context

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
  self.assoc = [OtherModel.find_by_name('special')]
end

19voto

Brad Murray Punkte 411

Das after_initialize-Callback-Muster kann durch folgende einfache Maßnahmen verbessert werden

after_initialize :some_method_goes_here, :if => :new_record?

Dies hat einen nicht-trivialen Vorteil, wenn Ihr Init-Code mit Assoziationen umgehen muss, da der folgende Code ein subtiles n+1 auslöst, wenn Sie den ersten Datensatz lesen, ohne die Assoziationen mit einzubeziehen.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X