PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Wie kann ich mit ActiveRecord auf den Standardwert einer Postgres-Spalte zugreifen?

Wenn ActiveRecord etwas über eine Tabelle wissen muss, führt es eine Abfrage ähnlich Ihrem information_schema durch Abfrage, aber AR wird die PostgreSQL-spezifische Systemtabellen stattdessen:

  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
         pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod
    FROM pg_attribute a LEFT JOIN pg_attrdef d
      ON a.attrelid = d.adrelid AND a.attnum = d.adnum
   WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
     AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum

Suchen Sie in der PostgreSQL-Adapterquelle für "regclass" und Sie werden einige andere Abfragen sehen, die AR verwenden wird, um die Struktur der Tabelle herauszufinden.

Der pg_get_expr Aufruf in der obigen Abfrage ist, woher der Standardwert der Spalte kommt.

Die Ergebnisse dieser Abfrage lauten mehr oder weniger direkt in PostgreSQLColumn.new :

def columns(table_name, name = nil)
  # Limit, precision, and scale are all handled by the superclass.
  column_definitions(table_name).collect do |column_name, type, default, notnull|
    PostgreSQLColumn.new(column_name, default, type, notnull == 'f')
  end
end

Der PostgreSQLColumn Konstruktor verwendet extract_value_from_default um die Standardeinstellung zu rubyifizieren; das Ende von den switch in extract_value_from_default ist hier interessant:

else
  # Anything else is blank, some user type, or some function
  # and we can't know the value of that, so return nil.
  nil

Wenn also der Standardwert an eine Sequenz gebunden ist (die eine id Spalte in PostgreSQL sein wird), dann kommt der Standard aus der Datenbank als Funktionsaufruf ähnlich dem folgenden:

nextval('models_id_seq'::regclass)

Das wird im obigen else enden branch und column.default.nil? wird wahr sein.

Für eine id Spalte ist dies kein Problem, AR erwartet, dass die Datenbank die Werte für id liefert Spalten, sodass es egal ist, was der Standardwert ist.

Dies ist ein großes Problem, wenn der Standardwert der Spalte etwas ist, das AR nicht versteht, sagen wir einen Funktionsaufruf wie als md5(random()::text) . Das Problem ist, dass AR alle Attribute auf ihre Standardwerte initialisiert – als Model.columns sieht sie, nicht wie die Datenbank sie sieht – wenn Sie Model.new sagen . In der Konsole sehen Sie beispielsweise Folgendes:

 > Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>

Wenn also def_is_function tatsächlich einen Funktionsaufruf als Standardwert verwendet, wird AR dies ignorieren und versuchen, eine NULL als Wert dieser Spalte einzufügen. Diese NULL verhindert, dass der Standardwert verwendet wird, und Sie erhalten ein verwirrendes Durcheinander. Standardwerte, die AR verstehen kann (z. B. Zeichenfolgen und Zahlen), funktionieren jedoch problemlos.

Das Ergebnis ist, dass Sie nicht wirklich nicht-triviale Standardspaltenwerte mit ActiveRecord verwenden können, wenn Sie einen nicht-trivialen Wert wollen, dann müssen Sie das in Ruby durch einen der ActiveRecord-Callbacks (wie before_create ).

IMO wäre es viel besser, wenn AR die Standardwerte der Datenbank überlassen würde, wenn sie sie nicht versteht:Sie aus dem INSERT wegzulassen oder DEFAULT in den VALUES zu verwenden, würde viel bessere Ergebnisse liefern; AR müsste natürlich neu erstellte Objekte aus der Datenbank neu laden, um alle korrekten Standardwerte zu erhalten, aber Sie müssten nur neu laden, wenn es Standardwerte gäbe, die AR nicht versteht. Wenn der else in extract_value_from_default verwendet ein spezielles "Ich weiß nicht, was das bedeutet"-Flag anstelle von nil dann wäre die Bedingung "Ich muss dieses Objekt nach dem ersten Speichern neu laden" einfach zu erkennen und Sie würden nur bei Bedarf neu laden.

Das obige ist PostgreSQL-spezifisch, aber der Prozess sollte für andere Datenbanken ähnlich sein; Ich gebe jedoch keine Garantien.