Xác định những thuộc tính nào đã được thay đổi trong cuộc gọi lại Rails after_save?


153

Tôi đang thiết lập một cuộc gọi lại after_save trong trình quan sát mô hình của mình để chỉ gửi thông báo nếu thuộc tính được xuất bản của mô hình được thay đổi từ false thành true. Vì phương pháp như thay đổi? chỉ hữu ích trước khi mô hình được lưu, cách mà tôi hiện tại (và không thành công) đang cố gắng thực hiện như sau:

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

Có ai có bất kỳ đề xuất nào về cách tốt nhất để xử lý việc này không, tốt nhất là sử dụng các cuộc gọi lại của người quan sát mô hình (để không làm ô nhiễm mã điều khiển của tôi)?

Câu trả lời:


182

Đường ray 5.1+

Sử dụng 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

Hoặc nếu bạn thích , saved_change_to_attribute?(:published).

Rails 3 đỉnh5.1

Cảnh báo

Cách tiếp cận này hoạt động thông qua Rails 5.1 (nhưng không được chấp nhận trong 5.1 và đã phá vỡ các thay đổi trong 5.2). Bạn có thể đọc về sự thay đổi trong yêu cầu kéo này .

Trong after_updatebộ lọc của bạn trên mô hình, bạn có thể sử dụng _changed?accessor. Ví dụ:

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

Nó chỉ hoạt động.


Quên những gì tôi đã nói ở trên - nó KHÔNG hoạt động trong Rails 2.0.5. Vì vậy, một bổ sung hữu ích cho Rails 3.
stephenr

4
Tôi nghĩ after_update không được dùng nữa? Dù sao, tôi đã thử điều này trong một hook after_save và nó dường như hoạt động tốt. (Các thay đổi () băm vẫn chưa được thiết lập lại trong after_save, rõ ràng.)
Tyler Rick

3
Tôi đã phải bao gồm dòng này trong tập tin model.rb. bao gồm ActiveModel :: Dirty
coderVishal 7/1/2016

13
Trong các phiên bản sau của Rails, bạn có thể thêm điều kiện vào after_updatecuộc gọi:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.

11
Sẽ có một số thay đổi API trong Rails 5.2. Bạn sẽ phải làm saved_change_to_published?hoặc saved_change_to_publishedtìm nạp thay đổi trong cuộc gọi lại
alopez02

183

Đối với những người muốn biết những thay đổi vừa thực hiện trong một after_savecuộc gọi lại:

Rails 5.1 trở lên

model.saved_changes

Đường ray <5.1

model.previous_changes

Xem thêm: http://api.rubyonrails.org/groupes/ActiveModel/Denty.html#method-i-preingly_changes


2
Điều này hoạt động hoàn hảo khi bạn không muốn sử dụng các cuộc gọi lại mô hình và cần một lưu hợp lệ để xảy ra trước khi thực hiện chức năng tiếp theo.
Dave Robertson

Đây là hoàn hảo! Cảm ơn vì đã đăng tải điều này.
jrhicks

Tuyệt vời! Cảm ơn vì điều đó.
Daniel Logan

4
Chỉ cần được rõ ràng: trong các thử nghiệm của tôi (Rails 4), nếu bạn đang sử dụng một after_savecallback, self.changed?trueself.attribute_name_changed?cũng là true, nhưng self.previous_changestrả về một băm rỗng.
sandre89

9
Điều này không được chấp nhận trong Rails 5.1 +. Thay vào đó, hãy sử dụng saved_changestrong các after_savecuộc gọi lại
rico_mac

71

Đối với bất kỳ ai nhìn thấy điều này sau này, vì hiện tại (tháng 8 năm 2017) đứng đầu google: Điều đáng nói, hành vi này sẽ được thay đổi trong Rails 5.2 và có cảnh báo không được chấp nhận kể từ Rails 5.1, vì ActiveModel :: Dirty đã thay đổi một chút .

Tôi thay đổi cái gì?

Nếu bạn đang sử dụng attribute_changed?phương thức trong after_*-callbacks, bạn sẽ thấy một cảnh báo như:

