Bagaimana saya bisa menetapkan nilai default di ActiveRecord?

417

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?

ryw
sumber
Sepertinya Anda sendiri yang menjawab pertanyaan, dalam dua varian berbeda :)
Adam Byrtek
19
Perhatikan bahwa idiom Ruby "standar" untuk 'self.status = ACTIVE kecuali self.status' adalah 'self.status || = ACTIVE'
Mike Woodhouse
1
Jawaban Jeff Perrin jauh lebih baik daripada jawaban yang sekarang diterima. default_scope adalah solusi yang tidak dapat diterima untuk menetapkan nilai default, karena ia memiliki EFEK SISI BESAR yang juga mengubah perilaku kueri.
lawrence
lihat juga stackoverflow.com/questions/3975161/…
Viktor Trón
2
diberikan semua upvotes untuk pertanyaan ini, saya akan mengatakan Ruby membutuhkan metode setDefaultValue untuk ActiveRecord
spartikus

Jawaban:

557

Ada beberapa masalah dengan masing-masing metode yang tersedia, tetapi saya percaya bahwa mendefinisikan after_initializepanggilan balik adalah cara untuk pergi karena alasan berikut:

  1. default_scopeakan 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.
  2. Menentukan default dalam migrasi Anda juga berfungsi sebagian waktu ... Seperti yang telah disebutkan ini tidak akan berfungsi ketika Anda baru saja memanggil Model.new.
  3. Mengganti initializebisa berhasil, tapi jangan lupa menelepon super!
  4. Menggunakan plugin seperti phusion menjadi sedikit konyol. Ini ruby, apakah kita benar-benar membutuhkan plugin hanya untuk menginisialisasi beberapa nilai default?
  5. Overriding after_initialize sudah tidak digunakan lagi pada Rails 3. Ketika saya menimpa after_initializedi rails 3.0.3 saya mendapatkan peringatan berikut di konsol:

PERINGATAN DEPRECASI: Base # after_initialize telah ditinggalkan, silakan gunakan Base.after_initialize: metode sebagai gantinya. (dipanggil dari / Users / me / myapp / app / models / my_model: 15)

Karena itu saya akan mengatakan menulis after_initializepanggilan balik, yang memungkinkan Anda atribut default selain membiarkan Anda menetapkan default pada asosiasi seperti:

  class Person < ActiveRecord::Base
    has_one :address
    after_initialize :init

    def init
      self.number  ||= 0.0           #will set the default value only if it's nil
      self.address ||= build_address #let's you set a default association
    end
  end    

Sekarang Anda hanya memiliki satu tempat untuk mencari inisialisasi model Anda. Saya menggunakan metode ini sampai seseorang menemukan yang lebih baik.

Peringatan:

  1. Untuk bidang boolean lakukan:

    self.bool_field = true if self.bool_field.nil?

    Lihat komentar Paul Russell tentang jawaban ini untuk lebih jelasnya

  2. Jika Anda hanya memilih subset kolom untuk model (yaitu; menggunakan selectdalam query seperti Person.select(:firstname, :lastname).all) Anda akan mendapatkan MissingAttributeErrorjika initmetode Anda mengakses kolom yang belum dimasukkan dalam selectklausa. 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)

Jeff Perrin
sumber
7
Ini jelas merupakan cara terbaik untuk mencapai ini. Yang benar-benar aneh dan malang. Metode pilihan yang masuk akal untuk menetapkan default atribut model pada penciptaan tampaknya seperti sesuatu yang seharusnya sudah dibangun oleh Rails. Satu-satunya cara lain (dapat diandalkan), mengesampingkan 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.
seaneshbaugh
106
Satu catatan tentang ini - jika Anda memiliki bidang boolean yang Anda ingin default, jangan lakukan self.bool_field ||= true, karena ini akan memaksa bidang menjadi true bahkan jika Anda secara eksplisit menginisialisasi ke false. Sebaliknya lakukan self.bool_field = true if self.bool_field.nil?.
Paul Russell
2
Mengenai poin # 2, Model.new benar-benar berfungsi (hanya untuk saya?) Bersama dengan default yang didefinisikan dalam migrasi, atau lebih tepatnya dengan nilai default untuk kolom tabel. Tetapi saya menyadari bahwa metode Jeff berdasarkan callback after_initialize mungkin adalah cara terbaik untuk melakukannya. Hanya sebuah pertanyaan: apakah itu berfungsi dengan benda-benda kotor namun belum disimpan? Dalam contoh Anda, apakah Person.new.number_was mengembalikan 0,0?
Laurent Farcy
21
Perhatian saat menggunakan pendekatan ini dikombinasikan dengan memilih kolom tertentu dengan catatan aktif. Dalam hal ini hanya atribut yang ditentukan dalam kueri yang akan ditemukan di objek dan kode init akan membuang a 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, gunakan self.attributes.has_key?, dan Anda harus menggunakan string alih-alih simbol.
Cliff Darling
6
Melakukan ini dengan asosiasi akan bersemangat memuat asosiasi tersebut pada pencarian. Mulai initializedengan return if !new_record?untuk menghindari masalah kinerja.
Kyle Macey
68

