Når ActiveRecord har brug for at kende til en tabel, udfører den en forespørgsel svarende til dit informationsskema
forespørgsel, men AR vil gå gennem PostgreSQL-specifikke systemtabeller
i stedet:
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øg i PostgreSQL-adapterkilden for "regclass", og du vil se nogle andre forespørgsler, som AR vil bruge til at finde ud af tabellens struktur.
pg_get_expr
kaldet i ovenstående forespørgsel er det sted, hvor kolonnens standardværdi kommer fra.
Resultaterne af den forespørgsel går, mere eller mindre, lige ind 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
vil bruge kode>udtræk_værdi_fra_standard
at Ruby-ify standarden; slutningen af kontakten
i extract_value_from_default
er interessant her:
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å hvis standardværdien er bundet til en sekvens (som er et id
kolonne i PostgreSQL vil være), så vil standarden komme ud af databasen som et funktionskald svarende til dette:
nextval('models_id_seq'::regclass)
Det ender i ovenstående else
branch og column.default.nil?
vil være sandt.
For et id
kolonne dette er ikke et problem, AR forventer, at databasen leverer værdierne for id
kolonner, så det er ligeglad med, hvad standardværdien er.
Dette er et stort problem, hvis kolonnens standard er noget, som AR ikke forstår, sig et funktionskald f.eks. som md5(random()::tekst)
. Problemet er, at AR vil initialisere alle attributterne til deres standardværdier – som Model.columns
ser dem, ikke som databasen ser dem – når du siger Model.new
. For eksempel, i konsollen vil du se ting som dette:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Så hvis def_is_function
faktisk bruger et funktionskald som standardværdi, vil AR ignorere det og forsøge at indsætte en NULL som kolonnens værdi. Den NULL forhindrer standardværdien i at blive brugt, og du ender med et forvirrende rod. Standarder, som AR kan forstå (såsom strenge og tal) fungerer dog fint.
Resultatet er, at du ikke rigtig kan bruge ikke-trivielle standardkolonneværdier med ActiveRecord, hvis du vil have en ikke-triviel værdi, skal du gøre det i Ruby gennem et af ActiveRecord-tilbagekaldene (såsom before_create
).
IMO ville det være meget bedre, hvis AR overlod standardværdierne til databasen, hvis den ikke forstod dem:at udelade dem fra INSERT eller bruge DEFAULT i VALUES ville give meget bedre resultater; AR ville selvfølgelig skulle genindlæse nyoprettede objekter fra databasen for at få alle de korrekte standardindstillinger, men du ville kun have brug for genindlæsningen, hvis der var standarder, som AR ikke forstod. Hvis else
i extract_value_from_default
brugt et specielt "Jeg ved ikke, hvad det betyder"-flag i stedet for nul
så ville betingelsen "Jeg skal genindlæse dette objekt efter den første lagring" være triviel at opdage, og du vil kun genindlæse, når det er nødvendigt.
Ovenstående er PostgreSQL-specifik, men processen bør være ens for andre databaser; jeg giver dog ingen garantier.