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.