3

我想在我的Postgres 9.2數據庫中訪問一個列的默認值。通過使用原始SQL,我可以確認的是,列默認爲「users_next_id()」:如何使用ActiveRecord訪問Postgres列的默認值?

> 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的「列」方法,默認值似乎是零:

[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> 

這不會造成任何問題(除了讓我感到困惑外),但這是否是預期的行爲?我是否對'列'方法做出了錯誤的假設?

+1

**簡短答案**:AR不知道'users_next_id()'意味着什麼,所以它假裝它不存在;對'id'列有特殊的處理,所以'columns'中的不正確的默認值不會引起麻煩(但只在'id'的特定情況下)。 **長答案**:這個遲到了,現在就跑。 – 2013-03-06 00:33:46

+0

感謝您的信息。我之前一定是做錯了什麼,因爲現在我看到了其他列的默認值。你的回答很有道理。我認爲ID可能很特別,謝謝您的確認,這讓我再次檢查我的數據。 (感謝upvote - 我們noobs需要我們可以得到的所有鼓勵:) :) – MothOnMars 2013-03-06 00:50:37

+2

這是一個很好的問題,因爲ActiveRecord在這裏的行爲很難直觀。像Rails一樣,找出WTF的唯一方法就是通過Rails源代碼進行瀏覽,這是一個相當先進的主題。嘿,每個人都以小菜作爲開始。 – 2013-03-06 04:34:05

回答

5

ActiveRecord的時候需要知道它類似於查詢到您的information_schema查詢,但AR將通過PostgreSQL-specific system tables不是表:

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 adapter source爲「regclass的」,你會看到一些AR將使用其他查詢來確定表的結構。

上述查詢中的pg_get_expr調用是列的默認值來自哪裏。

該查詢的結果去,或多或少,straight into 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 constructor將使用extract_value_from_default到Ruby-IFY的默認值;在extract_value_from_defaultend of the switch是這裏有意思:

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

因此,如果默認值綁定到一個序列(在PostgreSQL的一個id列會),那麼默認將類似於一個函數調用來對數據庫進行爲此:

nextval('models_id_seq'::regclass) 

這將在上面else分支結束和column.default.nil?將是真實的。

對於id列這不是問題,AR期望數據庫提供id列的值,因此它不關心默認值是什麼。

如果列的默認值是AR不理解的東西,那麼這是個大問題,say a function call such as 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回調之一(例如before_create)在Ruby中執行,您無法真正在ActiveRecord中使用不平凡的默認列值。

IMO它會好得多,如果AR如果不理解它們,則將缺省值留給數據庫:將它們從INSERT中刪除或在VALUES中使用DEFAULT會產生更好的結果;當然,AR會從數據庫中重新加載新創建的對象,以便獲得所有正確的默認值,但是如果存在AR不瞭解的默認值,則只需要重新加載。如果在extract_value_from_default中的else使用了一個特殊的「我不知道這意味着什麼」標誌而不是nil,那麼「我需要在第一次保存後重新加載此對象」條件將很容易檢測到,並且只有在必要時纔會重新加載。


以上是PostgreSQL特有的,但其他數據庫的過程應該是類似的;但是,我沒有保證。

+0

這非常有幫助。我非常感謝這個詳細的答案。我仍然在尋找pg_catalog數據的方法,因此瞭解PG在後端做什麼以及查看AR連接適配器代碼會很有幫助。你的回答也提醒我尾巴我的PG開發日誌,看看我的ActiveRecord調用產生了什麼內部PG查詢。非常感謝! – MothOnMars 2013-03-06 18:13:03