Wie setze ich Standardwerte in Rails?

107

Ich versuche, den besten Weg zu finden, um Standardwerte für Objekte in Rails festzulegen.

Das Beste, was ich mir vorstellen kann, ist, den Standardwert in der newMethode im Controller festzulegen.

Hat jemand eine Eingabe, ob dies akzeptabel ist oder ob es einen besseren Weg gibt, dies zu tun?

biagidp
quelle
1
Was sind diese Objekte? Wie werden sie konsumiert / verwendet? Werden sie beim Rendern der Ansichten oder für die Controller-Logik verwendet?
Gishu
3
Wenn Sie über ein ActiveRecord-Objekt sprechen, muss ich Ihnen sagen, dass es keine vernünftige Lösung für das Problem der Standardwerte gibt. Nur verrückte Hacks, und die Rails-Autoren scheinen nicht zu glauben, dass sich das Feature lohnt (erstaunlich, wie es nur die Rails-Community ist ..)
Mauricio
Da sich die akzeptierten und die meisten Antworten auf ActiveRecords konzentrieren, gehen wir davon aus, dass die ursprüngliche Frage AcitveRecords betraf. Daher mögliches Duplikat von stackoverflow.com/questions/328525/…
Ciro Santilli 14 冠状 病 六四 事件 法轮功
Mögliches Duplikat von Wie kann ich Standardwerte in ActiveRecord festlegen?
Lucas Caton

Antworten:

98

"Richtig" ist ein gefährliches Wort in Ruby. Es gibt normalerweise mehr als einen Weg, etwas zu tun. Wenn Sie wissen, dass Sie immer diesen Standardwert für diese Spalte in dieser Tabelle möchten, ist es am einfachsten, sie in einer DB-Migrationsdatei festzulegen:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

Da ActiveRecord Ihre Tabellen- und Spalteneigenschaften automatisch erkennt, wird in jedem Modell, das es in einer Standard-Rails-App verwendet, derselbe Standard festgelegt.

Wenn Sie jedoch nur in bestimmten Fällen Standardwerte festlegen möchten - beispielsweise, dass es sich um ein geerbtes Modell handelt, das eine Tabelle mit anderen teilt -, können Sie dies beim Erstellen des Modellobjekts direkt in Ihrem Rails-Code tun:

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

Wenn Sie dann a GenericPerson.new()ausführen, wird das Attribut "Doe" immer hochgespielt, es Person.new()sei denn, Sie überschreiben es mit etwas anderem.

SFEley
quelle
2
Versuch es. Es funktioniert mit neuen Modellobjekten, die mit der .newKlassenmethode aufgerufen werden. In der Diskussion dieses Blogposts über den direkten Aufruf von ActiveRecord ging es um Modellobjekte, .allocatedie mit vorhandenen Daten aus der Datenbank geladen wurden. ( Und es ist eine schreckliche Idee für ActiveRecord, so zu arbeiten, IMO. Aber das ist
nebensächlich
2
Wenn Sie zurücktreten, um die Frage des Originalplakats, Nikita, und dann meine Kommentare der Reihe nach erneut zu lesen, ist dies für Sie möglicherweise sinnvoller. Wenn nicht ... Nun, die Frage wurde beantwortet. Einen schönen Tag noch.
SFEley
8
Besser für die Down-Migration: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Nolan Amy
9
Bitte beachten Sie, dass Sie für neuere Rails-Versionen vor dem Standardparameter einen zusätzlichen Typparameter benötigen. Suchen Sie nach: Geben Sie auf dieser Seite ein.
Ben Wheeler
3
@ JoelBrewer Ich bin heute darauf gestoßen - für Rails 4 müssen Sie auch den change_column :people, :last_name, :string, default: "Doe"
Spaltentyp
56

Basierend auf der Antwort von SFEley ist hier eine aktualisierte / feste für neuere Rails-Versionen:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end
J L
quelle
2
Beachten Sie, dass dies NICHT auf Rails 4.2.x funktioniert (in späteren Versionen nicht getestet). wie change_columnkann durchaus sein „Strukturierung“, kann der umgekehrte Vorgang nicht abgeleitet werden. Wenn Sie sich nicht sicher sind, testen Sie es einfach durch Ausführen db:migrateund db:rollbackdirekt danach. Akzeptierte Antwort als gleiches Ergebnis, aber zumindest wird davon ausgegangen!
gfd
1
Dies ist die richtige Antwort für mich. Es funktioniert mit Schienen 5.1, aber Sie müssen tun upund downwie oben beschrieben von @GoBusto
Fabrizio Bertoglio
21

Erstens können Sie nicht überladen, initialize(*args)da es nicht in allen Fällen aufgerufen wird.

Am besten geben Sie Ihre Standardeinstellungen in Ihre Migration ein:

add_column :accounts, :max_users, :integer, :default => 10

Am zweitbesten ist es, Standardeinstellungen in Ihr Modell einzufügen. Dies funktioniert jedoch nur mit Attributen, die anfangs null sind. Möglicherweise haben Sie Probleme wie bei booleanSpalten:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

Du brauchst das new_record? damit die Standardeinstellungen die aus der Datenbank geladenen Werte nicht überschreiben.

Sie müssen verhindern ||=, dass Rails Parameter überschreibt, die an die Initialisierungsmethode übergeben werden.

Ian Purton
quelle
12
Nebenbemerkung - Sie möchten zwei Dinge tun: .... 1) Rufen Sie Ihre Methode nicht after_initialize auf. Sie wollen after_initiation :your_method_name... 2 verwendenself.max_users ||= 10
Jesse Wolgamott
5
Für Boolesche machen Sie einfach folgendes: prop = true wenn prop.nil?
Francis Potter
2
oder after_initialize dostattdef after_initialize
fotanus
self.max_users um sicher zu gehen.
Ken Ratanachai S.
15

