Flexible Model-to-User Broadcasts with CableReady
Tags: stimulusreflex, cableready, rails
Published: 2021-04-24 15:26:59

---
title: Flexible Model-to-User Broadcasts with CableReady
published: true
date: 2021-04-23 00:00:00 UTC
tags: stimulusreflex,cableready,rails
canonical_url: https://blog.minthesize.com/cable-ready-notify-users
---

 **Note:** I’m not too happy with the usage of the term _Notification_ here, but naming, as we all know, is hard. If you know of any better term, please let me know 🙌

### Problem: Scattered Notification Logic

Often in your reflexes, you end up wanting to notify other users than the `current` one hanging off a certain resource to be notified about changes, in one way or other. One user might add a comment or an emoji reaction that you want the others to see instantly, other times you might just want to dispatch a toast notification, what have you. You can easily make use of the implicit `cable_ready` channel that lives on every reflex:

```ruby
# app/reflexes/reaction_reflex.rb
class ReactionReflex < ApplicationReflex
  def toggle
    # ... do some work

    @reaction.users.each do |user|
      cable_ready[UserNotificationChannel].inner_html({
        selector: "#{dom_id(@reaction)}",
        html: "some rendered html depending on #{user}"
      }).broadcast_to(user)
    end
  end
end

```

Now I tend to defer this into a job so as not to inadvertently block the app server, and because usually it does not matter as much if the other users see the emoji a few milliseconds later:

```ruby
# app/reflexes/reaction_reflex.rb
class ReactionReflex < ApplicationReflex
  after_reflex do
    @reaction.users.each do |user|
      StreamReactionJob.perform_later(reaction: @reaction, user: notified_user)
    end
  end
  
  def toggle
    # ... do some work
  end
end

# app/jobs/stream_reaction_job.rb
class StreamReactionJob < ApplicationJob
  include CableReady::Broadcaster

  queue_as :default

  def perform(reaction:, user:)
    cable_ready[UserNotificationChannel].inner_html({
      selector: "#{dom_id(reaction)}",
      html: "some rendered html depending on #{user}"
    }).broadcast_to(user)
  end
end

```ruby

Now let’s suppose we have some branching to do depending on the type of the reaction:

```ruby
# app/jobs/stream_reaction_job.rb
class StreamReactionJob < ApplicationJob
  include CableReady::Broadcaster

  queue_as :default

  def perform(reaction:, user:)
    if reaction.thumbs_up?
      cable_ready[UserNotificationChannel].play_sound(
        src: "fanfare.mp3"
      )
    end

    cable_ready[UserNotificationChannel].inner_html({
      selector: "#{dom_id(reaction)}",
      html: "some rendered html depending on #{user}"
    }).broadcast_to(user)
  end
end

```

In this (contrived) example we play a fanfare through CableReady’s `play_sound` operation if the reaction was positive. However, now this begins to smell of **Feature Envy** (or in violation of _tell, don’t ask_): We are asking the reactions questions about its internal state.

Moreover, would we want to expand this to multiple reflexes or resources, we would quickly assemble a hotchpotch of custom CableReady code all over your application. What’s actually at the center of this problem is the individual **resource** , which has the knowledge (and thus the responsibility) to discern what should be broadcast to the user.

### Solution: A UserNotifiable Concern

Here’s my take on a solution to this problem. First, we create a generalized `NotifyUsersJob` that we can reuse:

```ruby
class NotifyUsersJob < ApplicationJob
  include CableReady::Broadcaster
  queue_as :default

  def perform(changes:)
    changes.each do |user, operations|
      cable_ready[UserNotificationChannel].apply!(operations).broadcast_to(user)
    end
  end
end

```

The `apply!` method is part of CableReady’s new operation serializer functionality aptly called `cable_car` which we need in order to be able to pass them to the job (because you cannot serialize `Procs`). Okay, where do those `changes` come from, and what do they contain? For that, let’s look at a `UserNotifiable` model concern I’ve crafted:

```ruby
module UserNotifiable
  extend ActiveSupport::Concern
  include CableReady::Broadcaster

  included do
    after_commit :notify_users
  end

  def notify_users
    NotifyUsersJob.perform_later(changes: cable_ready_changes)
  end

  def cable_ready_changes
    users.map do |user|
      operations = yield user
      [user, operations]
    end
  end
end

```

There’s one duck type, or **contract** the model it’s being included into has to fulfill, and that’s that it has a `users` accessor, be it through an association, a delegation to an association, or any other kind of entity such as a Redis set. Now in a method called `cable_ready_changes` we can iterate over those users and assemble CableReady operations in the including subclasses (we’ll get to that in a moment).

We return an array of `[user, operations]`, which is picked up by the job above. The models it’s being included can now implement it:

```ruby
class Reaction < ApplicationRecord
  include UserNotifiable

  def cable_ready_changes
    super do |user|
      if thumbs_up?
        cable_car.play_sound(
          src: "fanfare.mp3"
        )
      end

      cable_car.inner_html({
        selector: "#{dom_id(self)}",
        html: "some rendered html depending on #{user}"
      }).dispatch
    end
  end
end

```

Here’s where `cable_car` comes into play: It basically assembles operations like a regular `cable_ready` call would do (the syntax is the same), but when being sent the `dispatch` message, returns a hash that can be sent to a job, or rendered as JSON, etc. By calling `apply!` on a `cable_ready` channel as we did above, those operations are then broadcast to the respective `user`.

**Bonus** : Now that the model itself is in charge of gathering the necessary operations, you could also act according to [what has changed](https://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes) in the respective commit. For example, suppose you have a `Rating` and would like to send out different notifications whether it has increased by one, two, or three stars: Use `previous_changes`!

### Conclusion

What have we gained? Three things basically:

1. We have a flexible, reusable module (`UserNotifiable`) we can mix into any model we want to broadcast changes of to the pertaining users. Just make sure it has a `users` accessor.
2. The model is the object that knows best what has changed about it, so it is the proper thing to ask “what are your cable ready changes”?
3. We have successfully isolated all common functionality in a single concern and a single job, leaving the developer only with the need to `include` it and specify a hash of `cable_ready_changes`.

Would you like to learn **more**? Look [here.](https://www.stimulusreflexpatterns.com/)