Replying to myself, since I found a solution which works for my case and may help others.
There is a similar question but the answers there are too specific to the particular case on rely and css or javascript “tricks”, while I prefer solving this on the server side.
While my question was specific to the particular case of different markup for empty and non-empty lists, I thought it represented a whole class of cases: how to update a whole list of elements, when one of them changes, instead of updating individual elements.
For instance, I have a case where each elements has attributes (a link to a diff) related to one particular element of the list (the “latest”), and those attributes are calculated in the markup, not persisted in the DB (it doesn’t make sense to update all DB rows each time a row becomes the “latest”).
So when we have a new “latest”, each element of the list is updated.
Another case could be a list limited in number of elements (first X elements). When an element is added, another one needs to be removed. That’s a view concern, not a model one, I think.
The solution I found looks like this:
I used broadcast_render_later_to
in my Element
model, broadcasting to the Container
model the elements
belong to, and using a specified turbo stream partial: containers/_refresh_elements_list.turbo_stream.erb
.
So something like:
class Element
belongs_to :container
after_create_commit :update_elements_list
after_update_commit :update_elements_list
def update_elements_list
broadcast_render_later_to container, partial: "containers/refresh_elements_list", locals: { container: container }
end
end
The containers/_refresh_elements_list.turbo_stream.erb
. partial looks like this:
<%= turbo_stream.replace "elements" do %>
<%= render partial: "elements/list",
formats: :html,
locals: { container: container } %>
<% end %>
The containers/show.html.erb
view, like this:
[...]
<%= turbo_stream_from @container %>
<%= render partial: 'elements/list, locals: { container: @container } %>
[...]
And finally, the elements/_list.html.erb
view:
<div id="elements">
<% elements = container.elements %>
<% if elements.empty? %>
<div>
<div>No elements yet</div>
</div>
<% else %>
<table>
<thead>
<tr>....</tr>
</thead>
<tbody>
<%= render @elements %>
</tbody>
</table>
<% end %>
</div>
I didn’t try this exact code, it’s a simplification of the code in my application, so there may be typos, but the idea is there.
I feel a bit like abusing turbo stream so I would welcome alternatives, maybe more idiomatic (if those already exists).