Ketika ActiveRecord perlu mengetahui tentang sebuah tabel, ia melakukan kueri yang mirip dengan information_schema
Anda permintaan tetapi AR akan melalui Tabel sistem khusus PostgreSQL
sebagai gantinya:
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
Telusuri sumber adaptor PostgreSQL untuk "regclass" dan Anda akan melihat beberapa kueri lain yang akan digunakan AR untuk mengetahui struktur tabel.
pg_get_expr
panggilan dalam kueri di atas adalah asal nilai default kolom.
Hasil kueri itu, kurang lebih, langsung ke 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
konstruktor
akan menggunakan extract_value_from_default
untuk Ruby-ify default; akhir switch
di extract_value_from_default
menarik di sini:
else
# Anything else is blank, some user type, or some function
# and we can't know the value of that, so return nil.
nil
Jadi jika nilai default terikat ke urutan (yang merupakan id
kolom di PostgreSQL akan menjadi), maka default akan keluar dari database sebagai pemanggilan fungsi mirip dengan ini:
nextval('models_id_seq'::regclass)
Itu akan berakhir di else
di atas cabang dan column.default.nil?
akan menjadi kenyataan.
Untuk id
kolom ini bukan masalah, AR mengharapkan database menyediakan nilai untuk id
kolom sehingga tidak peduli apa nilai defaultnya.
Ini adalah masalah besar jika default kolom adalah sesuatu yang AR tidak mengerti, katakan panggilan fungsi seperti itu sebagai md5(random()::text)
. Masalahnya adalah AR akan menginisialisasi semua atribut ke nilai defaultnya – sebagai Model.columns
melihatnya, bukan seperti yang dilihat database – saat Anda mengucapkan Model.new
. Misalnya, di konsol Anda akan melihat hal-hal seperti ini:
> Model.new
=> #<Model id: nil, def_is_function: nil, def_is_zero: 0>
Jadi jika def_is_function
sebenarnya menggunakan panggilan fungsi sebagai nilai defaultnya, AR akan mengabaikannya dan mencoba memasukkan NULL sebagai nilai kolom itu. NULL itu akan mencegah nilai default digunakan dan Anda akan berakhir dengan kekacauan yang membingungkan. Default yang dapat dipahami AR (seperti string dan angka) berfungsi dengan baik.
Hasilnya adalah Anda tidak dapat benar-benar menggunakan nilai kolom default non-trivial dengan ActiveRecord, jika Anda menginginkan nilai non-trivial maka Anda harus melakukannya di Ruby melalui salah satu callback ActiveRecord (seperti before_create
).
IMO akan jauh lebih baik jika AR meninggalkan nilai default ke database jika tidak memahaminya:meninggalkannya dari INSERT atau menggunakan DEFAULT dalam VALUES akan menghasilkan hasil yang jauh lebih baik; AR tentu saja harus memuat ulang objek yang baru dibuat dari database untuk mendapatkan semua default yang tepat, tetapi Anda hanya perlu memuat ulang jika ada default yang tidak dipahami AR. Jika else
di extract_value_from_default
menggunakan tanda khusus "Saya tidak tahu apa artinya ini" alih-alih nil
maka kondisi "Saya perlu memuat ulang objek ini setelah penyimpanan pertama" akan mudah dideteksi dan Anda hanya akan memuat ulang jika diperlukan.
Di atas adalah khusus PostgreSQL tetapi prosesnya harus serupa untuk database lain; namun, saya tidak memberikan jaminan.