Wie kann ich Standardwerte in ActiveRecord festlegen?


417

Wie kann ich den Standardwert in ActiveRecord festlegen?

Ich sehe einen Beitrag von Pratik, der einen hässlichen, komplizierten Codeabschnitt 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

Ich habe die folgenden Beispiele gesehen, die herum gegoogelt haben:

  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 Leute gesehen, die es in ihre Migration aufgenommen haben, aber ich würde es lieber im Modellcode definiert sehen.

Gibt es eine kanonische Möglichkeit, den Standardwert für Felder im ActiveRecord-Modell festzulegen?


Sieht so aus, als hätten Sie die Frage selbst in zwei verschiedenen Varianten beantwortet :)
Adam Byrtek

19
Beachten Sie, dass die "Standard" -Robin-Redewendung für 'self.status = ACTIVE, es sei denn, self.status' 'self.status || = ACTIVE' ist
Mike Woodhouse

1
Jeff Perrins Antwort ist viel besser als die, die derzeit als akzeptiert markiert ist. default_scope ist eine inakzeptable Lösung zum Festlegen von Standardwerten, da es den RIESIGEN NEBENWIRKUNG hat, auch das Verhalten von Abfragen zu ändern.
Lawrence


2
Angesichts aller positiven Stimmen zu dieser Frage würde ich sagen, dass Ruby eine setDefaultValue-Methode für
ActiveRecord

Antworten:


557

Bei jeder der verfügbaren Methoden gibt es mehrere Probleme, aber ich glaube, dass das Definieren eines after_initializeRückrufs aus folgenden Gründen der richtige Weg ist:

  1. default_scopeinitialisiert Werte für neue Modelle, aber dann wird dies der Bereich, in dem Sie das Modell finden. Wenn Sie nur einige Zahlen auf 0 initialisieren möchten, ist dies nicht das, was Sie wollen.
  2. Das Definieren von Standardeinstellungen in Ihrer Migration funktioniert auch teilweise ... Wie bereits erwähnt, funktioniert dies nicht , wenn Sie nur Model.new aufrufen.
  3. Überschreiben initializekann funktionieren, aber vergessen Sie nicht anzurufen super!
  4. Die Verwendung eines Plugins wie Phusion wird ein bisschen lächerlich. Das ist Ruby. Brauchen wir wirklich ein Plugin, um einige Standardwerte zu initialisieren?
  5. Das Überschreiben after_initialize ist ab Rails 3 veraltet . Wenn ich after_initializein Rails 3.0.3 überschreibe, wird in der Konsole die folgende Warnung angezeigt:

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

Daher würde ich sagen, schreiben Sie einen after_initializeRückruf, mit dem Sie zusätzlich zu den Standardattributen Standardattribute festlegen können:

  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 noch einen Ort, an dem Sie nach der Initialisierung Ihrer Modelle suchen können. Ich benutze diese Methode, bis jemand eine bessere findet.

Vorsichtsmaßnahmen:

  1. Für boolesche Felder gilt Folgendes:

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

    Weitere Einzelheiten finden Sie in Paul Russells Kommentar zu dieser Antwort

  2. Wenn Sie nur eine Teilmenge von Spalten für ein Modell auswählen (dh selectin einer Abfrage wie Person.select(:firstname, :lastname).all) verwenden, erhalten Sie eine, MissingAttributeErrorwenn Ihre initMethode auf eine Spalte zugreift, die nicht in der selectKlausel enthalten ist. Sie können sich wie folgt gegen diesen Fall 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 unten).


7
Dies scheint definitiv der beste Weg zu sein, um dies zu erreichen. Welches ist wirklich seltsam und unglücklich. Eine vernünftige bevorzugte Methode zum Festlegen von Standardwerten für Modellattribute bei der Erstellung scheint etwas zu sein, das Rails bereits eingebaut haben sollte. Der einzige andere (zuverlässige) Weg, der überschrieben wird initialize, scheint nur für etwas wirklich kompliziert zu sein, das klar und klar definiert sein sollte. Ich habe Stunden damit verbracht, durch die Dokumentation zu kriechen, bevor ich hier gesucht habe, weil ich angenommen habe, dass diese Funktionalität bereits irgendwo vorhanden ist und ich es einfach nicht wusste.
Seaneshbaugh

