จะตรวจจับการเปลี่ยนแปลงแอตทริบิวต์จากโมเดลได้อย่างไร?


86

ฉันต้องการสร้างฟังก์ชันเรียกกลับในรางที่ดำเนินการหลังจากบันทึกโมเดลแล้ว

ฉันมีโมเดลนี้การอ้างสิทธิ์ที่มีแอตทริบิวต์ 'สถานะ' ซึ่งจะเปลี่ยนแปลงไปตามสถานะของการอ้างสิทธิ์ค่าที่เป็นไปได้อยู่ระหว่างดำเนินการรับรองอนุมัติถูกปฏิเสธ

ฐานข้อมูลมี 'สถานะ' โดยมีค่าเริ่มต้นเป็น 'รอดำเนินการ'

ฉันต้องการทำงานบางอย่างหลังจากสร้างโมเดลในครั้งแรกหรืออัปเดตจากสถานะหนึ่งไปยังอีกสถานะหนึ่งขึ้นอยู่กับว่าจะเปลี่ยนจากสถานะใด

ความคิดของฉันคือการมีฟังก์ชันในโมเดล:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

คำถามของฉันคือฉันจะตรวจสอบค่าก่อนหน้านี้ก่อนการเปลี่ยนแปลงภายในโมเดลได้อย่างไร

คำตอบ:


173

คุณควรดูที่โมดูลActiveModel :: Dirty : คุณควรจะสามารถดำเนินการต่อไปนี้ในรูปแบบการอ้างสิทธิ์ของคุณ:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

โอ้! ความสุขของ Rails!


6
สิ่งนี้จะไม่ทำงานหลังจากบันทึกโมเดลซึ่งเป็นสิ่งที่เขาขอ
Tom Rossi

4
@ TomRossi การdirtyโทรใช้งานได้after_save(ทั้งใน Rails 2.3 และ 3.x) ฉันได้ใช้มันหลายครั้ง
Harish Shetty

11
@TomRossi แฟล็กสกปรกจะถูกรีเซ็ตหลังจากคอมมิตดังนั้นจึงไม่สามารถใช้ได้ในการafter_commitเรียกกลับที่แนะนำใน Rails 3.x after_saveแน่นอนพวกเขาจะทำงานใน
Harish Shetty

ฉันไม่มีความคิดเห็น! ฉันคิดว่าพวกเขาถูกรีเซ็ตเมื่อได้รับการช่วยเหลือ!
Tom Rossi

5
@TomRossi ฉันเริ่มต้นด้วยสมมติฐานเดียวกันเมื่อหลายปีก่อน เมื่อฉันพยายามตรวจสอบแฟล็กสกปรกใน after_save มันใช้งานได้ ในสาระสำคัญafter_saveคือการเรียกกลับรัฐระหว่างและafter DML before_commitคุณสามารถยุติการทำธุรกรรมทั้งหมดได้after_saveโดยทิ้งข้อยกเว้น หากคุณต้องการทำบางสิ่งหลังจากบันทึกโดยไม่ส่งผลกระทบต่อการทำงานปัจจุบันให้ใช้after_commit:-)
Harish Shetty

38

คุณสามารถใช้สิ่งนี้

self.changed

มันส่งคืนอาร์เรย์ของคอลัมน์ทั้งหมดที่เปลี่ยนแปลงในเรกคอร์ดนี้

คุณยังสามารถใช้

self.changes

ซึ่งจะส่งคืนแฮชของคอลัมน์ที่เปลี่ยนแปลงและก่อนและหลังผลลัพธ์เป็นอาร์เรย์


7
เพียงบันทึกเล็ก ๆ น้อย ๆ จะบอกว่ามันไม่จำเป็นที่จะใช้self.เหล่านี้ - คุณเพียงแค่สามารถพูดและchanged changes
user664833

@ user664833 โดยเฉพาะอย่างยิ่งคุณสามารถละเว้นselfเมื่ออยู่ในรูปแบบของตัวเอง แต่คุณสามารถเรียกเหล่านี้บนวัตถุใด ๆ ด้วยและobject.changed object.changes:)
Joshua Pinter

4

ฉันขอแนะนำให้คุณดูหนึ่งในปลั๊กอินของเครื่องที่มีอยู่:

อย่างใดอย่างหนึ่งจะให้คุณตั้งค่าสถานะและการเปลี่ยนระหว่างสถานะ วิธีที่ง่ายและมีประโยชน์มากในการจัดการความต้องการของคุณ


ฉันกำลังลอง Rubyist-aasm สมมติว่าฉันมีคลาส Claim <ActiveRecord :: Base รวม AASM aasm_column: status aasm_initial_state: pending aasm_state: pending,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end และฟิลด์สถานะของฉันในฐานข้อมูลของฉันมีค่าดีฟอลต์ ของ "รอดำเนินการ" ถ้าฉันจะทำการ Claim.create โดยไม่กรอกข้อมูลในช่องสถานะ (เพื่อให้มันทำงาน 'รอดำเนินการ') AASM จะเรียกใช้เมธอด 'enter_pending' หรือไม่
David C

2

สำหรับ Rails 5.1+ คุณควรใช้ Active record attribute method: saved_change_to_attribute?

save_change_to_attribute? (attr_name, ** ตัวเลือก) "

แอตทริบิวต์นี้เปลี่ยนแปลงเมื่อเราบันทึกครั้งล่าสุดหรือไม่ วิธีนี้สามารถเรียกใช้saved_change_to_name?แทน saved_change_to_attribute?("name"). มีพฤติกรรมคล้ายกับ attribute_changed?. วิธีนี้มีประโยชน์ในการเรียกกลับเพื่อตรวจสอบว่าการเรียกเพื่อบันทึกเปลี่ยนแอตทริบิวต์บางอย่างหรือไม่

ตัวเลือก

from เมื่อผ่านไปวิธีนี้จะคืนค่าเป็นเท็จเว้นแต่ค่าเดิมจะเท่ากับตัวเลือกที่กำหนด

to เมื่อผ่านไปเมธอดนี้จะส่งคืนเท็จเว้นแต่ว่าค่านั้นจะเปลี่ยนเป็นค่าที่กำหนด

ดังนั้นโมเดลของคุณจะมีลักษณะเช่นนี้หากคุณต้องการเรียกเมธอดบางอย่างตามการเปลี่ยนแปลงของค่าแอตทริบิวต์:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

และหากคุณไม่ต้องการตรวจสอบการเปลี่ยนแปลงค่าในการโทรกลับคุณสามารถดำเนินการดังต่อไปนี้ ::

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end

0

ฉันเคยเห็นคำถามเพิ่มขึ้นในหลาย ๆ ที่ดังนั้นฉันจึงเขียนทับทิมเล็ก ๆ สำหรับมันเพื่อทำให้รหัสดีขึ้นเล็กน้อย (และหลีกเลี่ยงคำสั่ง if / else เป็นล้านทุกที่): https://github.com/ronna-s / on_change . ฉันหวังว่าจะช่วยได้


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.