Pertanyaan validate_presence_of menyebabkan after_initialize dipanggil dengan diri yang aneh


Saya sudah memiliki model ini yang berfungsi dengan baik:

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  attr_accessible :weight, :measured_on

  def after_initialize
    self.measured_on ||= Date.today
  end
end

Saya menambahkannya pada baris ini

validates_uniqueness_of :measured_on, :scope => :user_id

dan itu mulai melemparkan kesalahan pada validasi. Bukan kesalahan validasi tetapi kesalahan Ruby:

>> w.valid?
ActiveRecord::MissingAttributeError: missing attribute: measured_on
    from /Users/pupeno/Projects/sano/app/models/weight.rb:8:in `after_initialize'

Saya telah menempatkan pernyataan debugger di after_initialize dan saya telah melihat sesuatu yang tidak terduga. Saat saya membuat bobot baru, ini berfungsi seperti yang diharapkan dan objek diri pada after_initialize adalah bobot yang diharapkan:

>> w = Weight.new
/Users/pupeno/Projects/sano/app/models/weight.rb:9
self.measured_on ||= Date.today
(rdb:1) p self
#<Weight id: nil, user_id: nil, weight: nil, measured_on: nil, created_at: nil, updated_at: nil>
(rdb:1) c
=> #<Weight id: nil, user_id: nil, weight: nil, measured_on: "2009-11-22", created_at: nil, updated_at: nil>

Kapan saya menjalankan w.valid? itu menjadi aneh. after_initialize dipanggil lagi, saya tidak yakin mengapa, dan objek diri tidak ada yang saya harapkan:

>> w.valid?
/Users/pupeno/Projects/sano/app/models/weight.rb:9
self.measured_on ||= Date.today
(rdb:1) p self
#<Weight id: 1>
(rdb:1) p self.inspect
"#<Weight id: 1>"
(rdb:1) p self.class
Weight(id: integer, user_id: integer, weight: float, measured_on: date, created_at: datetime, updated_at: datetime)
(rdb:1) p self.measured_on
ActiveRecord::MissingAttributeError Exception: missing attribute: measured_on
(rdb:1)

Sepertinya objek Weight lain dibuat tanpa atribut apa pun tetapi set id. Ada gagasan mengapa? Apakah ini bug atau perilaku yang diharapkan? Apakah saya melakukan kesalahan dengan menetapkan measure_on pada after_initialize?

Solusi saya saat ini, jika ada yang mengalami masalah yang sama, adalah

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  validates_uniqueness_of :measured_on, :scope => :user_id
  attr_accessible :weight, :measured_on

  def after_initialize
    if self.has_attribute? :measured_on
      self.measured_on ||= Date.today
    end
  end
end

tapi saya ingin memiliki solusi yang tepat.


5
2017-11-22 10:32


asal


Jawaban:


Saya pikir Anda memukul bug rel saya baru-baru ini berjuang dengan. Lihat Entri blog ini menghubungkan ke bug mercusuar terkait.

Pemahaman saya adalah bahwa apa yang terjadi adalah bahwa beberapa bagian sebelumnya dari kode rel melakukan "pilih id dari tablename" untuk melihat apakah suatu entri ada atau cocok. Objek kemudian cache bahwa satu-satunya bidang yang ada untuk tabel adalah "id". Kode Anda kemudian berjalan, dan nilai "atribut" kemudian salah, hanya mencerminkan bidang id.

Dari apa yang dapat saya temukan, ini hanya terjadi ketika jalur kode khusus ini dipukul, dan umumnya tidak membuat kesalahan, kecuali ketika melakukan validasi.

Yang saya lakukan untuk menyiasati itu adalah membungkus kode after_initialise dalam memulai / menyelamatkan blok ActiveRecord :: MissingAttributeError. Lalu saya menulis catatan besar di aplikasi dan di atas setiap item yang menunjukkan ketika versi rel baru dirilis, kami dapat menghapus ini.

Ya, saya yakin ada solusi yang lebih elegan.

def after_initialize
  begin
    # ... updates here
    # self.unique_reference = UUIDTools::UUID.random_create.to_s
  rescue ActiveRecord::MissingAttributeError
  end
end

Atau Anda juga bisa melakukan:

def after_initialize
  if self.has_attribute? :measured_on
    self.measured_on ||= Date.today
  end
end

6
2017-11-22 22:53



Masalah ini terkait dengan tiket kereta api # 3165. Baca di sana, jika Anda tertarik dengan bagaimana dan mengapa hal ini terjadi

Saya hanya menghabiskan setengah hari untuk ini, sebelum akhirnya saya menemukan tiket itu. Meskipun saya sedih bahwa sudah hampir setahun sejak ini dilaporkan, dan belum diperbaiki, inilah pekerjaan sederhana saya untuk skenario saya:

validates_uniqueness_of :email, :scope => :library_id

def after_initialize
  self.status ||= "Invited"
end

Ini akan menyebabkan 'MissingAttributeError' dilemparkan, jika ada catatan yang dikembalikan oleh validate_uniqueness_of query. Solusi sederhana saya adalah ini:

def after_initialize
  self.status ||= "Invited" if new_record?
end

Sementara orang lain memiliki masalah yang lebih kompleks, ini harus menyelesaikan kasus sederhana, sampai solusi yang sebenarnya dilakukan dalam rel.


2
2017-09-02 18:11



validate_uniqueness_of perlu memindai database Anda untuk entri. ActiveRecord memuat semua entri lainnya sebagai contoh dari model Anda. Tetapi untuk mengurangi penggunaan prosesor / memori itu tidak menambahkan metode untuk atribut, karena tidak perlu mereka untuk memeriksa keberadaan cepat. Namun itu masih instantiate semua catatan yang ada sebagai contoh dari model Anda sehingga after_initialize dipanggil.

Pekerjaan di sekitar adalah untuk memodifikasi @attributes hash secara langsung daripada mengandalkan accessor:

class Weight < ActiveRecord::Base
  belongs_to :user
  validates_presence_of :weight, :measured_on
  validates_uniqueness_of :measured_on, :scope => :user_id
  attr_accessible :weight, :measured_on

  def after_initialize
    @attributes["measured_on"] ||= Date.today
  end
end

0
2017-11-22 17:39