Cuando ActiveRecord necesita saber acerca de una tabla, realiza una consulta similar a su information_schema
consulta pero AR pasará por Tablas del sistema específicas de PostgreSQL
en cambio:
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
Busque el fuente del adaptador de PostgreSQL para "regclass" y verá algunas otras consultas que AR utilizará para averiguar la estructura de la tabla.
El pg_get_expr
la llamada en la consulta anterior es de donde proviene el valor predeterminado de la columna.
Los resultados de esa consulta son, más o menos, directamente a 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
El PostgreSQLColumn
constructor
utilizará extract_value_from_default
para Ruby-ificar el valor predeterminado; el final de el switch
en extract_value_from_default
es interesante aquí:
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
Entonces, si el valor predeterminado está vinculado a una secuencia (que un id
será la columna en PostgreSQL), entonces el valor predeterminado saldrá de la base de datos como una llamada de función similar a esta:
nextval('models_id_seq'::regclass)
Eso terminará en el else
anterior branch y column.default.nil?
será verdad.
Para un id
columna esto no es un problema, AR espera que la base de datos proporcione los valores para id
columnas por lo que no importa cuál es el valor predeterminado.
Este es un gran problema si el valor predeterminado de la columna es algo que AR no entiende, di una llamada de función como como md5(random()::text)
. El problema es que AR inicializará todos los atributos a sus valores predeterminados, como Model.columns
los ve, no como los ve la base de datos, cuando dice Model.new
. Por ejemplo, en la consola verá cosas como esta:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Entonces si def_is_function
en realidad usa una llamada de función como su valor predeterminado, AR lo ignorará e intentará insertar un NULL como valor de esa columna. Ese NULL evitará que se use el valor predeterminado y terminará con un lío confuso. Sin embargo, los valores predeterminados que AR puede entender (como cadenas y números) funcionan bien.
El resultado es que realmente no puede usar valores de columna predeterminados no triviales con ActiveRecord, si desea un valor no trivial, debe hacerlo en Ruby a través de una de las devoluciones de llamada de ActiveRecord (como before_create
).
En mi opinión, sería mucho mejor si AR dejara los valores predeterminados en la base de datos si no los entendiera:dejarlos fuera del INSERTAR o usar DEFAULT en los VALORES produciría resultados mucho mejores; AR, por supuesto, tendría que recargar los objetos recién creados de la base de datos para obtener todos los valores predeterminados adecuados, pero solo necesitaría la recarga si hubiera valores predeterminados que AR no entendiera. Si el else
en extract_value_from_default
usó un indicador especial "No sé lo que esto significa" en lugar de nil
entonces la condición "Necesito recargar este objeto después del primer guardado" sería trivial de detectar y solo recargaría cuando sea necesario.
Lo anterior es específico de PostgreSQL, pero el proceso debería ser similar para otras bases de datos; sin embargo, no doy garantías.