Cách xác định xem bản ghi mới được tạo hay cập nhật trong after_save


95

#New_record? chức năng xác định xem một bản ghi đã được lưu. Nhưng nó luôn luôn là sai trong after_savehook. Có cách nào để xác định xem bản ghi là bản ghi mới được tạo hay bản ghi cũ từ bản cập nhật?

Tôi hy vọng không sử dụng một lệnh gọi lại khác chẳng hạn như before_createđể đặt cờ trong mô hình hoặc yêu cầu một truy vấn khác vào db.

Bất kỳ lời khuyên được đánh giá cao.

Chỉnh sửa: Cần xác định nó trong after_savehook và đối với trường hợp sử dụng cụ thể của tôi, không có updated_athoặc updated_ondấu thời gian


1
hmm có thể vượt qua một tham số trong before_save? chỉ nghĩ ra
Chuyến đi

Câu trả lời:


167

Tôi đang tìm cách sử dụng cái này để after_savegọi lại.

Một giải pháp đơn giản hơn là sử dụng id_changed?(vì nó sẽ không thay đổi trên update) hoặc ngay cả created_at_changed?khi các cột dấu thời gian hiện diện.

Cập nhật: Như @mitsy đã chỉ ra, nếu cần kiểm tra này bên ngoài lệnh gọi lại thì hãy sử dụng id_previously_changed?. Xem tài liệu .



6
Tốt nhất bạn nên phân biệt bằng after_update và after_create. Các lệnh gọi lại có thể chia sẻ một phương thức chung có một đối số để cho biết đó là một tạo hay cập nhật.
matthuhiggins

2
Điều này có thể đã thay đổi. Ít nhất trong Rails 4, lệnh gọi lại after_save chạy sau lệnh gọi lại after_create hoặc after_update (xem hướng dẫn.rubyonrails.org/active_record_callbacks.html ).
Đánh dấu

3
Kiểm tra không có trường nào trong số này hoạt động bên ngoài after_save.
fatuhoku

3
id_changed?sẽ sai sau khi bản ghi được lưu (ít nhất là bên ngoài hook). Trong trường hợp đó, bạn có thể sử dụngid_previously_changed?
mltsy

30

Không có phép thuật đường ray nào ở đây mà tôi biết, bạn sẽ phải tự làm. Bạn có thể làm sạch điều này bằng cách sử dụng thuộc tính ảo ...

Trong lớp mô hình của bạn:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end

26

Tuy nhiên, một tùy chọn, đối với những người làm có một updated_atdấu thời gian:

if created_at == updated_at
  # it's a newly created record
end

Không phải là một ý tưởng tồi, nhưng có vẻ như điều này có thể phản tác dụng trong một số tình huống (không nhất thiết phải có chống đạn).
Ash Blue vào

Tùy thuộc vào thời điểm di chuyển được chạy, các bản ghi create_at và updated_at có thể bị tắt. Ngoài ra, bạn luôn có khả năng ai đó cập nhật bản ghi ngay sau khi lưu lần đầu, điều này có thể khiến thời gian không đồng bộ. Nó không phải là một ý tưởng tồi, chỉ cảm thấy như một triển khai chống đạn có thể được thêm vào.
Ash Blue

Ngoài ra, chúng có thể bằng nhau rất lâu sau khi bản ghi ban đầu được tạo. Nếu một bản ghi không được cập nhật trong nhiều tháng, nó sẽ trông giống như nó vừa được tạo .
bschaeffer

1
@bschaeffer Xin lỗi, câu hỏi của tôi là "là nó có thể cho created_atđể bằng updated_attrong một after_savecallback bất cứ lúc nào khác so với khi nó lần đầu tiên được tạo ra?"
colllin

1
@colllin: Khi tạo bản ghi, created_atupdated_atsẽ bằng nhau trong một lần after_savegọi lại. Trong tất cả các tình huống khác, chúng sẽ không bằng nhau trong cuộc after_savegọi lại.
bschaeffer

