6

Postgres9.2データベースの列のデフォルト値にアクセスしようとしています。生のSQLを使用することで、列のデフォルトが「users_next_id()」であることを確認できます。

> db = ActiveRecord::Base.connection
> db.execute("SELECT table_name,column_name,column_default 
FROM information_schema.columns 
WHERE table_name = 'users' and column_name ='id'").first

=> {"table_name"=>"users",
 "column_name"=>"id",
 "column_default"=>"users_next_id()"}

しかし、ARの「columns」メソッドを使用すると、デフォルト値はnilのように見えます。

[26] pry(main)> db.columns('users')[0]=> #<ActiveRecord::ConnectionAdapters::PostgreSQLColumn:0x007feb397ba6e8
 @coder=nil,
 @default=nil,
 @limit=8,
 @name="id",
 @null=false,
 @precision=nil,
 @primary=nil,
 @scale=nil,
 @sql_type="bigint",
 @type=:integer>

これは(私を混乱させる以外に)問題を引き起こしていませんが、これは予想される動作ですか?'columns'メソッドについて間違った仮定をしていますか?

4

1 に答える 1

6

ActiveRecordがテーブルについて知る必要がある場合、ActiveRecordはクエリと同様のクエリを実行しますが、ARは代わりにPostgreSQL固有のシステムテーブルinformation_schemaを通過します。

  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

PostgreSQLアダプタソースで「regclass」を検索すると、ARがテーブルの構造を理解するために使用する他のクエリが表示されます。

上記のクエリのpg_get_expr呼び出しは、列のデフォルト値の取得元です。

そのクエリの結果は、多かれ少なかれ、直接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

PostgreSQLColumnコンストラクターextract_value_from_default、デフォルトをRuby化するために使用します。inの終わりはswitchextract_value_from_defaultここで興味深いです:

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

したがって、デフォルト値がシーケンス(idPostgreSQLの列にバインドされる)にバインドされている場合、デフォルトは次のような関数呼び出しとしてデータベースから出力されます。

nextval('models_id_seq'::regclass)

それは上記のelseブランチに行き着き、column.default.nil?真実になります。

列の場合、idこれは問題ではありません。ARはデータベースがid列の値を提供することを期待しているため、デフォルト値が何であるかは関係ありません。

列のデフォルトがARが理解できないものである場合、これは大きな問題です。たとえば、などの関数呼び出しmd5(random()::text)です。問題は、あなたが言うと、ARがすべての属性をデフォルト値に初期化することです-Model.columnsデータベースがそれらを見るようにではなく、それらを見るように- Model.new。たとえば、コンソールには次のようなものが表示されます。

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

したがって、def_is_function実際にデフォルト値として関数呼び出しを使用する場合、ARはそれを無視し、その列の値としてNULLを挿入しようとします。そのNULLは、デフォルト値が使用されるのを防ぎ、混乱を招く混乱を招きます。ただし、ARが理解できるデフォルト(文字列や数値など)は問題なく機能します。

その結果、ActiveRecordで重要なデフォルトの列値を実際に使用することはできません。重要な値が必要な場合は、RubyでActiveRecordコールバックの1つ(などbefore_create)を使用する必要があります。

IMOは、ARがデフォルト値を理解していない場合、それらをデータベースに任せた方がはるかに優れています。INSERTから除外するか、VALUESでDEFAULTを使用すると、はるかに優れた結果が得られます。もちろん、ARは、すべての適切なデフォルトを取得するために、データベースから新しく作成されたオブジェクトをリロードする必要がありますが、ARが理解できないデフォルトがあった場合にのみ、リロードが必要になります。elseextract_value_from_default代わりに特別な「これが何を意味するのかわからない」フラグを使用した場合、nil「最初の保存後にこのオブジェクトをリロードする必要があります」という条件を検出するのは簡単で、必要な場合にのみリロードします。


上記はPostgreSQL固有ですが、プロセスは他のデータベースでも同様である必要があります。ただし、保証はいたしかねます。

于 2013-03-06T04:29:35.807 に答える