När ActiveRecord behöver veta om en tabell gör den en fråga som liknar ditt informationsschema
fråga men AR kommer att gå igenom PostgreSQL-specifika systemtabeller
istället:
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
Sök i PostgreSQL-adapterkällan för "regclass" och du kommer att se några andra frågor som AR kommer att använda för att ta reda på tabellens struktur.
pg_get_expr
anropet i ovanstående fråga är varifrån kolumnens standardvärde kommer.
Resultaten av den frågan går, mer eller mindre, rakt in i 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
PostgreSQLColumn konstruktör
kommer att använda extract_value_from_default
att Ruby-ify standard; slutet av switchen
i extract_value_from_default
är intressant här:
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
Så om standardvärdet är bundet till en sekvens (som ett id
kolumnen i PostgreSQL kommer att vara), då kommer standarden att komma ut från databasen som ett funktionsanrop liknande detta:
nextval('models_id_seq'::regclass)
Det kommer att hamna i ovanstående else
filial och column.default.nil?
kommer att vara sant.
För ett id
kolumn detta är inte ett problem, AR förväntar sig att databasen tillhandahåller värdena för id
kolumner så att det inte bryr sig vad standardvärdet är.
Detta är ett stort problem om kolumnens standard är något som AR inte förstår, säg ett funktionsanrop som t.ex. som md5(random()::text)
. Problemet är att AR kommer att initiera alla attribut till sina standardvärden – som Model.columns
ser dem, inte som databasen ser dem – när du säger Model.new
. Till exempel, i konsolen ser du saker som detta:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Så om def_is_function
faktiskt använder ett funktionsanrop som standardvärde, kommer AR att ignorera det och försöka infoga en NULL som kolumns värde. Den NULL kommer att förhindra att standardvärdet används och du kommer att sluta med en förvirrande röra. Standardinställningar som AR kan förstå (som strängar och siffror) fungerar dock bra.
Resultatet är att du inte riktigt kan använda icke-triviala standardkolumnvärden med ActiveRecord, om du vill ha ett icke-trivialt värde måste du göra i Ruby genom en av ActiveRecord-återkallningarna (som before_create
).
IMO skulle det vara mycket bättre om AR lämnade standardvärdena till databasen om den inte förstod dem:att lämna dem utanför INSERT eller använda DEFAULT i VALUES skulle ge mycket bättre resultat; AR skulle naturligtvis behöva ladda om nyskapade objekt från databasen för att få alla korrekta standardinställningar, men du skulle bara behöva ladda om det om det fanns standardvärden som AR inte förstod. Om annat
i extract_value_from_default
använde en speciell "Jag vet inte vad detta betyder"-flagga istället för nil
då skulle villkoret "Jag måste ladda om det här objektet efter första sparandet" vara trivialt att upptäcka och du skulle bara ladda om det när det behövs.
Ovanstående är PostgreSQL-specifik men processen bör vara liknande för andra databaser; jag ger dock inga garantier.