PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come posso accedere al valore predefinito di una colonna Postgres utilizzando ActiveRecord?

Quando ActiveRecord ha bisogno di conoscere una tabella, esegue una query simile al tuo information_schema query ma AR passerà attraverso Tabelle di sistema specifiche per PostgreSQL invece:

  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

Cerca nel fonte dell'adattatore PostgreSQL per "regclass" e vedrai alcune altre query che AR utilizzerà per capire la struttura della tabella.

Il pg_get_expr la chiamata nella query precedente è da dove viene il valore predefinito della colonna.

I risultati di quella query vanno, più o meno, a direttamente in 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

Il PostgreSQLColumn costruttore utilizzerà extract_value_from_default Ruby-ify l'impostazione predefinita; la fine il switch in extract_value_from_default è interessante qui:

else
  # Anything else is blank, some user type, or some function
  # and we can't know the value of that, so return nil.
  nil

Quindi, se il valore predefinito è legato a una sequenza (che un id sarà la colonna in PostgreSQL), quindi l'impostazione predefinita uscirà dal database come una chiamata di funzione simile a questa:

nextval('models_id_seq'::regclass)

Questo finirà in else sopra branch e column.default.nil? sarà vero.

Per un id colonna questo non è un problema, AR si aspetta che il database fornisca i valori per id colonne, quindi non importa quale sia il valore predefinito.

Questo è un grosso problema se l'impostazione predefinita della colonna è qualcosa che AR non comprende, diciamo che una funzione chiama tale come md5(random()::text) . Il problema è che AR inizializzerà tutti gli attributi ai loro valori predefiniti, come Model.columns li vede, non come li vede il database, quando dici Model.new . Ad esempio, nella console vedrai cose come questa:

 > Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>

Quindi se def_is_function utilizza effettivamente una chiamata di funzione come valore predefinito, AR lo ignorerà e proverà a inserire un NULL come valore di quella colonna. Quel NULL impedirà l'uso del valore predefinito e ti ritroverai con un pasticcio confuso. Tuttavia, i valori predefiniti che AR può comprendere (come stringhe e numeri) funzionano bene.

Il risultato è che non puoi davvero usare valori di colonna predefiniti non banali con ActiveRecord, se vuoi un valore non banale devi farlo in Ruby attraverso uno dei callback di ActiveRecord (come before_create ).

IMO sarebbe molto meglio se AR lasciasse i valori di default al database se non li capisse:lasciarli fuori dall'INSERT o usare DEFAULT nei VALUES produrrebbe risultati molto migliori; AR dovrebbe, ovviamente, ricaricare gli oggetti appena creati dal database per ottenere tutte le impostazioni predefinite corrette, ma avresti bisogno del ricaricamento solo se ci fossero impostazioni predefinite che AR non capiva. Se il else in extract_value_from_default ha usato un flag speciale "Non so cosa significa" invece di nil quindi la condizione "Ho bisogno di ricaricare questo oggetto dopo il primo salvataggio" sarebbe banale da rilevare e dovresti ricaricare solo quando necessario.

Quanto sopra è specifico di PostgreSQL ma il processo dovrebbe essere simile per altri database; tuttavia, non offro garanzie.