Bagaimana saya bisa menetapkan nilai default di ActiveRecord?
Saya melihat posting dari Pratik yang menggambarkan potongan kode yang jelek dan rumit: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model
class Item < ActiveRecord::Base
def initialize_with_defaults(attrs = nil, &block)
initialize_without_defaults(attrs) do
setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
!attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
setter.call('scheduler_type', 'hotseat')
yield self if block_given?
end
end
alias_method_chain :initialize, :defaults
end
Saya telah melihat contoh-contoh berikut di Google:
def initialize
super
self.status = ACTIVE unless self.status
end
dan
def after_initialize
return unless new_record?
self.status = ACTIVE
end
Saya juga melihat orang memasukkannya ke dalam migrasi, tetapi saya lebih suka melihatnya didefinisikan dalam kode model.
Apakah ada cara kanonik untuk menetapkan nilai default untuk bidang dalam model ActiveRecord?
Jawaban:
Ada beberapa masalah dengan masing-masing metode yang tersedia, tetapi saya percaya bahwa mendefinisikan
after_initialize
panggilan balik adalah cara untuk pergi karena alasan berikut:default_scope
akan menginisialisasi nilai untuk model baru, tetapi kemudian itu akan menjadi ruang lingkup di mana Anda menemukan model. Jika Anda hanya ingin menginisialisasi beberapa angka ke 0 maka ini bukan yang Anda inginkan.initialize
bisa berhasil, tapi jangan lupa meneleponsuper
!after_initialize
sudah tidak digunakan lagi pada Rails 3. Ketika saya menimpaafter_initialize
di rails 3.0.3 saya mendapatkan peringatan berikut di konsol:Karena itu saya akan mengatakan menulis
after_initialize
panggilan balik, yang memungkinkan Anda atribut default selain membiarkan Anda menetapkan default pada asosiasi seperti:Sekarang Anda hanya memiliki satu tempat untuk mencari inisialisasi model Anda. Saya menggunakan metode ini sampai seseorang menemukan yang lebih baik.
Peringatan:
Untuk bidang boolean lakukan:
self.bool_field = true if self.bool_field.nil?
Lihat komentar Paul Russell tentang jawaban ini untuk lebih jelasnya
Jika Anda hanya memilih subset kolom untuk model (yaitu; menggunakan
select
dalam query sepertiPerson.select(:firstname, :lastname).all
) Anda akan mendapatkanMissingAttributeError
jikainit
metode Anda mengakses kolom yang belum dimasukkan dalamselect
klausa. Anda dapat menjaga terhadap kasus ini seperti:self.number ||= 0.0 if self.has_attribute? :number
dan untuk kolom boolean ...
self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
Perhatikan juga bahwa sintaksnya berbeda sebelum Rails 3.2 (lihat komentar Cliff Darling di bawah)
sumber
initialize
, sepertinya sangat berbelit-belit untuk sesuatu yang harus jelas dan didefinisikan dengan baik. Saya menghabiskan berjam-jam menjelajahi dokumentasi sebelum mencari di sini karena saya menganggap fungsi ini sudah ada di suatu tempat dan saya tidak menyadarinya.self.bool_field ||= true
, karena ini akan memaksa bidang menjadi true bahkan jika Anda secara eksplisit menginisialisasi ke false. Sebaliknya lakukanself.bool_field = true if self.bool_field.nil?
.MissingAttributeError
. Anda dapat menambahkan pemeriksaan ekstra seperti yang ditunjukkan:self.number ||= 0.0 if self.has_attribute? :number
Untuk boolean:self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?
. Ini adalah Rails 3.2+ - untuk yang sebelumnya, gunakanself.attributes.has_key?
, dan Anda harus menggunakan string alih-alih simbol.initialize
denganreturn if !new_record?
untuk menghindari masalah kinerja.Rails 5+
Anda dapat menggunakan metode atribut dalam model Anda, misalnya .:
Anda juga dapat meneruskan lambda ke
default
parameter. Contoh:sumber
Value
dan tidak ada typecasting akan dilakukan.store_accessor :my_jsonb_column, :locale
Anda kemudian dapat mendefinisikanattribute :locale, :string, default: 'en'
nil
. Jika tidak bisanil
DBnot null
+ DB default + github.com/sshaw/keep_defaults adalah cara untuk pergi dari pengalaman sayaKami menempatkan nilai-nilai default di database melalui migrasi (dengan menentukan
:default
opsi pada setiap definisi kolom) dan membiarkan Rekaman Aktif menggunakan nilai-nilai ini untuk mengatur default untuk setiap atribut.IMHO, pendekatan ini selaras dengan prinsip-prinsip AR: konvensi atas konfigurasi, KERING, definisi tabel menggerakkan model, bukan sebaliknya.
Perhatikan bahwa default masih dalam kode aplikasi (Ruby), meskipun tidak dalam model tetapi dalam migrasi.
sumber
Beberapa kasus sederhana dapat ditangani dengan mendefinisikan default dalam skema database tetapi itu tidak menangani sejumlah kasus rumit termasuk nilai yang dihitung dan kunci dari model lain. Untuk kasus ini saya melakukan ini:
Saya telah memutuskan untuk menggunakan after_initialize tetapi saya tidak ingin itu diterapkan ke objek yang hanya ditemukan yang baru atau dibuat. Saya pikir itu hampir mengejutkan bahwa panggilan balik after_new tidak disediakan untuk kasus penggunaan yang jelas ini tetapi saya lakukan dengan mengkonfirmasi apakah objek sudah bertahan yang menunjukkan bahwa itu bukan barang baru.
Setelah melihat jawaban Brad Murray ini bahkan lebih bersih jika kondisinya dipindahkan ke permintaan callback:
sumber
:before_create
?Pola panggilan balik after_initialize dapat ditingkatkan dengan hanya melakukan hal berikut
Ini memiliki manfaat non-sepele jika kode init Anda perlu berurusan dengan asosiasi, karena kode berikut ini memicu n + 1 yang halus jika Anda membaca catatan awal tanpa menyertakan yang terkait.
sumber
Orang-orang Phusion punya beberapa plugin yang bagus untuk ini.
sumber
:default
nilai dalam migrasi skema untuk 'hanya berfungsi' denganModel.new
.:default
nilai dalam migrasi untuk 'hanya bekerja' denganModel.new
, bertentangan dengan apa yang dikatakan Jeff di posnya. Diverifikasi bekerja di Rails 4.1.16.Cara potensial yang lebih baik / lebih bersih daripada jawaban yang diajukan adalah menimpa accessor, seperti ini:
Lihat "Menimpa aksesor default" di ActiveRecord :: dokumentasi Base dan lainnya dari StackOverflow tentang penggunaan mandiri .
sumber
attributes
. Diuji dalam rel 5.2.0.Saya menggunakan
attribute-defaults
permataDari dokumentasi: jalankan
sudo gem install attribute-defaults
dan tambahkanrequire 'attribute_defaults'
ke aplikasi Anda.sumber
Pertanyaan serupa, tetapi semua memiliki konteks yang sedikit berbeda: - Bagaimana cara membuat nilai default untuk atribut dalam model Rails activerecord?
Jawaban Terbaik: Tergantung pada Apa yang Anda Inginkan!
Jika Anda ingin setiap objek memulai dengan sebuah nilai: gunakan
after_initialize :init
Anda ingin
new.html
formulir memiliki nilai default saat membuka halaman? gunakan https://stackoverflow.com/a/5127684/1536309Jika Anda ingin setiap objek memiliki nilai yang dihitung dari input pengguna: gunakan
before_save :default_values
Anda ingin pengguna memasukkanX
laluY = X+'foo'
? menggunakan:sumber
Inilah gunanya konstruktor! Ganti metode modelinitialize
.Gunakan
after_initialize
metodenya.sumber
after_initialize
metode ini sebagai gantinya.Sup kawan, saya akhirnya melakukan hal berikut:
Bekerja seperti pesona!
sumber
Ini telah dijawab sejak lama, tetapi saya sering membutuhkan nilai default dan memilih untuk tidak memasukkannya ke dalam basis data. Saya membuat
DefaultValues
keprihatinan:Dan kemudian menggunakannya dalam model saya seperti ini:
sumber
Cara kanonik Rails, sebelum Rails 5, sebenarnya untuk mengaturnya dalam migrasi, dan lihat saja
db/schema.rb
untuk setiap kali ingin melihat apa nilai-nilai default yang ditetapkan oleh DB untuk model apa pun.Bertentangan dengan apa yang dijawab oleh @Jeff Perrin (yang agak lama), pendekatan migrasi bahkan akan menerapkan default saat menggunakan
Model.new
, karena beberapa sihir Rails. Diverifikasi bekerja di Rails 4.1.16.Yang paling sederhana seringkali yang terbaik. Kurang pengetahuan hutang dan potensi titik kebingungan dalam basis kode. Dan itu 'hanya berfungsi'.
Atau, untuk perubahan kolom tanpa membuat yang baru, maka lakukan:
Atau mungkin lebih baik:
Periksa panduan RoR resmi untuk opsi dalam metode penggantian kolom.
Tidak
null: false
diizinkan nilai NULL dalam DB, dan, sebagai manfaat tambahan, itu juga memperbarui sehingga semua catatan DB yang sudah ada sebelumnya yang sebelumnya nol ditetapkan dengan nilai default untuk bidang ini juga. Anda dapat mengecualikan parameter ini dalam migrasi jika Anda mau, tetapi saya merasa sangat berguna!Cara kanonik di Rails 5+ adalah, seperti yang dikatakan @Lucas Caton:
sumber
Masalah dengan solusi after_initialize adalah Anda harus menambahkan after_initialize ke setiap objek yang Anda cari dari DB, terlepas dari apakah Anda mengakses atribut ini atau tidak. Saya menyarankan pendekatan yang malas.
Metode atribut (getter) tentu saja merupakan metode itu sendiri, sehingga Anda dapat menimpanya dan memberikan default. Sesuatu seperti:
Kecuali, seperti yang ditunjukkan seseorang, Anda perlu melakukan Foo.find_by_status ('ACTIVE'). Dalam hal ini saya pikir Anda benar-benar harus menetapkan default di kendala database Anda, jika DB mendukungnya.
sumber
Saya mengalami masalah dengan
after_initialize
memberikanActiveModel::MissingAttributeError
kesalahan saat melakukan penemuan kompleks:misalnya:
"search" di
.where
hash of conditionsJadi saya akhirnya melakukannya dengan mengganti inisialisasi dengan cara ini:
The
super
panggilan diperlukan untuk memastikan objek menginisialisasi dengan benar dariActiveRecord::Base
sebelum melakukan kode customize saya, yaitu: default_valuessumber
def initialize(*); super; default_values; end
di Rails 5.2.0. Plus, nilai default tersedia, bahkan dalam.attributes
hash.sumber
Meskipun melakukan itu untuk menetapkan nilai default membingungkan dan canggung dalam kebanyakan kasus, Anda dapat menggunakannya
:default_scope
juga. Lihat komentar squil di sini .sumber
Metode after_initialize sudah usang, gunakan panggilan balik sebagai gantinya.
namun, menggunakan : default dalam migrasi Anda masih merupakan cara terbersih.
sumber
after_initialize
Metode ini tidak ditinggalkan . Bahkan, panggilan balik gaya makro yang Anda berikan contoh IS sudah usang . Detail: guides.rubyonrails.org/...Saya telah menemukan bahwa menggunakan metode validasi memberikan banyak kontrol terhadap pengaturan default. Anda bahkan dapat mengatur default (atau gagal validasi) untuk pembaruan. Anda bahkan menetapkan nilai default yang berbeda untuk insert vs update jika Anda benar-benar menginginkannya. Perhatikan bahwa default tidak akan ditetapkan hingga #valid? disebut.
Mengenai mendefinisikan metode after_initialize, mungkin ada masalah kinerja karena after_initialize juga dipanggil oleh setiap objek yang dikembalikan oleh: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize-and-after_find
sumber
new
tindakan dapat digunakan kembali.Jika kolom tersebut adalah kolom tipe 'status', dan model Anda cocok untuk penggunaan mesin negara, pertimbangkan untuk menggunakan permata aasm , setelah itu Anda cukup melakukan
Itu masih tidak menginisialisasi nilai untuk catatan yang belum disimpan, tetapi itu sedikit lebih bersih daripada menggulung sendiri dengan
init
atau apa pun, dan Anda menuai manfaat lain dari aasm seperti cakupan untuk semua status Anda.sumber
https://github.com/keithrowell/rails_default_value
sumber
Saya sangat menyarankan menggunakan permata "default_value_for": https://github.com/FooBarWidget/default_value_for
Ada beberapa skenario rumit yang cukup banyak mengharuskan mengganti metode inisialisasi, yang permata itu lakukan.
Contoh:
Default db Anda adalah NULL, model Anda / default yang ditentukan ruby adalah "some string", tetapi Anda sebenarnya ingin mengatur nilai menjadi nil karena alasan apa pun:
MyModel.new(my_attr: nil)
Sebagian besar solusi di sini akan gagal untuk mengatur nilai ke nol, dan sebagai gantinya akan menetapkannya ke default.
OK, jadi alih-alih mengambil
||=
pendekatan, Anda beralih kemy_attr_changed?
...TAPI sekarang bayangkan standar db Anda adalah "beberapa string", model Anda / default yang ditentukan ruby adalah "beberapa string lain", tetapi di bawah skenario tertentu, Anda ingin mengatur nilai ke "beberapa string" (default db):
MyModel.new(my_attr: 'some_string')
Hal ini akan mengakibatkan
my_attr_changed?
menjadi palsu karena nilai sesuai dengan standar db, yang pada gilirannya akan api kode default ruby-didefinisikan Anda dan mengatur nilai untuk "beberapa string lain" - lagi, bukan apa yang Anda inginkan.Untuk alasan itu saya tidak berpikir ini bisa diselesaikan dengan baik hanya dengan kait after_initialize.
Sekali lagi, saya pikir permata "default_value_for" mengambil pendekatan yang tepat: https://github.com/FooBarWidget/default_value_for
sumber
Inilah solusi yang saya gunakan sehingga saya sedikit terkejut belum ditambahkan.
Ada dua bagian untuk itu. Bagian pertama adalah pengaturan default dalam migrasi aktual, dan bagian kedua adalah menambahkan validasi dalam model memastikan bahwa kehadiran itu benar.
Jadi, Anda akan melihat di sini bahwa default sudah ditetapkan. Sekarang dalam validasi Anda ingin memastikan bahwa selalu ada nilai untuk string, jadi lakukan saja
Apa yang akan dilakukan adalah menetapkan nilai default untuk Anda. (bagi saya, saya memiliki "Selamat Datang di Tim"), dan kemudian akan selangkah lebih maju memastikan bahwa selalu ada nilai hadiah untuk objek itu.
Semoga itu bisa membantu!
sumber
sumber
gunakan default_scope di rails 3
api doc
ActiveRecord mengaburkan perbedaan antara default yang didefinisikan dalam database (skema) dan default yang dilakukan dalam aplikasi (model). Selama inisialisasi, ini mem-parsing skema database dan mencatat nilai-nilai default yang ditentukan di sana. Kemudian, ketika membuat objek, itu menetapkan nilai-nilai default yang ditentukan skema tanpa menyentuh database.
diskusi
sumber
default_scope
. Ini akan membuat semua pertanyaan Anda menambahkan kondisi ini ke bidang yang telah Anda atur. Hampir TIDAK PERNAH apa yang Anda inginkan.Dari api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Gunakan
before_validation
metode dalam model Anda, ini memberi Anda pilihan untuk membuat inisialisasi spesifik untuk membuat dan memperbarui panggilan misalnya dalam contoh ini (kode lagi diambil dari contoh api docs) bidang angka diinisialisasi untuk kartu kredit. Anda dapat dengan mudah mengadaptasi ini untuk menetapkan nilai apa pun yang Anda inginkanTerkejut bahwa itu belum disarankan di sini
sumber