Apa cara yang tepat untuk mengganti metode penyetel di Ruby on Rails?

184

Saya menggunakan Ruby on Rails 3.2.2 dan saya ingin tahu apakah yang berikut ini adalah cara yang "tepat" / "benar" / "yakin" untuk mengganti metode penyetel untuk atribut kelas saya.

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self[:attribute_name] = value
end

Kode di atas tampaknya berfungsi seperti yang diharapkan. Namun, saya ingin tahu apakah, dengan menggunakan kode di atas, di masa depan saya akan memiliki masalah atau, setidaknya, masalah apa yang "harus saya harapkan" / "bisa terjadi" dengan Ruby on Rails . Jika itu bukan cara yang tepat untuk mengganti metode setter, apa cara yang benar?


Catatan : Jika saya menggunakan kode

attr_accessible :attribute_name

def attribute_name=(value)
  ... # Some custom operation.

  self.attribute_name = value
end

Saya mendapatkan kesalahan berikut:

SystemStackError (stack level too deep):
  actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
Backo
sumber
4
Saya suka terminologi yang diterapkan '"tepat" / "benar" / "yakin"'. Ketika Anda memberikannya 3 cara itu benar-benar memastikan tidak ada salah tafsir. Kerja bagus!
Jay
5
@ Jay - "Kehalusan italianisme"; -)
Backo
2
Untuk lebih jelasnya, "level stack terlalu dalam" mengacu pada fakta bahwa itu adalah panggilan rekursif ... itu panggilan itu sendiri.
Nippysaurus

Jawaban:

295

================================================== ========================= Pembaruan: 19 Juli 2017

Sekarang dokumentasi Rails juga menyarankan untuk digunakan superseperti ini:

class Model < ActiveRecord::Base

  def attribute_name=(value)
    # custom actions
    ###
    super(value)
  end

end

================================================== =========================

Jawaban Asli

Jika Anda ingin menimpa metode penyetel untuk kolom tabel saat mengakses melalui model, ini adalah cara untuk melakukannya.

class Model < ActiveRecord::Base
  attr_accessible :attribute_name

  def attribute_name=(value)
    # custom actions
    ###
    write_attribute(:attribute_name, value)
    # this is same as self[:attribute_name] = value
  end

end

Lihat Mengesampingkan accessors default dalam dokumentasi Rails.

Jadi, metode pertama Anda adalah cara yang benar untuk mengesampingkan setter kolom di Model Ruby on Rails. Accessor ini sudah disediakan oleh Rails untuk mengakses kolom tabel sebagai atribut dari model. Inilah yang kami sebut pemetaan ORM ActiveRecord.

Juga perlu diingat bahwa attr_accessibledi bagian atas model tidak ada hubungannya dengan pengakses. Ini memiliki functionlity yang sama sekali berbeda (lihat pertanyaan ini )

Tetapi di Ruby murni, jika Anda memiliki aksesor yang ditentukan untuk sebuah kelas dan ingin menimpa setter, Anda harus menggunakan variabel instan seperti ini:

class Person
  attr_accessor :name
end

class NewPerson < Person
  def name=(value)
    # do something
    @name = value
  end
end

Ini akan lebih mudah dipahami setelah Anda tahu apa yang attr_accessordilakukannya. Kode attr_accessor :nameini setara dengan dua metode ini (pengambil dan penyetel)

def name # getter
  @name
end

def name=(value) #  setter
  @name = value
end

Metode kedua Anda juga gagal karena akan menyebabkan loop tak terbatas saat Anda memanggil metode yang sama attribute_name=di dalam metode itu.

rubyprince
sumber
9
Untuk Rails 4 lewati saja attr_accessiblekarena sudah tidak ada lagi, dan itu akan berfungsi
zigomir
11
Kenapa tidak menelpon super?
Nathan Lilienthal
1
Saya terkesan bahwa karena pengakses dan penulis dibuat secara dinamis, supermungkin tidak berfungsi. Tapi, sepertinya bukan itu masalahnya. Saya baru saja memeriksanya dan berfungsi untuk saya. Juga, pertanyaan ini menanyakan hal yang sama
rubyprince
4
Ada gotcha besar dengan write_attribute. Konversi akan dilewati. Sadarilah bahwa write_attributeakan melewati konversi zona waktu dengan tanggal, yang hampir selalu tidak diinginkan.
Tim Scott
2
Super akan bekerja dengan baik namun ada beberapa alasan mengapa Anda mungkin tidak menginginkannya. Sebagai contoh di permata mongoid ada bug di mana Anda tidak bisa mendorong ke array jika Anda super metode pengambil. Itu bug karena ada cara mengelola array di memori. Selain itu @nama juga akan mengembalikan nilai yang ditetapkan daripada memanggil metode yang Anda timpa. Namun dalam solusi di atas keduanya akan bekerja dengan baik.
newdark-it
44

Gunakan superkata kunci:

def attribute_name=(value)
  super(value.some_custom_encode)
end

Sebaliknya, untuk menimpa pembaca:

def attribute_name
  super.some_custom_decode
end
Robert Kajic
sumber
1
Jawaban yang lebih baik daripada IMO yang diterima karena metode panggilan tetap terbatas untuk nama yang sama. Ini mempertahankan perilaku overridden yang diwarisi ke attribute_name =
Andrew Schwartz
Mengganti metode pengambil menjadi berbahaya di Rails 4.2 karena perubahan ini: github.com/rails/rails/commit/... Sebelumnya form helper akan memanggil nilai untypecast dari bidang tersebut dan tidak memanggil custom getter Anda. Sekarang mereka memanggil metode Anda, dan akan menghasilkan hasil yang membingungkan dalam formulir Anda tergantung pada bagaimana Anda mengganti nilainya.
Brendon Muir
16

Di rel 4

katakanlah Anda memiliki atribut umur di tabel Anda

def age=(dob)   
    now = Time.now.utc.to_date
    age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
    super(age) #must add this otherwise you need to add this thing and place the value which you want to save. 
  end

Catatan: Untuk pendatang baru di rails 4 Anda tidak perlu menentukan attr_accessible dalam model. Sebagai gantinya Anda harus membuat daftar putih atribut Anda di tingkat pengontrol menggunakan metode izin .

Taimoor Changaiz
sumber
3

Saya telah menemukan bahwa (setidaknya untuk koleksi hubungan ActiveRecord) pola berikut berfungsi:

has_many :specialties

def specialty_ids=(values)
  super values.uniq.first(3)
end

(Ini mengambil 3 entri non-duplikat pertama dalam array yang diteruskan.)

Robin Daugherty
sumber
0

Menggunakan attr_writeruntuk menimpa setter attr_writer: attribute_name

  def attribute_name=(value)
    # manipulate value
    # then send result to the default setter
    super(result)
  end
bananaappletw
sumber