ruby-on-railsrubyroutesfriendly-id

Rails not differentiating between two resources nested in the same namespaces


I have two types of products that are nested under the same categories. The routes I have setup are

resources :categories, path: '/', only: [:show] do
  resources :subcategories, path: '/', only: [:show] do
    resources :amazon_products, path: '/', only: [:show]
    resources :other_products, path: '/', only: [:show]
  end
end

which I was previously accessing using this link

<%= link_to "View Product Page", [product.collection, product.category, product.subcategory, product], class: 'product__link' %>

which that resulted in something like this url after friendly_id finished with it

/cleansers/face-wash-and-cleansers/blemish-remedy-acne-treatment-gelee-cleanser

The issue is that the link only resolves for amazon_products and I'm not sure how to make it differentiate between the two. I think the problem is in the way I am referencing the path since when I enter rails routes in the console, I can see the two different paths there like so

category_subcategory_amazon_product
GET    :category_id/:subcategory_id/:id(.:format)
amazon_products#show

category_subcategory_other_product
GET    /:collection_id/:category_id/:subcategory_id/:id(.:format)
other_products#show

I tried specifically referencing the other product path using the link

category_subcategory_other_product_path(product.category, product.subcategory, product)

but it is giving me an ActiveRecord::RecordNotFound since its still looking in the wrong controller

app/controllers/amazon_products_controller.rb:5:in `show'

How do I tell rails to differentiate between these two resources?


Solution

  • When you boil it down, Rails routes are relatively simple patterns that match URIs and dispatch requests to the proper controller/method.

    If you had specified your nested routes in their basic form:

    resources :categories, only: [:show] do
      resources :subcategories, only: [:show] do
        resources :amazon_products, only: [:show]
        resources :other_products, only: [:show]
      end
    end
    

    The resulting URI patterns for the product level would look like:

    /categories/<id>/subcategories/<id>/amazon_products/<id>
    /categories/<id>/subcategories/<id>/other_products/<id>
    

    Admittedly verbose, but obviously distinct, patterns.

    The issue is that you are using path: '/' on the resources to remove everything unique about the resource routes. So, your product-level route patterns are:

    category_subcategory_amazon_product: /<id>/<id>/<id>
    category_subcategory_other_product: /<id>/<id>/<id>
    

    Since the two patterns are identical, Rails falls back to the time honoured practice of matching the first one defined. You can demonstrate that for yourself by swapping :other_products so it is first; its controller will then be the one always invoked.

    NB: It doesn't matter that you are using friendly-id -- that just changes what the IDs look like to the user, not the basic route pattern.

    The solution is to simply reintroduce some uniqueness, at least at the product level.

    resources :categories, path: '/', only: [:show] do
      resources :subcategories, path: '/', only: [:show] do
        resources :amazon_products, path: 'amazon', only: [:show]
        resources :other_products, path: 'other', only: [:show]
      end
    end
    

    The result will be route patterns that Rails can actually distinguish:

    category_subcategory_amazon_product: /<id>/<id>/amazon/<id>
    category_subcategory_other_product: /<id>/<id>/other/<id>