6
votes

I have nested resources as below:

resources :categories do
  resources :products
end

According to the Rails Guides,

You can also use url_for with a set of objects, and Rails will automatically determine which route you want:

<%= link_to 'Ad details', url_for([@magazine, @ad]) %>

In this case, Rails will see that @magazine is a Magazine and @ad is an Ad and will therefore use the magazine_ad_path helper. In helpers like link_to, you can specify just the object in place of the full url_for call:

<%= link_to 'Ad details', [@magazine, @ad] %>

For other actions, you just need to insert the action name as the first element of the array:

<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

In my case, I have the following code which was fully functional:

<% @products.each do |product| %>
  <tr>
    <td><%= product.name %></td>
    <td><%= link_to 'Show', category_product_path(product, category_id: product.category_id) %></td>
    <td><%= link_to 'Edit', edit_category_product_path(product, category_id: product.category_id) %></td>
    <td><%= link_to 'Destroy', category_product_path(product, category_id: product.category_id), method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %> 

Obviously it is a little too verbose and I wanted to shorten it using the trick mentioned above from rails guides.

But if I changed the Show and Edit link as follows:

<% @products.each do |product| %>
  <tr>
    <td><%= product.name %></td>
    <td><%= link_to 'Show', [product, product.category_id] %></td>
    <td><%= link_to 'Edit', [:edit, product, product.category_id] %></td>
    <td><%= link_to 'Destroy', category_product_path(product, category_id: product.category_id), method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

Neither of them works any more, and the pages complains the same thing:

NoMethodError in Products#index
Showing /root/Projects/foo/app/views/products/index.html.erb where line #16 raised:

undefined method `persisted?' for 3:Fixnum

What did I miss?

2
Does it work if you do [product, product.category] (for the 'Show' url)?Paul Richter

2 Answers

5
votes

The way Rails is 'automagically' knowing which path to use is by inspecting the objects you pass for their classes, and then looking for a controller whose name matches. So you need to make sure that what you are passing to the link_to helper is the actual model object, and not something like category_id which is just a fixnum and therefore has no associated controller.

<% @products.each do |product| %>
  <tr>
    <td><%= product.name %></td>
    <td><%= link_to 'Show', [product.category, product] %></td>
    <td><%= link_to 'Edit', [:edit, product.category, product] %></td>
    <td><%= link_to 'Destroy', [product.category, product], method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
4
votes

I'm guessing the offending line is one of these:

<td><%= link_to 'Show', [product, product.category_id] %></td>
<td><%= link_to 'Edit', [:edit, product, product.category_id] %></td>

The product.category_id is a Fixnum and the routing can't know that a random number is supposed to map to category_id.

Use the previous URLs you had, they're more readable.