106
Ein Hinweis dazu: Wenn Sie ein boolesches Feld haben, das Sie standardmäßig verwenden möchten, tun Sie dies nicht self.bool_field ||= true, da dies das Feld auf true erzwingt, selbst wenn Sie es explizit auf false initialisieren. Stattdessen tun self.bool_field = true if self.bool_field.nil?.
Paul Russell

2
In Bezug auf Punkt 2 funktioniert Model.new tatsächlich (nur für mich?) Zusammen mit Standardwerten, die in Migrationen definiert wurden, oder genauer mit Standardwerten für Tabellenspalten. Aber ich erkenne, dass Jeffs Methode, die auf dem Rückruf nach_initialize basiert, wahrscheinlich der beste Weg ist. Nur eine Frage: Funktioniert es mit schmutzigen, aber nicht gespeicherten Objekten? Gibt Person.new.number_was in Ihrem Beispiel 0.0 zurück?
Laurent Farcy

21
Vorsicht bei Verwendung dieses Ansatzes in Kombination mit der Auswahl bestimmter Spalten mit aktivem Datensatz. In diesem Fall werden nur die in der Abfrage angegebenen Attribute im Objekt gefunden und der Init-Code löst a aus MissingAttributeError. Sie können eine zusätzliche Prüfung wie self.number ||= 0.0 if self.has_attribute? :number folgt hinzufügen: Für Boolesche Werte : self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?. Dies ist Rails 3.2+ - für frühere Verwendung self.attributes.has_key?, und Sie müssen eine Zeichenfolge anstelle eines Symbols verwenden.
Cliff Darling

6
Wenn Sie dies mit Assoziationen tun, werden diese Assoziationen bei der Suche eifrig geladen. Beginnen Sie initializemit return if !new_record?, um Leistungsprobleme zu vermeiden.
Kyle Macey

68

Schienen 5+

Sie können die Attributmethode in Ihren Modellen verwenden, z.

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

Sie können dem defaultParameter auch ein Lambda übergeben . Beispiel:

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

3
ahhhhh das ist das Juwel, nach dem ich gesucht habe! Standard kann auch einen Prozess
annehmen

1
Stellen Sie sicher, dass Sie den Typ als zweites Argument angeben. Andernfalls wird der Typ verwendet Valueund es wird keine Typumwandlung durchgeführt.
null

Zu meiner Freude funktioniert dies auch mit store_accessor, zum Beispiel, wenn store_accessor :my_jsonb_column, :localeSie dann definieren könnenattribute :locale, :string, default: 'en'
Ryan Romanchuk

Oh, das ist fantastisch, ich brauchte Standardeinstellungen, um sie in einer Form anzuzeigen, und das funktioniert großartig. Danke Lucas.
Paul Watson

Man kann diese immer noch einstellen nil. Wenn sie nicht nilDB not null+ DB default + sein können, sind github.com/sshaw/keep_defaults meiner Erfahrung nach der
richtige

47

Wir setzen die Standardwerte durch Migrationen in die Datenbank (indem wir die :defaultOption für jede Spaltendefinition angeben) und lassen Active Record diese Werte verwenden, um den Standard für jedes Attribut festzulegen.

IMHO, dieser Ansatz ist auf die Prinzipien von AR ausgerichtet: Konvention über Konfiguration, DRY, die Tabellendefinition treibt das Modell an, nicht umgekehrt.

Beachten Sie, dass sich die Standardeinstellungen weiterhin im Anwendungscode (Ruby) befinden, jedoch nicht im Modell, sondern in den Migrationen.


3
Ein weiteres Problem besteht darin, dass Sie einen Standardwert für einen Fremdschlüssel wünschen. Sie können einen ID-Wert nicht fest in das Fremdschlüsselfeld codieren, da die ID in verschiedenen DBs möglicherweise unterschiedlich ist.
Shmichael

2
Ein weiteres Problem besteht darin, dass Sie auf diese Weise keine nicht persistenten Accessoren (Attribute, die keine Datenbankspalten sind) initialisieren können.
Viktor Trón

2
Ein weiteres Problem ist, dass Sie nicht unbedingt alle Standardwerte an einem Ort sehen können. Sie können über verschiedene Migrationen verstreut sein.
Dekan

8
deklarieren, es gibt db / schema.rb
Benjamin Atkin