CẢNH BÁO KHAI THÁC: Hành vi attribute_changed?bên trong sau khi gọi lại sẽ thay đổi trong phiên bản tiếp theo của Rails. Giá trị trả về mới sẽ phản ánh hành vi gọi phương thức sau khi savetrả về (ví dụ ngược lại với giá trị trả về bây giờ). Để duy trì hành vi hiện tại, sử dụng saved_change_to_attribute?thay thế. (được gọi từ some_callback tại /PATH_TO/app/models/user.rb:15)

Vì nó đề cập, bạn có thể khắc phục điều này một cách dễ dàng bằng cách thay thế chức năng bằng saved_change_to_attribute?. Vì vậy, ví dụ, name_changed?trở thành saved_change_to_name?.

Tương tự, nếu bạn đang sử dụng attribute_changeđể lấy các giá trị trước-sau, điều này cũng thay đổi và ném như sau:

CẢNH BÁO KHAI THÁC: Hành vi attribute_changebên trong sau khi gọi lại sẽ thay đổi trong phiên bản tiếp theo của Rails. Giá trị trả về mới sẽ phản ánh hành vi gọi phương thức sau khi savetrả về (ví dụ ngược lại với giá trị trả về bây giờ). Để duy trì hành vi hiện tại, sử dụng saved_change_to_attributethay thế. (được gọi từ some_callback tại /PATH_TO/app/models/user.rb:20)

Một lần nữa, như nó đề cập, phương thức thay đổi tên saved_change_to_attributemà trả về ["old", "new"]. hoặc sử dụng saved_changes, trả về tất cả các thay đổi và những thay đổi này có thể được truy cập dưới dạng saved_changes['attribute'].


2
Lưu ý rằng phản hồi hữu ích này cũng bao gồm các cách giải quyết cho sự phản đối của các attribute_wasphương thức: sử dụng saved_change_to_attributethay thế.
Joe Atzberger

47

Trong trường hợp bạn có thể làm điều này before_savethay vì after_save, bạn sẽ có thể sử dụng điều này:

self.changed

nó trả về một mảng của tất cả các cột đã thay đổi trong bản ghi này.

bạn cũng có thể dùng:

self.changes

trong đó trả về một hàm băm của các cột đã thay đổi và trước và sau kết quả dưới dạng mảng


8
Ngoại trừ việc chúng không hoạt động trong một after_cuộc gọi lại, đó là những gì câu hỏi thực sự là về. Câu trả lời của @ jacek-głodek dưới đây là câu trả lời đúng.
Jazz

Đã cập nhật câu trả lời để làm rõ điều này chỉ áp dụng chobefore_save
mahemoff

1
Phiên bản Rails nào đây? Trong Rails 4, self.changedcó thể được sử dụng trong các after_savecuộc gọi lại.
sandre89

Hoạt động tuyệt vời! Cũng cần lưu ý, kết quả của self.changedlà một chuỗi các chuỗi! (Không phải biểu tượng!)["attr_name", "other_attr_name"]
LukeS

8

Câu trả lời "được chọn" không phù hợp với tôi. Tôi đang sử dụng rails 3.1 với CouchRest :: Model (dựa trên Active Model). Các _changed?phương thức không trả về true cho các thuộc tính đã thay đổi trong after_updatehook, chỉ trong before_updatehook. Tôi đã có thể làm cho nó hoạt động bằng cách sử dụng around_updatehook (mới?) :

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

1
Câu trả lời được chọn cũng không hiệu quả với tôi, nhưng điều này đã làm. Cảm ơn! Tôi đang sử dụng ActiveRecord 3.2.16.
Ben Lee

5

bạn có thể thêm một điều kiện để after_updatenhư vậy:

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

  ...
end

không cần thêm một điều kiện trong send_notificationchính phương thức đó.


-17

Bạn chỉ cần thêm một người truy cập xác định những gì bạn thay đổi

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

Đây là một thực tế rất xấu, thậm chí không xem xét nó.
Nuno Silva
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.