PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Como posso acessar um valor padrão de coluna Postgres usando ActiveRecord?


Quando o ActiveRecord precisa saber sobre uma tabela, ele faz uma consulta semelhante ao seu information_schema consulta, mas o AR passará pelo Tabelas de sistema específicas do PostgreSQL em vez de:
  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

Pesquise a fonte do adaptador PostgreSQL para "regclass" e você verá algumas outras consultas que o AR usará para descobrir a estrutura da tabela.

O pg_get_expr call na consulta acima é de onde vem o valor padrão da coluna.

Os resultados dessa consulta são, mais ou menos, direto para 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

O PostgreSQLColumn construtor usará extract_value_from_default para Ruby-ify o padrão; o fim de o interruptor em extract_value_from_default é interessante aqui:
else
  # Anything else is blank, some user type, or some function
  # and we can't know the value of that, so return nil.
  nil

Portanto, se o valor padrão estiver vinculado a uma sequência (que um id coluna no PostgreSQL será), então o padrão sairá do banco de dados como uma chamada de função semelhante a esta:
nextval('models_id_seq'::regclass)

Isso terminará no else acima branch e column.default.nil? será verdade.

Para um id coluna isso não é um problema, o AR espera que o banco de dados forneça os valores para id colunas para que não se importe com o valor padrão.

Este é um grande problema se o padrão da coluna for algo que o AR não entende, diga uma chamada de função como como md5(random()::text) . O problema é que o AR inicializará todos os atributos com seus valores padrão – como Model.columns vê-los, não como o banco de dados os vê - quando você diz Model.new . Por exemplo, no console, você verá coisas assim:
 > Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>

Então, se def_is_function realmente usa uma chamada de função como seu valor padrão, o AR ignorará isso e tentará inserir um NULL como o valor dessa coluna. Esse NULL impedirá que o valor padrão seja usado e você acabará com uma bagunça confusa. Os padrões que o AR pode entender (como strings e números) funcionam bem.

O resultado é que você não pode realmente usar valores de coluna padrão não triviais com ActiveRecord, se você quiser um valor não trivial, então você tem que fazer em Ruby através de um dos retornos de chamada ActiveRecord (como before_create ).

IMO seria muito melhor se AR deixasse os valores padrão para o banco de dados se não os entendesse:deixá-los fora do INSERT ou usar DEFAULT nos VALUES produziria resultados muito melhores; O AR teria, é claro, que recarregar os objetos recém-criados do banco de dados para obter todos os padrões adequados, mas você só precisaria do recarregamento se houvesse padrões que o AR não entendesse. Se o outro em extract_value_from_default usou um sinalizador especial "não sei o que isso significa" em vez de nil então a condição "Preciso recarregar este objeto após o primeiro salvamento" seria trivial para detectar e você só recarregaria quando necessário.

O acima é específico do PostgreSQL, mas o processo deve ser semelhante para outros bancos de dados; no entanto, não dou garantias.