6
Ich möchte für zukünftige Leser erwähnen: Zumindest nach dem, was ich gelesen habe, widerspricht dies den Prinzipien von AR. Die Logik für die Modelle sollte innerhalb der Modellklassen liegen, und die Datenbanken sollten so unwissend wie möglich sein. Standardwerte sind für mich eine spezifische Logik für ein Modell.
Darethas

40

Einige einfache Fälle können durch Definieren eines Standardwerts im Datenbankschema behandelt werden, dies behandelt jedoch nicht 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 mich für after_initialize entschieden, möchte aber nicht, dass es auf Objekte angewendet wird, die nur neu oder erstellt gefunden werden. Ich finde es fast schockierend, dass für diesen offensichtlichen Anwendungsfall kein after_new-Rückruf bereitgestellt wird, aber ich habe dies getan, indem ich bestätigt habe, ob das Objekt bereits beibehalten wurde, um anzuzeigen, dass es nicht neu ist.

Nachdem Brad Murrays Antwort gesehen wurde, ist dies noch sauberer, wenn die Bedingung in eine 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

4
Dies ist ein wirklich wichtiger Punkt. Ich muss mir vorstellen, dass das Festlegen der Standardeinstellung für einen Datensatz in den meisten Fällen nur vor dem Speichern eines neuen Datensatzes erfolgen muss, nicht beim Laden eines dauerhaften Datensatzes.
Russell Silva

Danke, du hast meinen Tag gerettet.
stephanfriedrich

2
Wie wäre es :before_create?
Franklin Yu

Wie behandelt: before_create separate neue und gespeicherte Anrufe? Ich würde das gerne überprüfen und wirklich verstehen, bevor ich dazu wechsle.
Joseph Lord

17

Das Rückrufmuster after_initialize kann durch einfaches Ausführen der folgenden Schritte verbessert werden

after_initialize :some_method_goes_here, :if => :new_record?

Dies hat einen nicht trivialen Vorteil, wenn Ihr Init-Code sich mit Assoziationen befassen muss, da der folgende Code ein subtiles n + 1 auslöst, wenn Sie den ursprünglichen Datensatz lesen, ohne den zugehörigen einzuschließen.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end

16

Die Phusion-Jungs haben ein nettes Plugin dafür.


Beachten Sie, dass mit diesem Plugin die :defaultWerte in Schemamigrationen "nur funktionieren" können Model.new.
Jchook

Ich kann die :defaultWerte in Migrationen dazu bringen, "nur zu arbeiten" Model.new, im Gegensatz zu dem, was Jeff in seinem Beitrag gesagt hat. Verifiziertes Arbeiten in Rails 4.1.16.
Magne


8

Ich benutze den attribute-defaultsEdelstein

Aus der Dokumentation: Führen sudo gem install attribute-defaultsSie require 'attribute_defaults'Ihre App aus und fügen Sie sie hinzu.

class Foo < ActiveRecord::Base
  attr_default :age, 18
  attr_default :last_seen do
    Time.now
  end
end

Foo.new()           # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"

7

Ähnliche Fragen, aber alle haben einen leicht unterschiedlichen Kontext: - Wie erstelle ich einen Standardwert für Attribute im Modell von Rails activerecord?

Beste Antwort: Kommt darauf an, was du willst!

Wenn Sie möchten, dass jedes Objekt mit einem Wert beginnt: Verwenden Sieafter_initialize :init

Sie möchten, dass das new.htmlFormular beim Öffnen der Seite einen Standardwert hat? Verwenden Sie https://stackoverflow.com/a/5127684/1536309

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 

Wenn Sie möchten, dass für jedes Objekt ein Wert aus Benutzereingaben berechnet wird: usebefore_save :default_values Sie möchten, dass der Benutzer Folgendes eingibt,Xund dannY = X+'foo'? verwenden:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end

4

Dafür sind Konstruktoren da! Überschreiben Sie die initializeMethode des Modells .

Verwenden Sie die after_initializeMethode.


2
Normalerweise sind Sie korrekt, aber Sie sollten die Initialisierung in einem ActiveRecord-Modell niemals überschreiben, da es möglicherweise nicht immer aufgerufen wird. Sie sollten after_initializestattdessen die Methode verwenden.
Luke Redpath

Die Verwendung eines default_scope nur zum Festlegen eines Standards ist BESTIMMT falsch. after_initialize ist die richtige Antwort.
Joaomilho

