Reputation: 3
I have Categories (Parents) within which are listed Products (Children).
I want to be able to create a new Product directly from the navbar, anywhere in the app and then, during the creation, assign it to a Category.
However, I get the present error:
NoMethodError in Products#new
Showing /Users/istvanlazar/Mobily/app/views/products/new.html.erb where line #9 raised:
undefined method `products_path' for #<#<Class:0x00007febaa5aec98>:0x00007febae0f9e38>
Did you mean? product_show_path
## product_show_path is a custom controller that has nothing to do with this one,
enabling show and clean redirection from newest_products index bypassing categories nesting.
Extracted source (around line #9):
9 <%= form_for [@product] do |f| %>
10 <div class="form-styling">
11 <div>
12 <%= f.label :name %>
My App works as such:
Models
class Category < ApplicationRecord
has_many :products, inverse_of: :category
accepts_nested_attributes_for :products
validates :name, presence: true
end
class Product < ApplicationRecord
belongs_to :user, optional: true
belongs_to :category, inverse_of: :products
validates :category, presence: true
end
Routes
get 'products/new', to: 'products#new', as: 'new_product'
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :edit]
# resources :products, only: [:index, :show, :new, :edit]
end
Controllers
class ProductsController < ApplicationController
before_action :set_category, except: :new
def index
@products = Product.all
@products = policy_scope(@category.products).order(created_at: :desc)
end
def show
@product = Product.find(params[:id])
end
def new
@product = Product.new
@product.user = current_user
end
def create
@product = Product.new(product_params)
@product.user = current_user
if @product.save!
redirect_to category_product_path(@category, @product), notice: "Product has been successfully added to our database"
else
render :new
end
end
private
def set_category
@category = Category.find(params[:category_id])
end
def product_params
params.require(:product).permit(:name, :price, :description, :category_id, :user, :id)
end
end
class CategoriesController < ApplicationController
def index
@categories = Category.all
end
def show
@category = Category.find(params[:id])
end
def new
# Non-existant created in seeds.rb
end
def create
# idem
end
def edit
# idem
end
def update
# idem
end
def destroy
# idem
end
private
def category_params
params.require(:category).permit(:name, :id)
end
end
Views
# In shared/navbar.html.erb:
<nav>
<ul>
<li>Some Link</li>
<li>Another Link</li>
<li><%= link_to "Create", new_product_path %></li>
</ul>
</nav>
# In products/new.html.erb:
<%= form_for [@product] do |f| %>
<div class="form-styling">
<div>
<%= f.label :name %>
<%= f.text_field :name, required: true, placeholder: "Enter product name" %>
<%= f.label :price %>
<%= f.number_field :price, required: true %>
</div>
<div>
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div>
<%= f.label :category %>
<%= f.collection_select :category_id, Category.order(:name), :id, :name, {prompt: 'Select a Category'}, required: true %>
</div>
<div>
<%= f.submit %>
</div>
</div>
<% end %>
Do you have any idea of where it went wrong? Or is it impossible to Create a Child before assigning it to a Parent..?
Thanks in advance.
Best, Ist
Upvotes: 0
Views: 679
Reputation: 457
Edit: I misread the intention, ignore this. Go with Jeremy Weather's answer.
Or is it impossible to Create a Child before assigning it to a Parent..?
With the way you have your relationships and validations set up: yes, it is. A Product requires a Category, through the validates :category, presence: true
. And if you're on Rails 5+, the association will already be required by default (meaning that validates
is actually redundant). Meaning if you try to create a Product without a Category, it will fail, and the Product will not be saved to the database.
With that constraint in place, here's what I recommend trying:
/products/new
route
get 'products/new', to: 'products#new', as: 'new_product'
:create
and :new
, to the :only
array to the resources :products
nested under resources :categories
views/products/new.html.erb
to views/categories/products/new.html.erb
(creating a new categories
directory in views
if necessary).form_for
so that the form is POSTing to the nested :create route:
form_for [@category, @product] do |f|
/categories/[insert some Category ID here]/products/new
, filling out some data, and see if that works.Your routes.rb should look like this:
resources :categories, only: [:index, :show, :new, :edit] do
resources :products, only: [:index, :show, :new, :create, :edit]
end
Upvotes: 0
Reputation: 2554
You haven't defined any route to handle your new product form's POST. You've defined the new_product
path, but this arrangement is breaking Rails' conventions and you're not providing a work-around.
You could define another custom route, e.g. post 'products', to: 'products#create', as: 'create_new_product'
and then set that in your form like form_for @product, url: create_new_product_path do |f|
.
However, you should consider changing the structure so that product routes are not nested under categories. Think twice before breaking conventions this way.
Upvotes: 1