simoncocking

simoncocking

Amplified.PubSub - a protocol-based PubSub abstraction for Phoenix and LiveView

Hi folks!

We’ve just published a module which we’ve built and have been using at Amplified for a few years: A protocol-based PubSub abstraction for Phoenix LiveView.

Why did we build this?

Amplified is a multi-user collaborative patent research platform that depends heavily on real-time updates both from background jobs and user interactions. We lean heavily on Phoenix.PubSub and before building this module, we repeated the same code smells all over the place:

  • manually building PubSub channel strings like user:123 or tag:asdf, sometimes getting things wrong and causing hard-to-find bugs
  • writing the same broadcast/2 helper in context modules
  • writing the same handle_info/2 callbacks in LiveView modules, or forgetting them and wondering why views weren’t updating

Now, we just do this instead. Schema modules opt-in with use Amplified.PubSub:

defmodule MyApp.Blog.Post do
  use Ecto.Schema
  use Amplified.PubSub

  schema "posts" do
    field :title, :string
    field :body, :string
    timestamps()
  end
end

and contexts broadcast CRUD events on schema changes.

defmodule MyApp.Blog do
  alias Amplified.PubSub

  def create_post(attrs) do
    %Post{}
    |> Post.changeset(attrs)
    |> Repo.insert()
    |> PubSub.broadcast(:created)
  end

We hook into LiveView’s handle_info/2 lifecycle callback chain in an on_mount hook:

attach_hook(:pubsub, :handle_info, &PubSub.handle_info/2)

and in either a LiveView or a mount hook, subscribe to events:

# Contrived example
123 |> Accounts.get_account!() |> PubSub.subscribe()

If you find yourself writing the same handle_info/2 callbacks across multiple LiveViews, you can put them in the schema module instead and that will run every time that thing changes:

defmodule MyApp.Accounts.User do
  use Amplified.PubSub do
    def handle_info(
          %User{id: id} = user,
          :updated,
          %{assigns: %{current_scope: %{user: %{id: id}} = scope}} = socket
        ) do
      {:cont, assign(socket, current_scope: %{scope | user: user})}
    end
  end

  schema "users" do
    ...
  end
end

The ergonomics are wonderful - real-time updates just happen with near zero code and way fewer bugs.

Find it on Hex and

https://github.com/amplifiedai/amplified_pubsub

Where Next?

Popular in Announcing Top

martinthenth
Hello everybody :wave: Recently, some of my colleagues talked about database ids and uuids and their problems, and I remembered the pain...
New
ostinelli
Let’s write a database! Well not really, but I think it’s a little sad that there doesn’t seem to be a simple in-memory distributed KV da...
New
Crowdhailer
The latest release of Ace (0.10.0) includes serving content over HTTP/2. I have started writing a webserver to teach my self more about...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 10167 134
New
mplatts
With HEEX released we decided to start a components library using Tailwind CSS - check it out here: Petal Components. We also have a boi...
New
maltoe
Hello! Came here to announce ChromicPDF, a pet project PDF generator I’ve been working on for the past few months. Why another PDF gener...
New
Crowdhailer
Raxx is an alternative to Plug and is inspired by projects such as Rack(Ruby) and Ring(Clojure). 1.0-rc.1 is now available. To use it re...
New
michalmuskala
Hello everybody. I have just released Jason - a new JSON library. You might be wondering, why do we need a new library? The primary foc...
New
archan937
It is a well-know topic within the Elixir community: “To mock or not to mock? :)” Every alchemist probably has his / her own opinion con...
New
markmark206
simple_feature_flags is a tiny package that lets you turn features on or off based on which environment (e.g. localhost, staging, product...
New

Other popular topics Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30877 112
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New

We're in Beta

About us Mission Statement