4

Sup Jungs, am Ende habe ich folgendes gemacht:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

Klappt wunderbar!


3

Dies wurde lange beantwortet, aber ich benötige häufig Standardwerte und ziehe es vor, sie nicht in die Datenbank aufzunehmen. Ich mache mir DefaultValuesSorgen:

module DefaultValues
  extend ActiveSupport::Concern

  class_methods do
    def defaults(attr, to: nil, on: :initialize)
      method_name = "set_default_#{attr}"
      send "after_#{on}", method_name.to_sym

      define_method(method_name) do
        if send(attr)
          send(attr)
        else
          value = to.is_a?(Proc) ? to.call : to
          send("#{attr}=", value)
        end
      end

      private method_name
    end
  end
end

Und dann benutze es in meinen Modellen so:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end

3

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

Gibt es eine kanonische Möglichkeit, den Standardwert für Felder im ActiveRecord-Modell festzulegen?

Der kanonische Rails-Weg vor Rails 5 bestand darin, ihn in der Migration festzulegen und nur in den zu schauen db/schema.rb festzulegen und nachzuschauen, wann immer Sie sehen möchten, welche Standardwerte von der Datenbank für ein Modell festgelegt werden.

Im Gegensatz zu den Antwortzuständen von @Jeff Perrin (die etwas alt sind) wendet der Migrationsansatz bei Verwendung sogar die Standardeinstellung an Model.new aufgrund einiger Rails-Magie . Verifiziertes Arbeiten in Rails 4.1.16.

Das Einfachste ist oft das Beste. Weniger Wissensschulden und potenzielle Verwirrungspunkte in der Codebasis. Und es funktioniert einfach.

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

Oder führen Sie für einen Spaltenwechsel, ohne einen neuen zu erstellen, Folgendes aus:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

Oder vielleicht sogar noch besser:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

Überprüfen Sie den offiziellen RoR-Leitfaden auf Optionen für Spaltenänderungsmethoden .

Das null: falseerlaubt NULL-Werte in der Datenbank nicht und wird als zusätzlichen Vorteil auch aktualisiert, sodass alle bereits vorhandenen DB-Datensätze, die zuvor null waren, ebenfalls mit dem Standardwert für dieses Feld festgelegt werden. Sie können diesen Parameter in der Migration ausschließen, wenn Sie möchten, aber ich fand ihn sehr praktisch!

Der kanonische Weg in Rails 5+ ist, wie @Lucas Caton sagte:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end

1

Das Problem bei den after_initialize-Lösungen besteht darin, dass Sie jedem einzelnen Objekt, das Sie in der Datenbank suchen, eine after_initialize hinzufügen müssen, unabhängig davon, ob Sie auf dieses Attribut zugreifen oder nicht. Ich schlage einen faulen Ansatz vor.

Die Attributmethoden (Getter) sind natürlich selbst Methoden, sodass Sie sie überschreiben und einen Standard angeben können. Etwas wie:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

Es sei denn, wie jemand darauf hingewiesen hat, müssen Sie Foo.find_by_status ('ACTIVE') ausführen. In diesem Fall müssten Sie den Standard in Ihren Datenbankeinschränkungen wirklich festlegen, wenn die Datenbank dies unterstützt.


Diese Lösung und die vorgeschlagene Alternative funktionieren in meinem Fall nicht: Ich habe eine STI-Klassenhierarchie, in der nur eine Klasse dieses Attribut hat, und die entsprechende Spalte, die in DB-Abfragebedingungen verwendet wird.
cmoran92

1

Ich lief in Probleme mit after_initializegeben ActiveModel::MissingAttributeErrorFehler bei komplexen Funde tun:

z.B:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"Suche" im .whereHash der Bedingungen

Am Ende habe ich die Initialisierung folgendermaßen überschrieben:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

Der superAufruf ist erforderlich, um sicherzustellen, dass das Objekt korrekt initialisiert wurde, ActiveRecord::Basebevor Sie meinen Anpassungscode ausführen, dh: default_values


Ich mag das. Ich musste def initialize(*); super; default_values; endin Rails 5.2.0 tun . Außerdem ist der Standardwert auch im .attributesHash verfügbar .
Spyle

1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end

