Menentukan atribut apa yang diubah dalam Rails after_save panggilan balik?

153

Saya menyiapkan callback after_save di pengamat model saya untuk mengirim pemberitahuan hanya jika atribut model yang diterbitkan diubah dari false menjadi true. Karena metode seperti diubah? hanya berguna sebelum model disimpan, cara saya saat ini (dan tidak berhasil) mencoba melakukannya adalah sebagai berikut:

def before_save(blog)
  @og_published = blog.published?
end

def after_save(blog)
  if @og_published == false and blog.published? == true
    Notification.send(...)
  end
end

Apakah ada yang punya saran tentang cara terbaik untuk menangani ini, lebih disukai menggunakan model pengamat panggilan balik (agar tidak mencemari kode pengontrol saya)?

modulaaron
sumber

Jawaban:

182

Rails 5.1+

Gunakan saved_change_to_published?:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(…) if (saved_change_to_published? && self.published == true)
  end

end

Atau jika Anda lebih suka saved_change_to_attribute?(:published),.

Rel 3–5.1

Peringatan

Pendekatan ini bekerja melalui Rails 5.1 (tetapi sudah usang dalam 5.1 dan telah melanggar perubahan dalam 5.2). Anda dapat membaca tentang perubahan permintaan tarikan ini .

Dalam after_updatefilter Anda pada model, Anda dapat menggunakan _changed?accessor. Jadi misalnya:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(...) if (self.published_changed? && self.published == true)
  end

end

Itu hanya bekerja.

Radek Paviensky
sumber
Lupakan apa yang saya katakan di atas - itu TIDAK bekerja di Rails 2.0.5. Jadi tambahan yang berguna untuk Rails 3.
stephenr
4
Saya pikir after_update sudah usang sekarang? Lagi pula, saya mencoba ini di hook after_save dan itu tampaknya bekerja dengan baik. (Perubahan () hash masih belum diatur ulang di after_save, rupanya.)
Tyler Rick
3
Saya harus memasukkan baris ini dalam file model.rb. termasuk ActiveModel :: Dirty
coderVishal
13
Di versi Rails yang lebih baru, Anda dapat menambahkan kondisi ke after_updatepanggilan:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.
11
Akan ada beberapa perubahan API di Rails 5.2. Anda harus melakukan saved_change_to_published?atau saved_change_to_publishedmengambil perubahan selama panggilan balik
alopez02
183

Bagi mereka yang ingin mengetahui perubahan yang baru saja dilakukan dalam after_savepanggilan balik:

Rails 5.1 dan lebih besar

model.saved_changes

Rel <5.1

model.previous_changes

Lihat juga: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes

Jacek Głodek
sumber
2
Ini berfungsi dengan baik ketika Anda tidak ingin menggunakan model callback dan perlu penyimpanan yang valid untuk terjadi sebelum melakukan fungsionalitas lebih lanjut.
Dave Robertson
Ini sempurna! Terima kasih telah memposting ini.
jrhicks
Luar biasa! Terima kasih untuk ini.
Daniel Logan
4
Hanya untuk menjadi jelas: dalam pengujian saya (Rails 4), jika Anda menggunakan after_savepanggilan balik, self.changed?adalahtrue dan self.attribute_name_changed?juga true, tetapi self.previous_changesmengembalikan hash kosong.
sandre89
9
Ini tidak digunakan lagi dalam Rails 5.1 +. Gunakan sebaliknya saved_changesdalam after_savepanggilan balik
rico_mac
71

Bagi siapa pun yang melihat ini nanti, seperti saat ini (Agustus 2017) teratas di google: Perlu disebutkan, bahwa perilaku ini akan diubah dalam Rails 5.2 , dan memiliki peringatan penghentian pada Rails 5.1, seperti ActiveModel :: Dirty berubah sedikit .

Apa yang saya ubah?

Jika Anda menggunakan attribute_changed? metode di after_*-callbacks, Anda akan melihat peringatan seperti:

