You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

删除Rails嵌套资源记录时遭遇NoMethodError问题求助

Let's break down what's going wrong here — the core issue is a mismatch between your view's delete link parameters and your nested route structure, which is causing your controller to receive incorrect params, leading to that nil:NilClass error. Here's the step-by-step fix:

1. Recap Your Nested Route Structure

First, let's confirm how your nested routes work:

resources :vendors do
  resources :services
end

This generates a destroy route for services that follows this pattern:
DELETE /vendors/:vendor_id/services/:id
To hit this route correctly, the vendor_service_path helper requires two arguments: the parent vendor and the child service.

2. The Bug in Your View

Looking at your delete link in vendor/show.html.erb:

<%= link_to "Delete Service", vendor_service_path(current_vendor), method: :delete, ... %>

You're only passing current_vendor to the path helper — you're missing the service object! This means Rails generates an invalid URL (it’ll look like /vendors/123/services instead of /vendors/123/services/456). When you click this link:

  • params[:vendor_id] gets set to your vendor's ID (good)
  • params[:id] is nil (bad, because this should be the service's ID)
    So in your original destroy method, @service = Service.find_by_id(params[:id]) returns nil, hence the undefined method 'user' for nil:NilClass error.

3. Why Your "Fix" Broke Tests

When you swapped the params in your controller:

@vendor = Vendor.find_by_id(params[:id])
@service = Service.find_by_id(params[:vendor_id])

You’re essentially reversing what the params are supposed to represent. This "works" only because your broken link is sending the vendor's ID in params[:vendor_id] — but this is completely wrong for your route structure. Your RSpec tests are written to use the correct params (sending vendor ID in vendor_id and service ID in id), so when you swap them, the controller looks for a vendor with the service's ID and vice versa, which fails.

The Correct Fix

  1. Fix your delete link in the view to pass both the vendor and service:
<%= link_to "Delete Service", vendor_service_path(current_vendor, service), 
            method: :delete, 
            data: {confirm: 'Are you sure? This action cannot be reversed.'}, 
            class: 'btn btn-danger float-right'%>
  1. Revert your controller's destroy method back to the original version — it was logically correct, the problem was just the view passing wrong params:
def destroy
  @vendor = Vendor.find_by_id(params[:vendor_id])
  @service = Service.find_by_id(params[:id])
  if @service.user == current_user
    @service.destroy
    redirect_to vendor_path(@vendor), notice: "The service has been deleted."
  else
    return render text: 'Not Allowed', status: :forbidden
  end
end
  1. (Optional) To make your controller more robust, you could use find instead of find_by_id (which raises an error if the record isn't found) or add a before_action to fetch the vendor and service upfront.

Bonus: Why Your Original Tests Passed

Your RSpec tests likely bypassed the view entirely — they probably called the destroy method directly with the correct params (vendor ID and service ID), so they didn't catch the broken link in the view. To prevent this in the future, add system tests or request tests that actually click the delete link to verify end-to-end behavior.

内容的提问来源于stack exchange,提问作者swilliams

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:34:25