Rails 5+

Anda dapat menggunakan metode atribut dalam model Anda, misalnya .:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

Anda juga dapat meneruskan lambda ke defaultparameter. Contoh:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }
Lucas Caton
sumber
3
ahhhhh ini permata yang kucari! default dapat mengambil proc juga, misal default: -> {Time.current.to_date}
schpet
1
Pastikan untuk menentukan tipe sebagai argumen kedua, jika tidak maka tipe akan Valuedan tidak ada typecasting akan dilakukan.
null
saya senang, ini juga bekerja dengan store_accessor, misalnya mengingat store_accessor :my_jsonb_column, :localeAnda kemudian dapat mendefinisikanattribute :locale, :string, default: 'en'
Ryan Romanchuk
Oh itu fantastis, saya perlu default untuk ditampilkan dalam bentuk dan ini berfungsi dengan baik. Terima kasih, Lucas.
Paul Watson
Kita masih bisa mengatur ini nil. Jika tidak bisa nilDB not null+ DB default + github.com/sshaw/keep_defaults adalah cara untuk pergi dari pengalaman saya
sshaw
47

Kami menempatkan nilai-nilai default di database melalui migrasi (dengan menentukan :defaultopsi 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.

Laurent Farcy
sumber
3
Masalah lain adalah ketika Anda menginginkan nilai default untuk kunci asing. Anda tidak dapat membuat kode nilai ID ke bidang kunci asing karena pada DB yang berbeda ID mungkin berbeda.
shmichael
2
namun masalah lain adalah, bahwa dengan cara ini Anda tidak dapat menginisialisasi pengakses tidak persisten (atribut yang bukan kolom db).
Viktor Trón
2
masalah lain adalah Anda tidak dapat melihat semua nilai default di satu tempat. mereka mungkin tersebar melalui berbagai migrasi.
deklan
8
declan, ada db / schema.rb
Benjamin Atkin
6
Saya ingin menyebutkan untuk pembaca masa depan: setidaknya dari apa yang saya baca, ini bertentangan dengan prinsip-prinsip AR. Logika untuk model harus berada di dalam kelas model, dan basis data harus sebodoh mungkin. Nilai default untuk saya merupakan logika spesifik tentang model.
darethas
40

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:

after_initialize :defaults

def defaults
   unless persisted?
    self.extras||={}
    self.other_stuff||="This stuff"
    self.assoc = [OtherModel.find_by_name('special')]
  end
end

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:

after_initialize :defaults, unless: :persisted?
              # ":if => :new_record?" is equivalent in this context

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
  self.assoc = [OtherModel.find_by_name('special')]
end
Joseph Lord
sumber
4
Ini adalah poin yang sangat penting. Saya harus membayangkan bahwa dalam kebanyakan kasus, pengaturan default pada catatan hanya dilakukan sebelum mempertahankan rekaman baru, bukan ketika memuat rekaman yang ada.
Russell Silva
Terima kasih, kau menyelamatkan hariku.
stephanfriedrich
2
Bagaimana dengan :before_create?
Franklin Yu
Bagaimana caranya: before_create menangani panggilan baru dan menyimpan yang terpisah? Saya ingin memeriksa itu dan benar-benar mengerti sebelum beralih ke sana.
Joseph Lord
17

Pola panggilan balik after_initialize dapat ditingkatkan dengan hanya melakukan hal berikut

after_initialize :some_method_goes_here, :if => :new_record?

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.

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end
Brad Murray
sumber
16

Orang-orang Phusion punya beberapa plugin yang bagus untuk ini.

Milan Novota
sumber
Catatan, plugin ini memungkinkan :defaultnilai dalam migrasi skema untuk 'hanya berfungsi' dengan Model.new.
jchook
Saya bisa mendapatkan :defaultnilai dalam migrasi untuk 'hanya bekerja' dengan Model.new, bertentangan dengan apa yang dikatakan Jeff di posnya. Diverifikasi bekerja di Rails 4.1.16.
Magne
8

Saya menggunakan attribute-defaultspermata

Dari dokumentasi: jalankan sudo gem install attribute-defaultsdan tambahkan require 'attribute_defaults'ke aplikasi Anda.

class Foo < ActiveRecord::Base
  attr_default :age, 18
  attr_default :last_seen do
    Time.now
  end
end

Foo.new()           # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"
bantu
sumber
7

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: gunakanafter_initialize :init

Anda ingin new.htmlformulir memiliki nilai default saat membuka halaman? gunakan https://stackoverflow.com/a/5127684/1536309

class Person < ActiveRecord::Base
  has_one :address
  after_initialize :init

  def init
    self.number  ||= 0.0           #will set the default value only if it's nil
    self.address ||= build_address #let's you set a default association
  end
  ...
end 

Jika Anda ingin setiap objek memiliki nilai yang dihitung dari input pengguna: gunakanbefore_save :default_values Anda ingin pengguna memasukkanXlaluY = X+'foo'? menggunakan:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end
Blair Anderson
sumber
4

Inilah gunanya konstruktor! Ganti metode model initialize.

Gunakan after_initializemetodenya.

John Topley
sumber
2
Biasanya Anda akan benar tetapi Anda tidak boleh mengganti menginisialisasi dalam model ActiveRecord karena mungkin tidak selalu dipanggil. Anda harus menggunakan after_initializemetode ini sebagai gantinya.
Luke Redpath
Menggunakan default_scope hanya untuk menetapkan standar adalah salah. after_initialize adalah jawaban yang benar.
joaomilho
4

Sup kawan, saya akhirnya melakukan hal berikut:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

Bekerja seperti pesona!

Tony
sumber
3

Ini telah dijawab sejak lama, tetapi saya sering membutuhkan nilai default dan memilih untuk tidak memasukkannya ke dalam basis data. Saya membuat DefaultValueskeprihatinan:

module DefaultValues
  extend ActiveSupport::Concern

  class_methods do
    def defaults(attr, to: nil, on: :initialize)
      method_name = "set_default_#{attr}"
      send "after_#{on}", method_name.to_sym

      define_method(method_name) do
        if send(attr)
          send(attr)
        else
          value = to.is_a?(Proc) ? to.call : to
          send("#{attr}=", value)
        end
      end

      private method_name
    end
  end
end

Dan kemudian menggunakannya dalam model saya seperti ini:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end
clem
sumber
3

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?

Cara kanonik Rails, sebelum Rails 5, sebenarnya untuk mengaturnya dalam migrasi, dan lihat saja db/schema.rbuntuk 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'.

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

Atau, untuk perubahan kolom tanpa membuat yang baru, maka lakukan:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

Atau mungkin lebih baik:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

Periksa panduan RoR resmi untuk opsi dalam metode penggantian kolom.

Tidak null: falsediizinkan 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:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end
Magne
sumber
1

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:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

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.

Jeff Gran
sumber
Solusi ini dan alternatif yang diusulkan tidak berfungsi dalam kasus saya: Saya memiliki hierarki kelas IMS di mana hanya satu kelas yang memiliki atribut itu, dan kolom terkait yang akan digunakan dalam kondisi permintaan DB.
cmoran92
1

Saya mengalami masalah dengan after_initializememberikan ActiveModel::MissingAttributeErrorkesalahan saat melakukan penemuan kompleks:

misalnya:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"search" di .wherehash of conditions

Jadi saya akhirnya melakukannya dengan mengganti inisialisasi dengan cara ini:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

The superpanggilan diperlukan untuk memastikan objek menginisialisasi dengan benar dari ActiveRecord::Basesebelum melakukan kode customize saya, yaitu: default_values

Sean
sumber
Saya suka itu. Yang perlu saya lakukan def initialize(*); super; default_values; enddi Rails 5.2.0. Plus, nilai default tersedia, bahkan dalam .attributeshash.
spyle
1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end
Mike Breen
sumber
2
Mmmhh ... tampaknya cerdik pada awalnya, tetapi setelah berpikir sedikit, saya melihat beberapa masalah. Pertama, semua nilai default tidak dalam satu titik, tetapi tersebar melalui kelas (bayangkan mencari mereka atau mengubahnya). Kedua dan terburuk, Anda tidak dapat menempatkan, nanti, nilai nol (atau bahkan salah!).
paradoja
mengapa Anda harus menetapkan nilai nol sebagai default? Anda mendapatkannya dari kotak dengan AR tanpa melakukan apa pun. Sedangkan untuk false ketika menggunakan kolom boolean maka Anda benar, ini bukan pendekatan terbaik.
Mike Breen
Saya tidak dapat berbicara untuk kebiasaan pengkodean orang lain yang belum saya miliki masalah karena saya tidak menyebarkan getter / setter saya di sekitar file kelas. Juga, setiap editor teks modern harus membuatnya mudah untuk menavigasi ke suatu metode (shift-cmd-t di textmate).
Mike Breen
@paradoja - Saya mengambil kembali, saya sekarang melihat di mana ia rusak dengan menggunakan null juga. Tidak harus menggunakan null sebagai default, tetapi jika Anda benar-benar ingin mengubah nilai menjadi null di beberapa titik. Tangkapan yang bagus @paradoja, terima kasih.
Mike Breen
Saya menggunakan metode ini karena berfungsi baik dengan atribut yang dihasilkan secara dinamis.
Dale Campbell
0

Meskipun melakukan itu untuk menetapkan nilai default membingungkan dan canggung dalam kebanyakan kasus, Anda dapat menggunakannya :default_scopejuga. Lihat komentar squil di sini .

skalee
sumber
0

Metode after_initialize sudah usang, gunakan panggilan balik sebagai gantinya.

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

namun, menggunakan : default dalam migrasi Anda masih merupakan cara terbersih.

Greg
sumber
4
Dalam Rails 3: after_initializeMetode ini tidak ditinggalkan . Bahkan, panggilan balik gaya makro yang Anda berikan contoh IS sudah usang . Detail: guides.rubyonrails.org/...
Zabba
0

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.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

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

Kelvin
sumber
bukankah validasi hanya terjadi sebelum menyimpan? Bagaimana jika Anda ingin menampilkan default sebelum menyimpan?
nurettin
@nurettin Itu poin yang bagus dan saya bisa melihat mengapa Anda kadang-kadang menginginkannya, tetapi OP tidak menyebut ini sebagai persyaratan. Anda harus memutuskan sendiri apakah Anda ingin overhead pengaturan default pada setiap contoh, bahkan jika itu tidak disimpan. Alternatifnya adalah menyimpan benda boneka di sekitar agar newtindakan dapat digunakan kembali.
Kelvin
0

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

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

Itu masih tidak menginisialisasi nilai untuk catatan yang belum disimpan, tetapi itu sedikit lebih bersih daripada menggulung sendiri dengan initatau apa pun, dan Anda menuai manfaat lain dari aasm seperti cakupan untuk semua status Anda.

Permintaan yang buruk
sumber
0

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 ke my_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

etipton
sumber
0

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.

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

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

 validates :new_team_signature, presence: true

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!

kdweber89
sumber
0
# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
shilovk
sumber
-2

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

Viktor Trón
sumber
jika Anda menggunakan meta_where, default_scope mungkin tidak berfungsi untuk menetapkan default ke objek AR baru karena bug.
Viktor Trón
masalah meta_where ini sekarang telah diperbaiki [ metautonomous.lighthouseapp.com/projects/53011/tickets/…
Viktor Trón
3
JANGAN gunakan default_scope. Ini akan membuat semua pertanyaan Anda menambahkan kondisi ini ke bidang yang telah Anda atur. Hampir TIDAK PERNAH apa yang Anda inginkan.
brad
@ brad, lucu Anda sebutkan, saya sepenuhnya setuju, itu jahat :). lihat komentar saya di stackoverflow.com/questions/10680845/… .
Viktor Trón
-3

Dari api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html Gunakan before_validationmetode 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 inginkan

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

Terkejut bahwa itu belum disarankan di sini

jamesc
sumber
before_validation tidak akan mengatur default sampai objek siap untuk dipertahankan. Jika proses perlu membaca default sebelum bertahan maka nilai tidak akan siap.
mmell
Anda tidak pernah menetapkan nilai default selama pengecekan validasi. Bahkan tidak ada Retasan. Lakukan selama inisialisasi
Sachin