在Rails中使用Turbo Frames更新关联模型的最佳实践
解决方案:符合Rails MVC的Turbo交互实现
核心思路
围绕单一职责原则,让InvoicesController负责处理发票关联客户的更新,通过Turbo Streams精准更新目标Turbo Frame,既不破坏表单未保存内容,也不跨控制器修改模型。
1. 重构Turbo Frame结构
在Invoice编辑视图中,给嵌入的Turbo Frame指定唯一ID,方便后续精准替换:
<%= turbo_frame_tag "customer_picker", src: customers_path(invoice_id: @invoice.id) %>
2. 客户列表的选择按钮(Customers#index)
每个客户的「选择」按钮,提交到Invoices#update接口并指定Turbo Stream响应:
<% @customers.each do |customer| %> <%= form_with model: @invoice, url: invoice_path(@invoice), method: :patch, data: { turbo_stream: true } do |f| %> <%= f.hidden_field :customer_id, value: customer.id %> <%= f.submit "选择", class: "btn btn-primary" %> <% end %> <% end %>
同时在CustomersController中接收invoice_id参数,用于初始化视图中的发票实例:
def index @customers = Customer.all @invoice = Invoice.find(params[:invoice_id]) if params[:invoice_id].present? end
3. Invoices#update的Turbo Stream响应
创建app/views/invoices/update.turbo_stream.erb,只更新目标Turbo Frame为选中客户的详情视图:
<% if @invoice.update(invoice_params) %> <%= turbo_stream.replace "customer_picker" do %> <%= render "customers/show", customer: @invoice.customer %> <% end %> <% else %> # 处理验证错误,比如重渲染客户列表并提示错误 <%= turbo_stream.replace "customer_picker" do %> <%= render "customers/index", customers: @customers, invoice: @invoice, error: "关联客户失败,请重试" %> <% end %> <% end %>
这里的invoice_params要确保允许customer_id参数:
def invoice_params params.require(:invoice).permit(:customer_id, # 其他发票字段...) end
4. 客户详情视图(Customers#show)
在customers/show.html.erb中,可添加「更换客户」按钮,让Turbo Frame重新加载客户列表:
<h3><%= @customer.name %></h3> # 客户其他信息展示... <%= button_to "更换客户", customers_path(invoice_id: params[:invoice_id]), method: :get, data: { turbo_frame: "customer_picker" } %>
为什么符合MVC原则?
- 所有对Invoice模型的修改都由
InvoicesController处理,严格遵循单一职责 CustomersController仅负责客户列表和详情的展示,不触及Invoice模型- Turbo Streams只更新目标Turbo Frame,完全保留Invoice表单中未保存的输入内容
替代方案:动态切换Turbo Frame的src
如果不想用Turbo Streams渲染视图,也可以直接更新Turbo Frame的src属性,让它自动加载客户详情:
# update.turbo_stream.erb <% if @invoice.update(invoice_params) %> <%= turbo_stream.update "customer_picker", src: customer_path(@invoice.customer, invoice_id: @invoice.id) %> <% end %>
内容的提问来源于stack exchange,提问作者Kloot
相关产品推荐
相关产品推荐

