sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo puedo acceder al valor predeterminado de una columna de Postgres usando ActiveRecord?

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.