2
Mmmhh ... scheint zunächst genial, aber nachdem ich ein bisschen nachgedacht habe, sehe ich ein paar Probleme. Erstens befinden sich alle Standardwerte nicht in einem einzigen Punkt, sondern sind in der Klasse verteilt (stellen Sie sich vor, Sie suchen nach ihnen oder ändern sie). Zweitens und am schlimmsten, Sie können später keinen Nullwert (oder sogar einen falschen!) Setzen.
Paradoja

Warum sollten Sie einen Nullwert als Standard festlegen? Mit AR bekommen Sie das sofort, ohne etwas zu tun. Was false betrifft, wenn Sie eine boolesche Spalte verwenden, dann haben Sie Recht, dies ist nicht der beste Ansatz.
Mike Breen

Ich kann nicht für andere Codierungsgewohnheiten sprechen. Ich hatte kein Problem, weil ich meine Getter / Setter nicht in einer Klassendatei verteile. Außerdem sollte jeder moderne Texteditor das Navigieren zu einer Methode erleichtern (shift-cmd-t in textmate).
Mike Breen

@paradoja - Ich nehme das zurück, ich sehe jetzt, wo es mit der Verwendung von null zusammenbricht. Verwenden Sie nicht unbedingt null als Standard, sondern wenn Sie den Wert tatsächlich irgendwann auf null ändern möchten. Guter Fang @paradoja, danke.
Mike Breen

Ich benutze diese Methode, da sie gut mit dynamisch generierten Attributen funktioniert.
Dale Campbell


0

Nachdem die Methode after_initialize veraltet ist, verwenden Sie stattdessen den Rückruf.

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

Die Verwendung von : default in Ihren Migrationen ist jedoch immer noch der sauberste Weg.


4
In Rails 3: after_initializeMethode ist NICHT veraltet . Tatsächlich ist der Rückruf im Makro-Stil, den Sie als Beispiel für IS angeben, veraltet . Details : guides.rubyonrails.org/…
Zabba

0

Ich habe festgestellt, dass die Verwendung einer Validierungsmethode viel Kontrolle über das Festlegen von Standardeinstellungen bietet. Sie können sogar Standardeinstellungen (oder eine Fehlerüberprüfung) für Updates festlegen. Sie haben sogar einen anderen Standardwert für Einfügungen als für Aktualisierungen festgelegt, wenn Sie dies wirklich wollten. Beachten Sie, dass die Standardeinstellung erst festgelegt wird, wenn #valid? wird genannt.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

Beim Definieren einer after_initialize-Methode können Leistungsprobleme auftreten, da after_initialize auch von jedem zurückgegebenen Objekt aufgerufen wird von: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find


erfolgt die Validierung nicht erst vor dem Speichern? Was ist, wenn Sie vor dem Speichern Standardeinstellungen anzeigen möchten?
Nurettin

@nurettin Das ist ein guter Punkt und ich kann sehen, warum Sie das manchmal wollen, aber das OP hat dies nicht als Voraussetzung erwähnt. Sie müssen selbst entscheiden, ob Sie die Standardeinstellungen für jede Instanz festlegen möchten, auch wenn diese nicht gespeichert sind. Die Alternative besteht darin, ein Dummy-Objekt für die newWiederverwendung der Aktion zu behalten .
Kelvin

0

Wenn es sich bei der Spalte zufällig um eine Spalte vom Typ "Status" handelt und Ihr Modell für die Verwendung von Zustandsautomaten geeignet ist , sollten Sie das Aasm-Juwel verwenden. Anschließend können Sie dies einfach tun

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

Der Wert für nicht gespeicherte Datensätze wird immer noch nicht initialisiert, aber es ist ein bisschen sauberer, als Ihren eigenen mit initoder was auch immer zu rollen , und Sie profitieren von den anderen Vorteilen von aasm, wie z. B. Gültigkeitsbereichen für alle Ihre Status.



0

Ich empfehle dringend, das Juwel "default_value_for" zu verwenden: https://github.com/FooBarWidget/default_value_for

Es gibt einige knifflige Szenarien, bei denen die Initialisierungsmethode, die dieses Juwel ausführt, überschrieben werden muss.

Beispiele:

Ihr DB-Standard ist NULL, Ihr vom Modell / Ruby definierter Standard ist "irgendein String", aber Sie möchten es tatsächlich den Wert aus irgendeinem Grund auf Null setzen:MyModel.new(my_attr: nil)

