ruby-on-railsruby-on-rails-5nested-resources

Multiple rails 5 nested routes from one controller


I have nested routes of categories/subcategories/products and my controller and view files are setup accordingly, but I now have some products that don't have subcategories. How, if possible, can I have some products part of a category, while others are part of a sub-category? I've seen many other posts on this, but none seem to address exactly what I'm looking to do.

current nested routes

resources :categories do
  resources :subcategories do
    resources :products
  end
end

additional needed nested routes

resources :categories do
  resources :products
end

My current Products controller Create method

def create
    @category = Category.friendly.find(params[:category_id])
    @subcategory = Subcategory.friendly.find(params[:subcategory_id])
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to category_subcategory_product_path(@category, @subcategory, @product), notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: category_subcategory_product_path(@category, @subcategory, @product) }
      else
        ...
      end
    end
  end

models

class Category < ApplicationRecord
  has_many :subcategories
  has_many :products, through: :subcategories
end

class Subcategory < ApplicationRecord
  has_many :products
  belongs_to :category
end

class Product < ApplicationRecord
  belongs_to :subcategory
end

Solution

  • What I would do here is remove the Subcategory model and let Categories belong to themselves. This would let you create a nesting hierarchy of categories (which would allow you to get even more granular, if you wanted to).

    class Category
      has_many :categories
      belongs_to :category
      has_many :products
    end
    
    class Product
      belongs_to :category
    end
    

    Any "top level" categories would have a category_id of nil, and any subcategories would belong_to an existing one.

    top_level = Category.create(slug: "top_level", category_id: nil)
    subcategory = Category.create(slug: "subcategory_1", category_id: top_level.id)
    Product.create(category: top_level)
    Product.create(category: subcategory)
    

    In your routes, you can make something like this:

    get "/:category/products", to: "products#index"
    get "/:category/:subcategory/products", to: "products#index"