I have students and checklists for models, and each student has many checklists. I've been trying to change the default scaffolds to work with shallow routing. I fixed by controller by setting the checklist and student depending on the action, and that seems to work. But I'm having trouble with the _form
partial in that I don't know what to change - and while I can find stuff on forms for nested routes, there is no such help for shallow routing.
<%= form_with(model: [@student, checklist]) do |form| %>
<% if checklist.errors.any? %>
<div style="color: red">
<h2><%= pluralize(checklist.errors.count, "error") %> prohibited this checklist from being saved:</h2>
<ul>
<% checklist.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :date, style: "display: block" %>
<%= form.date_field :date %>
</div>
<div>
<%= form.label :setting, style: "display: block" %>
<%= form.text_field :setting %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
CodePudding user response:
You really don't have to do much to get forms to work with nested routes.
When you're using form_with
it passes the arguments to the polymorphic route helpers which will compact the array when trying to find a corresponding route:
# method | path
form_with(model: [Foo.find(1), Bar.new]) # POST | foo_bars_path(foo_id: 1)
form_with(model: [nil, Bar.new]) # POST | bars_path
form_with(model: [Foo.find(1), Bar.find(2)]) -> # PATCH | foo_bars_path(foo_id: 1, id: 2)
form_with(model: [nil, Bar.find(1)]) -> # PATCH | bar_path(1)
The last element of the array is treated as the actual object that gets wrapped by the form builder. When dealing with shallow nesting you just have to make sure that you don't pass the parent record when updating the record.
You can do this by either just using a single "local" in your partial:
<%= form_with(model: model) %>
# new.html.erb
<%= render partial: 'form', model: [@student, @checklist] %>
# edit.html.erb
<%= render partial: 'form', model: @checklist %>
Or you can use the local_assigns
hash to access a local without a NoMethodError being raised if no local is passed:
<%= form_with(model: [local_assigns[:student], checklist]) %>
# new.html.erb
<%= render partial: 'form', student: @student, checklist: @checklist %>
# edit.html.erb
<%= render partial: 'form', checklist: @checklist %>
This works since locals are not actual local variables - instead the passed locals are stored in a hash and Rails provides accessors dynamically via metaprogramming.