hotwired / turbo-rails

Use Turbo in your Ruby on Rails app
https://turbo.hotwired.dev
MIT License
2.11k stars 326 forks source link

How to properly handle multiple broadcasts from a model on after_destroy_commit? #363

Open spaquet opened 2 years ago

spaquet commented 2 years ago

Here is the use case: When deleting an object the list must be refreshed and the counter updated. As this object can be manipulated by other processes that are not calling the controller. This forces Turbo Stream calls to be executed from with the model.

Now, when the object is destroyed its reference will be lost... making the following code impossible to run:

after_destroy_commit do
    broadcast_update_later_to [self.room_id, :questions], target: "question_counter", html: Question.approved_questions_for_room(self.room_id).count
    broadcast_remove_to [self.room_id, :questions], target: self
end

The error is then ERROR: Discarded Turbo::Streams::ActionBroadcastJob due to a ActiveJob::DeserializationError.

(the same sequence called from the controller does work as expected with no error)

I do understand the process behind the scene, but wouldn't it be better to properly "serialize" the active job so that it can be run once the object does not exist so we can keep the async performance benefits.

This question could also be extended to the broadcast_remove_to as it does not exist a broadcast_remove_later_to (same, I understand the why, but I also think that developers have solutions in their arsenal to circumvent the potential issues - for instance Realm database is fully async and you quickly learn how to properly delete data and reflect these changes on the UI)

daya commented 2 years ago

While the developers may come back with a better solution I think the following hackish pseudo-code will work

before_destroy do
  Thread.current_thread[:broadcast_room_id] = self.room_id
  # OR store the whole record in current thread
  Thread.current_thread[:selfish] = self
end

then

after_destroy_commit do
    broadcast_update_later_to [Thread.current_thread[:broadcast_room_id], :questions], target: "question_counter", html: Question.approved_questions_for_room(Thread.current_thread[:selfish].room_id]).count
    broadcast_remove_to [Thread.current_thread[:selfish].room_id, :questions], target: Thread.current_thread[:selfish]
end