Wanneer ActiveRecord iets moet weten over een tabel, voert het een query uit die lijkt op uw information_schema
query, maar AR gaat door de PostgreSQL-specifieke systeemtabellen
in plaats daarvan:
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
Zoek in de PostgreSQL-adapterbron voor "regclass" en je zult enkele andere zoekopdrachten zien die AR zal gebruiken om de structuur van de tabel te achterhalen.
De pg_get_expr
oproep in de bovenstaande query is waar de standaardwaarde van de kolom vandaan komt.
De resultaten van die zoekopdracht zijn min of meer rechtstreeks naar 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
De PostgreSQLColumn
constructeur
gebruikt extract_value_from_default
om de standaard te Ruby-ify; het einde van de switch
in extract_value_from_default
is 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
Dus als de standaardwaarde is gebonden aan een reeks (die een id
kolom in PostgreSQL zal zijn), dan komt de standaard uit de database als een functieaanroep die er ongeveer zo uitziet:
nextval('models_id_seq'::regclass)
Dat zal eindigen in de bovenstaande else
branch en column.default.nil?
zal waar zijn.
Voor een id
kolom is dit geen probleem, AR verwacht dat de database de waarden levert voor id
kolommen, dus het maakt niet uit wat de standaardwaarde is.
Dit is een groot probleem als de standaardinstelling van de kolom iets is dat AR niet begrijpt, zeg een functieaanroep zoals als md5(random()::text)
. Het probleem is dat AR alle attributen initialiseert naar hun standaardwaarden - als Model.columns
ziet ze, niet zoals de database ze ziet – als je zegt Model.new
. In de console ziet u bijvoorbeeld dit soort dingen:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Dus als def_is_function
daadwerkelijk een functie-aanroep als standaardwaarde gebruikt, zal AR dat negeren en proberen een NULL in te voegen als de waarde van die kolom. Die NULL voorkomt dat de standaardwaarde wordt gebruikt en je krijgt een verwarrende puinhoop. Standaardinstellingen die AR kan begrijpen (zoals tekenreeksen en cijfers) werken echter prima.
Het resultaat is dat je niet echt niet-triviale standaard kolomwaarden kunt gebruiken met ActiveRecord, als je een niet-triviale waarde wilt, dan moet je dat in Ruby doen via een van de ActiveRecord-callbacks (zoals before_create
).
IMO zou het veel beter zijn als AR de standaardwaarden aan de database zou overlaten als het ze niet begreep:ze uit de INSERT weglaten of DEFAULT gebruiken in de VALUES zou veel betere resultaten opleveren; AR zou natuurlijk nieuw gemaakte objecten uit de database opnieuw moeten laden om alle juiste standaardwaarden te krijgen, maar je zou het opnieuw moeten laden als er standaardwaarden waren die AR niet begreep. Als de else
in extract_value_from_default
gebruikte een speciale "ik weet niet wat dit betekent" vlag in plaats van nil
dan zou de voorwaarde "Ik moet dit object opnieuw laden na de eerste keer opslaan" triviaal zijn om te detecteren en zou je alleen opnieuw moeten laden als dat nodig is.
Het bovenstaande is PostgreSQL-specifiek, maar het proces zou vergelijkbaar moeten zijn voor andere databases; ik geef echter geen garanties.