Když ActiveRecord potřebuje vědět o tabulce, provede dotaz podobný vašemu informačnímu schématu
dotaz, ale AR projde přes Systémové tabulky specifické pro PostgreSQL
místo toho:
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
Prohledejte zdroj adaptéru PostgreSQL pro "regclass" a uvidíte některé další dotazy, které AR použije k určení struktury tabulky.
pg_get_expr
volání ve výše uvedeném dotazu je místo, odkud pochází výchozí hodnota sloupce.
Výsledky tohoto dotazu jsou víceméně přímo do 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
Kód PostgreSQLColumn
konstruktor
bude používat extract_value_from_default
k Ruby-ify výchozí; konec přepínač
v extract_value_from_default
je zajímavé zde:
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
Pokud je tedy výchozí hodnota vázána na sekvenci (což je id
sloupec v PostgreSQL bude), pak výchozí vyjde z databáze jako volání funkce podobné tomuto:
nextval('models_id_seq'::regclass)
To skončí ve výše uvedeném else
větev a column.default.nil?
bude pravda.
Pro id
to není problém, AR očekává, že databáze dodá hodnoty pro id
sloupců, takže je jedno, jaká je výchozí hodnota.
To je velký problém, pokud je výchozí nastavení sloupce něco, čemu AR nerozumí, řekněme volání funkce jako jako md5(random()::text)
. Problém je v tom, že AR inicializuje všechny atributy na jejich výchozí hodnoty – jako Model.columns
vidí je, ne tak, jak je vidí databáze – když řeknete Model.new
. Například v konzole uvidíte věci jako toto:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Pokud tedy def_is_function
ve skutečnosti používá jako výchozí hodnotu volání funkce, AR to bude ignorovat a pokusí se vložit NULL jako hodnotu tohoto sloupce. Tato NULL zabrání použití výchozí hodnoty a skončíte s matoucím nepořádkem. Výchozí hodnoty, kterým AR rozumí (jako jsou řetězce a čísla), však fungují dobře.
Výsledkem je, že ve skutečnosti nemůžete použít netriviální výchozí hodnoty sloupců s ActiveRecord, pokud chcete netriviální hodnotu, musíte to udělat v Ruby pomocí jednoho ze zpětných volání ActiveRecord (například before_create
).
IMO by bylo mnohem lepší, kdyby AR ponechal výchozí hodnoty na databázi, pokud jim nerozuměla:vynechat je z INSERT nebo použít DEFAULT v VALUES by přineslo mnohem lepší výsledky; AR by samozřejmě musel znovu načíst nově vytvořené objekty z databáze, aby získal všechny správné výchozí hodnoty, ale nové načtení byste potřebovali pouze v případě, že by existovaly výchozí hodnoty, kterým AR nerozuměl. Pokud else
v extract_value_from_default
místo nil
použil speciální příznak „Nevím, co to znamená“. pak by bylo triviální detekovat podmínku "Po prvním uložení musím tento objekt znovu načíst" a znovu načtete pouze v případě potřeby.
Výše uvedené je specifické pro PostgreSQL, ale proces by měl být podobný pro ostatní databáze; nicméně neposkytuji žádné záruky.