22

Có một lệnh after_creategọi lại chỉ được gọi nếu bản ghi là một bản ghi mới, sau khi nó được lưu . Cũng có một after_updatecuộc gọi lại để sử dụng nếu đây là một bản ghi hiện có đã được thay đổi và lưu. Lệnh after_savegọi lại được gọi trong cả hai trường hợp, sau một trong hai after_createhoặc after_updateđược gọi.

Sử dụng after_createnếu bạn cần điều gì đó xảy ra một lần sau khi một bản ghi mới đã được lưu.

Thông tin thêm tại đây: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html


1
Này, cảm ơn vì câu trả lời, điều này đã giúp tôi rất nhiều. Chúc mừng anh bạn, +10 :)
Adam McArthur

1
Điều này có thể không đúng trên thực tế. Nếu bạn có các hiệp hội trong sáng tạo của bạn, sau đó được gọi là after_create TRƯỚC các hiệp hội được tạo ra, vì vậy nếu bạn cần phải chắc chắn tất cả mọi thứ được tạo ra, sau đó bạn cần phải sử dụng after_save
Niels Kristian

18

Vì đối tượng đã được lưu, bạn cần xem xét các thay đổi trước đó. ID chỉ nên thay đổi sau khi tạo.

# true if this is a new record
@object.previous_changes[:id].any?

Ngoài ra còn có một biến thể hiện @new_record_before_save. Bạn có thể truy cập bằng cách làm như sau:

# true if this is a new record
@object.instance_variable_get(:@new_record_before_save)

Cả hai đều khá xấu, nhưng chúng sẽ cho phép bạn biết liệu đối tượng có được tạo mới hay không. Hy vọng rằng sẽ giúp!


Tôi hiện đang gặp khó khăn trong một after_savecuộc gọi lại bằng cách sử dụng Rails 4 và cả hai đều không hoạt động để xác định bản ghi mới này.
MCB

Tôi muốn nói rằng điều đó @object.previous_changes[:id].any?khá đơn giản và thanh lịch. Nó hoạt động cho tôi sau khi bản ghi được cập nhật (tôi không gọi nó từ after_save).
thekingoftruth

1
@object.id_previously_changed?là bớt xấu xí hơn một chút.
aNoble

@MCB trong after_saveRails 4 mà bạn muốn xem changes[:id]thay vì previous_changes[:id]. Tuy nhiên, điều này đang thay đổi trong Rails5.1 (xem thảo luận trong github.com/rails/rails/pull/25337 )
gmcnaughton

previous_changes.key?(:id)để hiểu rõ hơn.
Sebastian Palma

2

Rails 5.1+ cách:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true

1

Đối với Rails 4 (được kiểm tra trên 4.2.11.1), kết quả của changesprevious_changescác phương thức là các hàm băm trống {}khi tạo đối tượng bên trong after_save. Vì vậy, attribute_changed?các phương pháp nhưid_changed? sẽ không hoạt động như mong đợi.

Nhưng bạn có thể tận dụng kiến ​​thức này và - biết rằng phải có ít nhất 1 thuộc tính changeskhi cập nhật - hãy kiểm tra xem changescó trống không. Khi bạn xác nhận rằng nó trống, bạn phải trong quá trình tạo đối tượng:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end

0

Tôi thích cụ thể ngay cả khi tôi biết rằng điều đó :idkhông nên thay đổi trong bình thường, nhưng

(byebug) id_change.first.nil?
true

Thay vào đó, việc phát hiện ra lỗi bất ngờ rất kỳ lạ luôn rẻ hơn.

Tương tự như vậy nếu tôi mong đợi truegắn cờ từ đối số không đáng tin cậy

def foo?(flag)
  flag == true
end

Điều này tiết kiệm rất nhiều giờ để không phải ngồi trên các lỗi kỳ lạ.

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.