Sie können auch change_column_defaultIhre Migrationen ausprobieren (getestet in Rails 3.2.8):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

change_column_default Rails-API-Dokumente

Sebastiaan Pouyet
quelle
1
Die akzeptierte Antwort ist vollständiger, aber ich mag diese saubere und dokumentierte ... Danke!
gfd
7

Wenn Sie sich auf ActiveRecord-Objekte beziehen, haben Sie (mehr als) zwei Möglichkeiten, dies zu tun:

1. Verwenden Sie einen: Standardparameter in der Datenbank

Z.B

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

Weitere Informationen hier: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. Verwenden Sie einen Rückruf

Z.B before_validation_on_create

Weitere Informationen hier: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147

Vlad Zloteanu
quelle
2
Ist das Problem bei der Verwendung von Standardeinstellungen in der Datenbank nicht, dass sie erst festgelegt werden, wenn das Objekt gespeichert wird? Wenn ich ein neues Objekt mit den aufgefüllten Standardeinstellungen erstellen möchte, muss ich sie im Initialisierer festlegen.
Rafe
Boolesche Werte können nicht standardmäßig 1 oder 0 sein - sie müssen auf wahr oder falsch gesetzt sein (siehe Silasjs Antwort).
Jamon Holmgren
@ JamonHolmgren Danke für die Bemerkung, ich habe die Antwort korrigiert :)
Vlad Zloteanu
Kein Problem @VladZloteanu
Jamon Holmgren
7

In Ruby on Rails v3.2.8 können Sie mithilfe des after_initializeActiveRecord-Rückrufs eine Methode in Ihrem Modell aufrufen, die die Standardwerte für ein neues Objekt zuweist .

Der Rückruf after_initialize wird für jedes Objekt ausgelöst, das von einem Finder gefunden und instanziiert wird. Der Rückruf after_initialize wird ebenfalls ausgelöst, nachdem neue Objekte instanziiert wurden ( siehe ActiveRecord-Rückrufe ).

Also, IMO sollte es ungefähr so ​​aussehen:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_valuefür diese Instanz, es sei denn, die Instanz enthält eine attribute_whose_presence_has_been_validatedzuvor beim Speichern / Aktualisieren. Das default_valuewird dann in Verbindung mit Ihrer Ansicht verwendet, um das Formular mit dem default_valuefor- barAttribut zu rendern .

Bestenfalls ist das hacky ...

BEARBEITEN - benutze 'new_record?' um zu überprüfen, ob ein neuer Anruf ausgelöst wurde

Verwenden Sie die new_record?integrierte Methode mit Schienen, anstatt einen Attributwert zu überprüfen . Das obige Beispiel sollte also so aussehen:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

Das ist viel sauberer. Ah, die Magie von Rails - es ist schlauer als ich.

fehlerhaft
quelle
Dies funktioniert nicht wie erwartet - es sollte nur den Standardwert für new festlegen, aber es wird auch der Standardwert für das Attribut für jedes Objekt festgelegt, das gefunden und instanziiert wird (dh Datensätze, die aus der Datenbank geladen werden). Sie können ein Objektattribut überprüfen, um nach Werten zu suchen, bevor Sie die Standardeinstellung zuweisen. Dies ist jedoch keine sehr elegante oder robuste Lösung.
Fehler
1
Warum empfehlen Sie after_initializehier einen Rückruf? Die Rails-Dokumentation zu Rückrufen enthält ein Beispiel für das Festlegen des Standardwerts before_createohne zusätzliche Bedingungsprüfungen
Dfr
@Dfr - Ich muss das verpasst haben, können Sie mir einen Link zur Überprüfung werfen und ich werde die Antwort aktualisieren ...
Fehler
Ja, hier api.rubyonrails.org/classes/ActiveRecord/Callbacks.html erstes Beispiel, KlasseSubscription
Dfr
5
@Dfr - Der Grund, warum wir after_initializeanstelle des before_createRückrufs den verwenden, ist, dass wir beim Erstellen neuer Objekte einen Standardwert für den Benutzer (zur Verwendung in einer Ansicht) festlegen möchten . Der before_createRückruf wird aufgerufen, nachdem dem Benutzer ein neues Objekt zugestellt, seine Eingabe bereitgestellt und das Objekt zur Erstellung an die Steuerung gesendet wurde. Der Controller sucht dann nach before_createRückrufen. Es scheint nicht intuitiv zu sein, aber es ist eine Nomenklatursache - before_createbezieht sich auf die createHandlung. Das Instanziieren eines neuen Objekts führt nicht createzum Objekt.
Fehler
5

