Partials used for turbo streaming must be free of global references.

mirmayne

Daveyon Mayne 😻

Posted on July 8, 2023

Partials used for turbo streaming must be free of global references.

A flower in the sand

It's never a good idea for partials to reference a global variable, such variable would be current_user. DHH mentioned this already and I'll show you how to gain access to the current user's information. I do it by storing information of the current_user in a meta:


<head>
  <meta name="current_user" content="<%= current_user.json_info %>">
</head>

Enter fullscreen mode Exit fullscreen mode

json_info would contain the information that you need:

# user.rb

[..]

def json_info
  {
    id: self.id, 
    name: <NAME>,
    # Maybe full url to their avatar?
    avatar_url: Rails.application.routes.url_helpers.rails_blob_path(self.avatar, only_path: true)
  }.to_json
end

Enter fullscreen mode Exit fullscreen mode

Basic stuff. With that, our current_user object lives inside the meta of the document head.

This partial will be a Stimulus controller and our partial is called _message.html.erb that lives inside the shared folder:

<article 
  data-controller="message" 
  id="<%= dom_id(message) %>" 
  data-author-id="<%= message.user.id %>">
   <%= message.body %>
</article>

Enter fullscreen mode Exit fullscreen mode

Notice I have here data-author-id, very important, and I've connected a Stimulus controller. Every time this partial get's rendered in the DOM, the connect function gets triggered:

// message_controller.js

import { Controller } from '@hotwired/stimulus'

export default class extends Controller {

  connect() {
    const currentUserBlob = this._getMetaValue('current_user');
    const id = JSON.parse(currentUserBlob).id

    const authorId = this.element.getAttribute('data-author-id')

    // Do something when authorId matches/does not match id.
    // This could be hiding/removing an element or something else.
  }

  _getMetaValue = (name) => {
    const element = document.head.querySelector(`meta[name="${name}"]`)
    return element?.getAttribute("content")
  }

}

Enter fullscreen mode Exit fullscreen mode

Our message model message.rb would look something like this:


[..]

belongs_to :user
belongs_to :message_thread
has_rich_text :body

after_create_commit do
  # We cannot pass current user
  broadcast_append_to(self.message_thread, target: self.message_thread, partial: "shared/message", locals: { message: self })
end

Enter fullscreen mode Exit fullscreen mode

message.rb is just an high-level example showing only the message object that was passed in and nothing else. Depending on what you're doing with "current_user", you'd implement that logic in the Stimulus controller's connect function.

The entire page may look like this:


<%= turbo_stream_from @thread %>

<section id="<%= dom_id(@thread) %>">
  <% @messages.each do |message|
    <%= render "shared/message", message: message %>
  <% end %>
</section>

Enter fullscreen mode Exit fullscreen mode

As a rule of thumb, partials should be free from global references/objects but where a partial is used with ApplicationRenderer, you must connect it with a Stimulus controller to gain access to the current_user.

🎉 Self-promotion 🎉

I'm building Farm Swop, a platform for farmers where farmers can connect with nearby farmers to share/exchange support, machinery etc. It's in active development. Feel free to join to see my progress while I BuildInPublic.

Photo by Jill Heyer on Unsplash

💖 💪 🙅 🚩
mirmayne
Daveyon Mayne 😻

Posted on July 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related