Die meisten Lösungen hier setzen den Wert nicht auf Null und setzen ihn stattdessen auf den Standardwert.

OK, anstatt den ||=Ansatz zu wählen, wechseln Sie zu my_attr_changed?...

ABER stellen Sie sich jetzt vor, Ihr DB-Standard ist "irgendein String", Ihr vom Modell / Ruby definierter Standard ist "irgendein anderer String", aber unter einem bestimmten Szenario möchten Sie den Wert auf "irgendeinen String" setzen (der DB-Standard):MyModel.new(my_attr: 'some_string')

Dies führt dazu my_attr_changed?, dass der Wert falsch ist, da der Wert mit dem Standardwert von db übereinstimmt, wodurch Ihr von Ruby definierter Standardcode ausgelöst und der Wert auf "eine andere Zeichenfolge" gesetzt wird - wiederum nicht das, was Sie gewünscht haben.


Aus diesen Gründen denke ich nicht, dass dies mit nur einem after_initialize-Hook richtig erreicht werden kann.

Auch hier denke ich, dass das Juwel "default_value_for" den richtigen Ansatz verfolgt: https://github.com/FooBarWidget/default_value_for


0

Hier ist eine Lösung, die ich verwendet habe und die mich ein wenig überrascht hat. Sie wurde noch nicht hinzugefügt.

Es gibt zwei Teile. Im ersten Teil wird die Standardeinstellung für die eigentliche Migration festgelegt, und im zweiten Teil wird dem Modell eine Validierung hinzugefügt, um sicherzustellen, dass die Anwesenheit wahr ist.

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

Hier sehen Sie also, dass der Standard bereits festgelegt ist. Jetzt möchten Sie in der Validierung sicherstellen, dass immer ein Wert für die Zeichenfolge vorhanden ist

 validates :new_team_signature, presence: true

Dadurch wird der Standardwert für Sie festgelegt. (Für mich habe ich "Willkommen im Team"), und dann geht es noch einen Schritt weiter und stellt sicher, dass für dieses Objekt immer ein Wert vorhanden ist.

Ich hoffe, das hilft!


0
# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

-2

Verwenden Sie default_scope in Schienen 3

api doc

ActiveRecord verdeckt den Unterschied zwischen den in der Datenbank definierten Standardeinstellungen (Schema) und den in der Anwendung (Modell) vorgenommenen Standardeinstellungen. Während der Initialisierung wird das Datenbankschema analysiert und alle dort angegebenen Standardwerte notiert. Später beim Erstellen von Objekten werden diese schemaspezifischen Standardwerte zugewiesen, ohne die Datenbank zu berühren.

Diskussion


Wenn Sie meta_where verwenden, funktioniert default_scope aufgrund eines Fehlers möglicherweise nicht, um neuen AR-Objekten Standardeinstellungen zuzuweisen.
Viktor Trón

Dieses meta_where-Problem wurde jetzt behoben [ metautonomous.lighthouseapp.com/projects/53011/tickets/…
Viktor Trón

3
NICHT verwenden default_scope. Dadurch fügen alle Ihre Abfragen diese Bedingung zu dem von Ihnen festgelegten Feld hinzu. Es ist fast NIE was du willst.
Brad

@brad, lustig du sagst, ich stimme vollkommen zu, es ist böse :). Siehe meinen Kommentar in stackoverflow.com/questions/10680845/… .
Viktor Trón

-3

In den API-Dokumenten http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Verwenden Sie die before_validationMethode in Ihrem Modell. Sie haben die Möglichkeit, eine spezifische Initialisierung für das Erstellen und Aktualisieren von Aufrufen zu erstellen, z. B. in diesem Beispiel (erneut Code verwendet) Aus dem API-Dokumentbeispiel wird das Nummernfeld für eine Kreditkarte initialisiert. Sie können dies einfach anpassen, um die gewünschten Werte festzulegen

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

Überrascht, dass sein hier nicht vorgeschlagen wurde


before_validation setzt die Standardeinstellungen erst, wenn das Objekt zum Fortbestehen bereit ist. Wenn der Prozess die Standardeinstellungen lesen muss, bevor er fortbesteht, sind die Werte nicht bereit.
mmell

Sie haben ohnehin nie Standardwerte während der Validierungsprüfung festgelegt. Es ist nicht einmal ein Hack. Tun Sie es während der Initialisierung
Sachin
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.