Zumindest für boolesche Felder in Rails 3.2.6 funktioniert dies bei Ihrer Migration.

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

Das Setzen eines 1oder 0für einen Standardwert funktioniert hier nicht, da es sich um ein boolesches Feld handelt. Es muss ein trueoder falseWert sein.

Silasj
quelle
2

Eine Migration und Verwendung generieren change_column_default, ist prägnant und reversibel:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end
Pere Joan Martorell
quelle
1

Wenn Sie nur Standardeinstellungen für bestimmte Attribute eines datenbankgestützten Modells festlegen, würde ich die Verwendung von SQL-Standardspaltenwerten in Betracht ziehen. Können Sie klarstellen, welche Arten von Standardeinstellungen Sie verwenden?

Es gibt eine Reihe von Ansätzen, um damit umzugehen. Dieses Plugin scheint eine interessante Option zu sein.

paulthenerd
quelle
1
Ich denke, Sie sollten sich nicht auf die Datenbank verlassen, um mit Standardeinstellungen und Einschränkungen fertig zu werden. All diese Dinge sollten in der Modellebene aussortiert werden. Dieses Plugin funktioniert nur für ActiveRecord-Modelle. Es ist keine generische Methode, um Standardeinstellungen für Objekte festzulegen.
Lukas Stejskal
Ich würde argumentieren, dass es von der Art der Standardeinstellungen abhängt, die Sie verwenden möchten. Ich musste das nicht sehr oft tun, aber ich würde sagen, dass Einschränkungen in der Tat viel besser sind, wenn sie sowohl in der Datenbank als auch in der Datenbank festgelegt werden das Modell - um zu verhindern, dass Ihre Daten in der Datenbank ungültig werden.
Paulthenerd
1

Der Vorschlag, new / initialize zu überschreiben, ist wahrscheinlich unvollständig. Rails ruft (häufig) die Zuweisung für ActiveRecord-Objekte auf, und Aufrufe für die Zuweisung führen nicht zu Aufrufen zum Initialisieren.

Wenn Sie über ActiveRecord-Objekte sprechen, sehen Sie sich das Überschreiben von after_initialize an.

Diese Blog-Beiträge (nicht meine) sind nützlich:

Standardwerte Standardkonstruktoren werden nicht aufgerufen

[Bearbeiten: SFEley weist darauf hin, dass Rails tatsächlich die Standardeinstellung in der Datenbank überprüft, wenn ein neues Objekt im Speicher instanziiert wird - das hatte ich nicht bemerkt.]

James Moore
quelle
1

Ich musste einen Standard festlegen, als wäre er als Standardspaltenwert in der Datenbank angegeben worden. So verhält es sich also

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

Da der Rückruf after_initialize nach dem Festlegen von Attributen aus Argumenten aufgerufen wird, konnte nicht festgestellt werden, ob das Attribut Null ist, da es nie oder absichtlich als Null festgelegt wurde. Also musste ich ein bisschen reinschauen und kam mit dieser einfachen Lösung.

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

Funktioniert super für mich. (Schienen 3.2.x)

Petr Bubák Šedivý
quelle
0

i beantworten eine ähnliche Frage hier .. eine saubere Art und Weise , dies zu tun ist mit Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

AKTUALISIEREN

attr_accessor_with_default ist in Rails 3.2 veraltet. Sie können dies stattdessen mit reinem Ruby tun

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true
Orlando
quelle
attr_accessor_with_defaultist ab Schienen> 3.1.0 veraltet
flynfish
In Ihrem Beispiel ist is_awesome immer wahr, auch wenn @is_awesome == false ist.
Ritchie
-3

Sie können den Konstruktor für das ActiveRecord-Modell überschreiben.

So was:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end
Terry
quelle
2
Dies ist ein schrecklicher Ort, um die Funktionalität der Kernschienen zu ändern. Es gibt andere, viel stabilere, weniger wahrscheinlich andere Dinge zu brechen, Möglichkeiten, dies zu tun, werden in anderen Antworten erwähnt. Das Patchen von Affen sollte nur ein letzter Ausweg für Dinge sein, die unmöglich anders gemacht werden können.
Will