当 ActiveRecord 需要了解一个表时,它会执行与您的查询类似的information_schema
查询,但 AR 将通过PostgreSQL 特定的系统表来代替:
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-ify 默认值;in的结尾switch
extract_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
因此,如果默认值绑定到一个序列(id
PostgreSQL 中的列将是),那么默认值将作为类似于以下的函数调用从数据库中出来:
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>
So if def_is_function
actually uses a function call as its default value, AR will ignore that and try to insert a NULL as that column's value. That NULL will prevent the default value from being used and you'll end up with a confusing mess. Defaults that AR can understand (such as strings and numbers) work just fine though.
The result is that you can't really use non-trivial default column values with ActiveRecord, if you want a non-trivial value then you have to do in Ruby through one of the ActiveRecord callbacks (such as before_create
).
IMO it would be much better if AR left the default values up to the database if it didn't understand them: leaving them out of the INSERT or using DEFAULT in the VALUES would produce much better results; AR would, of course, have to reload newly created objects from the database in order to get all the proper defaults but you'd only need the reload if there were defaults that AR didn't understand. If the else
in extract_value_from_default
used a special "I don't know what this means" flag instead of nil
then the "I need to reload this object after the first save" condition would be trivial to detect and you'd only reload when necessary.
The above is PostgreSQL-specific but the process should be similar for other databases; however, I make no guarantees.