PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Comment puis-je accéder à une valeur par défaut de colonne Postgres à l'aide d'ActiveRecord ?

Lorsqu'ActiveRecord a besoin de connaître une table, il effectue une requête similaire à votre information_schema requête mais AR passera par Tables système spécifiques à PostgreSQL à la place :

  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

Recherchez la source de l'adaptateur PostgreSQL pour "regclass" et vous verrez d'autres requêtes qu'AR utilisera pour déterminer la structure de la table.

Le pg_get_expr call dans la requête ci-dessus est d'où provient la valeur par défaut de la colonne.

Les résultats de cette requête vont, plus ou moins, directement dans 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

Le PostgreSQLColumn constructeur utilisera extract_value_from_default pour Ruby-ifier la valeur par défaut ; la fin de le switch dans extract_value_from_default est intéressant ici :

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

Donc, si la valeur par défaut est liée à une séquence (qu'un id colonne dans PostgreSQL sera), alors la valeur par défaut sortira de la base de données sous la forme d'un appel de fonction similaire à ceci :

nextval('models_id_seq'::regclass)

Cela se retrouvera dans le else ci-dessus branche et column.default.nil? sera vrai.

Pour un id ce n'est pas un problème, AR s'attend à ce que la base de données fournisse les valeurs pour id colonnes afin qu'il ne se soucie pas de la valeur par défaut.

C'est un gros problème si la valeur par défaut de la colonne est quelque chose que AR ne comprend pas, dire un appel de fonction tel comme md5(random()::text) . Le problème est que AR initialisera tous les attributs à leurs valeurs par défaut - comme Model.columns les voit, pas comme la base de données les voit - quand vous dites Model.new . Par exemple, dans la console, vous verrez des choses comme ceci :

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

Donc si def_is_function utilise en fait un appel de fonction comme valeur par défaut, AR l'ignorera et essaiera d'insérer un NULL comme valeur de cette colonne. Ce NULL empêchera l'utilisation de la valeur par défaut et vous vous retrouverez avec un désordre déroutant. Les paramètres par défaut que AR peut comprendre (comme les chaînes et les nombres) fonctionnent cependant très bien.

Le résultat est que vous ne pouvez pas vraiment utiliser des valeurs de colonne par défaut non triviales avec ActiveRecord, si vous voulez une valeur non triviale, vous devez le faire dans Ruby via l'un des rappels ActiveRecord (tels que before_create ).

IMO, ce serait bien mieux si AR laissait les valeurs par défaut à la base de données si elle ne les comprenait pas :les laisser hors de l'INSERT ou utiliser DEFAULT dans les VALUES produirait de bien meilleurs résultats ; AR devrait, bien sûr, recharger les objets nouvellement créés à partir de la base de données afin d'obtenir toutes les valeurs par défaut appropriées, mais vous n'auriez besoin du rechargement que s'il y avait des valeurs par défaut qu'AR ne comprenait pas. Si le else dans extract_value_from_default utilisé un indicateur spécial "Je ne sais pas ce que cela signifie" au lieu de nil alors la condition "J'ai besoin de recharger cet objet après la première sauvegarde" serait triviale à détecter et vous ne rechargeriez que si nécessaire.

Ce qui précède est spécifique à PostgreSQL mais le processus devrait être similaire pour les autres bases de données ; cependant, je ne donne aucune garantie.