PERINGATAN DEPRECASI: Perilaku attribute_changed?dalam setelah panggilan balik akan berubah di versi Rails berikutnya. Nilai pengembalian baru akan mencerminkan perilaku memanggil metode setelahsave kembali (misalnya kebalikan dari apa yang dikembalikan sekarang). Untuk mempertahankan perilaku saat ini, gunakan saved_change_to_attribute?saja. (dipanggil dari some_callback di /PATH_TO/app/models/user.rb:15)

Seperti yang disebutkan, Anda dapat memperbaikinya dengan mudah dengan mengganti fungsinya saved_change_to_attribute? . Jadi misalnya, name_changed?menjadi saved_change_to_name?.

Demikian juga, jika Anda menggunakan attribute_change untuk mendapatkan nilai sebelum-sesudah, ini juga berubah dan melempar yang berikut:

PERINGATAN DEPRECASI: Perilaku attribute_changedalam setelah panggilan balik akan berubah di versi Rails berikutnya. Nilai pengembalian baru akan mencerminkan perilaku memanggil metode setelahsave kembali (misalnya kebalikan dari apa yang dikembalikan sekarang). Untuk mempertahankan perilaku saat ini, gunakan saved_change_to_attributesaja. (dipanggil dari some_callback di /PATH_TO/app/models/user.rb:20)

Sekali lagi, seperti yang disebutkan, metode ini mengubah nama saved_change_to_attributeyang kembali ["old", "new"]. atau gunakan saved_changes, yang mengembalikan semua perubahan, dan ini dapat diakses sebagai saved_changes['attribute'].

Frederik Spang
sumber
2
Perhatikan respons yang bermanfaat ini juga mencakup penyelesaian masalah untuk penghentian attribute_wasmetode: gunakan saved_change_to_attributesaja.
Joe Atzberger
47

Jika Anda bisa melakukan ini, before_savebukan after_save, Anda akan dapat menggunakan ini:

self.changed

itu mengembalikan array dari semua kolom yang diubah dalam catatan ini.

Anda juga bisa menggunakan:

self.changes

yang mengembalikan hash kolom yang berubah dan sebelum dan sesudah hasil sebagai array

Zeacuss
sumber
8
Kecuali bahwa ini tidak berfungsi dalam after_panggilan balik, yang merupakan pertanyaan sebenarnya. @ jacek-głodek jawaban di bawah ini adalah yang benar.
Jazz
Diperbarui jawaban untuk memperjelas ini hanya berlaku untukbefore_save
mahemoff
1
Versi Rails manakah ini? Dalam Rails 4, self.changeddapat digunakan dalam after_savepanggilan balik.
sandre89
Bagus sekali! Perlu dicatat, hasilnya self.changedadalah array string! (Bukan simbol!)["attr_name", "other_attr_name"]
LukeS
8

Jawaban "terpilih" tidak berhasil untuk saya. Saya menggunakan rel 3.1 dengan CouchRest :: Model (berdasarkan Model Aktif). The _changed?metode tidak kembali berlaku untuk atribut berubah di after_updatehook, hanya di before_updatekait. Saya bisa membuatnya bekerja menggunakan around_updatekait (baru?) :

class SomeModel < ActiveRecord::Base
  around_update :send_notification_after_change

  def send_notification_after_change
    should_send_it = self.published_changed? && self.published == true

    yield

    Notification.send(...) if should_send_it
  end

end
Jeff Gran
sumber
1
Jawaban yang dipilih juga tidak berhasil untuk saya, tetapi ini berhasil. Terima kasih! Saya menggunakan ActiveRecord 3.2.16.
Ben Lee
5

Anda dapat menambahkan kondisi after_updateseperti ini:

class SomeModel < ActiveRecord::Base
  after_update :send_notification, if: :published_changed?

  ...
end

tidak perlu menambahkan kondisi dalam send_notificationmetode itu sendiri.

gema
sumber
-17

Anda hanya menambahkan accessor yang menentukan apa yang Anda ubah

class Post < AR::Base
  attr_reader :what_changed

  before_filter :what_changed?

  def what_changed?
    @what_changed = changes || []
  end

  after_filter :action_on_changes

  def action_on_changes
    @what_changed.each do |change|
      p change
    end
  end
end
shingara
sumber
Ini adalah praktik yang sangat buruk, bahkan tidak mempertimbangkannya.
Nuno Silva