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.