如何使用ActiveRecord访问Postgres列的默认值?

时间:2013-03-05 23:58:38

标签: ruby-on-rails postgresql activerecord rails-activerecord

我正在尝试访问Postgres 9.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>

这不会导致任何问题(除了让我感到困惑),但这是预期的行为吗?我对“列”方法做出了错误的假设吗?

1 个答案:

答案 0 :(得分: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 constructorextract_value_from_default用于Ruby-ify默认值; extract_value_from_default中的end 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的非平凡的默认列值,如果你想要一个非平凡的值,那么你必须通过一个ActiveRecord回调在Ruby中做(例如{{1} })。

IMO如果AR不理解它们会将默认值保留到数据库会好得多:将它们从INSERT中删除或在VALUES中使用DEFAULT会产生更好的结果;当然,AR必须从数据库重新加载新创建的对象才能获得所有正确的默认值,但是如果存在AR不理解的默认值,则只需要重新加载。如果before_create中的else使用了特殊的“我不知道这意味着什么”而不是extract_value_from_default那么“我需要在第一次保存后重新加载此对象”条件很容易检测到,你只需要在必要时重新加载。


以上是PostgreSQL特有的,但其他数据库的过程应该类似;但是,我不保证。