We've moved discussions to Discord

New Hotwire Scaffolds | View Methodology?

Jon Sullivan β€’
Greetings! I was reading through the other 'Hotwire scaffolds' thread (div tables vs. tr tables) but wanted to open up a new thread to ask about the idea and impetus behind the new view structures in the scaffolds.

With the new scaffolds, when a new object is scaffolded an index.html.erb page is created which subscribes to the class-wide Turbo stream (for creates and destroys) then renders out the collection of objects (as an index should):

(scaffold code, lib/templates/erb/scaffold/index.html.erb.tt):
<%%= turbo_stream_from :<%= plural_table_name %> %>
...
<%%= tag.div id: ("<%= plural_table_name %>" if first_page?), class: "bg-white rounded-md shadow p-6 space-y-8" do %>
  <%%= render partial: "<%= controller_file_path %>/index", collection: @<%= plural_table_name %>, as: :<%= singular_name %>, cached: true %>
...
But the partial that the index renders is actually a partial called.. index? πŸ€” So we have an index.html.erb 'page' and an _index.html.erb partial that's a per-record partial. That partial looks like:

(scaffold code, lib/templates/erb/scaffold/index_partial.html.erb.tt):
<div id="<%%= dom_id(<%= singular_name %>, :index) %>">
  <%%= turbo_stream_from <%= singular_name %> %>
  <%%= render <%= singular_name %> %>
  <%%= link_to "View", <%= singular_name %>, class: "btn btn-white" %>
</div>

So this little _index partial is essentially just a wrapper around rendering the actual individual _<singular_name>.html.erb record partial? Adding an ID'd div and a subscription to the individual record's ActionCable channel (for record updates)? Render wise it just inserts the base record partial:

(scaffold code, lib/templates/erb/scaffold/partial.html.erb.tt):
<div id="<%%= dom_id <%= singular_name %> %>">
<%- attributes.reject(&:password_digest?).each do |attribute| -%>
  <div class="mb-4">
    <p class="text-sm font-medium text-gray-500"><%= attribute.human_name %></p>
<%- if attribute.attachment? -%>
    <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %> if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
<%- elsif attribute.attachments? -%>
    <%% <%= singular_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
      <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
    <%% end %>
<%- else -%>
    <%%= <%= singular_name %>.<%= attribute.column_name %> %>
<%- end -%>
  </div>
<%- end -%>
</div>
Which looks more normal β€” a standard ID'd div with fields generated for each attribute of the model.

So question is, why is there an index -> _index(many) -> base partial(many) hierarchy here instead of just index -> base partial(many)? My spidey sense is telling me it's got something to do with the Turbo streams? Or maybe because the base partial is rendered by both the #index view and the #show view? And you only want the update turbo streams to go to the #index view? So you have to create a layer in the middle? To that end though, I don't typically have the #show view render the base partial πŸ€”. I have a sense that this setup (including how much declaration for specific broadcast channels) is a little non-standard for a "vanilla Hotwire install" so I figured I ought to ask. Can't quite figure it out!

Cheers!
Jon

PS: just wanted to clarify that last part β€” more just hoping that if we could simplify some of the view layering we could slim the model code down to simply the broadcasts method instead of explicit per-action broadcast declarations
Jon Sullivan β€’
Just following up for myself here, I think I figured out at least one major piece of the puzzle as to why we can't just use broadcasts alone. I actually wrote it up as an issue/question in the turbo-rails repo:

https://github.com/hotwired/turbo-rails/issues/312

But broadcasts sets up automatic turbo stream responses to channel names that don't make much sense for index views. This is probably why the JSP scaffold uses pretty specific setups for after_create and after_destroy but essentially the same as what broadcasts sets up for after_update. (My model is Page:) 
after_create_commit -> { broadcast_prepend_later_to :pages, partial: "pages/index", locals: { page: self } }
after_update_commit -> { broadcast_replace_later_to self }
after_destroy_commit -> { broadcast_remove_to :pages, target: self }
I think this helps inform the three-layer-partial setup too. Since additions and subtractions to the index have to be broadcast to a separate channel than updates to a specific record, each member of the index will be given a dom id of index_page_5 (for Page.find 5) and the index will listen to channel :pages. So there has to be a layer wrapping the base partial in a div (or etc.) that has the dom id of index_page_5, but we also don't want that extra wrapping layer inside the base partial because the base partial could be used in other places. So we need a third partial, one that's only ever really used in the index view (as to add the index_-prefixed wrapper) β€” may as well call it the _index.html.erb partial.

Huh. Well now that I write it out, yeah I guess that's probably the reasoning behind the structure.

That said, I'm very hopeful that my issue in the turbo-rails repo will be looked at and possibly considered. Having broadcasts changed to broadcast to the plural class name by default for after_create and after_destroy (instead of the record name) ought to simplify a lot of this. I believe that, in that case, the model could just have broadcasts rather than the three lines above, the index view could simply listen to :pages (in my example case), and we wouldn't need an _index partial wrapper, instead just rendering the base partial (and making sure the base partial has turbo_stream_from page in it). Feels like the "rails magic" way to me πŸ˜… TBD for more.

Notifications
You’re not receiving notifications from this thread.