New Hotwire Scaffolds | View Methodology?
Greetings! I was reading through the other 'Hotwire scaffolds' thread (
With the new scaffolds, when a new object is scaffolded an
(scaffold code,
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..
(scaffold code,
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
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
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 declarationsJust 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
https://github.com/hotwired/turbo-rails/issues/312
But
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
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
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.