Daði Hall Daði Hall - 5 months ago 18
Ruby Question

Rails search form throwing back error Couldn't find Product with 'id'=search

I'm building an rails app to learn ruby on rails.(I'm very unexperienced)
I'm trying to implement a search bar to the app.

When I hit search rails throws back this error

Couldn't find Product with 'id'=search


in this line

# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end


the server log shows this

Started GET "/products/search?utf8=%E2%9C%93&query=Desk" for ::1 at 2016-06-24 16:02:32 +0000
Processing by ProductsController#show as HTML
Parameters: {"utf8"=>"✓", "query"=>"Desk", "id"=>"search"}
Product Load (0.4ms) SELECT "products".* FROM "products" WHERE "products"."id" = $1 LIMIT 1 [["id", 0]]
Completed 404 Not Found in 4ms (ActiveRecord: 0.4ms)

ActiveRecord::RecordNotFound (Couldn't find Product with 'id'=search):
app/controllers/products_controller.rb:77:in `set_product'


I've been working on this search form for hours and checked out various tutorials and stackoverflow posts trying to figure this out.
I'm totally lost but the solution is probably pretty simple.
Please can anyone help me.

this is my search form in _navbar.html.erb

<div class="input-group">

<%= form_tag search_products_path, method: 'get', class: 'navbar-form' do %>
<%= text_field_tag :query, params[:query], placeholder: "Search", class: "form-control"%>
</div>
<span class="input-group-btn">
<%= submit_tag "Search", class: 'btn btn-default' %>
</span>

<% end %>


This is the products.rb model

class Product < ActiveRecord::Base


mount_uploader :image, ImageUploader

validates_presence_of :name, :price, :stock_quantity
validates_numericality_of :price, :stock_quantity

belongs_to :designer
belongs_to :category
belongs_to :page

def self.search(query)

where("name LIKE ? OR description LIKE ?", "%#{query}%", "%#{query}%")
end

end


This is the products_controller.rb

class ProductsController < ApplicationController
before_action :set_product, only: [:show, :edit, :update, :destroy]
before_filter :initialize_cart
before_action :authenticate_admin!, only: [ :new, :edit, :update, :create, :destroy ]
# GET /products
# GET /products.json
def index
@products = Product.all
end

def search

@products = Product.search(params[:query]).order("created_at DESC")
@categories = Category.joins(:products).where(:products => {:id => @products.map{|x| x.id }}).distinct

end

# GET /products/1
# GET /products/1.json
def show

end

# GET /products/new
def new
@product = Product.new
end

# GET /products/1/edit
def edit

end

# POST /products
# POST /products.json
def create
@product = Product.new(product_params)

respond_to do |format|
if @product.save
format.html { redirect_to @product, notice: 'Product was successfully created.' }
format.json { render :show, status: :created, location: @product }
else
format.html { render :new }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end

# PATCH/PUT /products/1
# PATCH/PUT /products/1.json
def update
respond_to do |format|
if @product.update(product_params)
format.html { redirect_to @product, notice: 'Product was successfully updated.' }
format.json { render :show, status: :ok, location: @product }
else
format.html { render :edit }
format.json { render json: @product.errors, status: :unprocessable_entity }
end
end
end

# DELETE /products/1
# DELETE /products/1.json
def destroy
@product.destroy
respond_to do |format|
format.html { redirect_to products_url, notice: 'Product was successfully destroyed.' }
format.json { head :no_content }
end
end

private
# Use callbacks to share common setup or constraints between actions.
def set_product
@product = Product.find(params[:id])
end

# Never trust parameters from the scary internet, only allow the white list through.
def product_params
params.require(:product).permit(:name, :description, :price, :image, :category_id, :stock_quantity, :designer_id, :query)
end
end


this is views/products/search.html.erb

<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Designer</th>
<th>Price</th>
<th>Stock</th>
<th>Image</th>

</tr>
</thead>

<tbody>
<% @products.each do |product| %>

<tr>
<td><%= link_to product.name, product %></td>
<td><%= product.description %></td>
<td><%= product.designer.designer_name %></td>

<td><%= number_to_currency product.price %></td>
<td><%= product.stock_quantity %></td>

<td><%= image_tag product.image.thumb %></td>


<% end %>
</tr>
</tbody>


and in routes.rb I have

Rails.application.routes.draw do
post 'products/search' => 'products#search', as: 'search_products'
get 'pages/index'

get 'pages/about'

get 'pages/location'

get 'pages/stockists'

devise_for :users
resources :categories
resources :categories
resources :designers
resources :category_names
resources :products


resource :cart, only: [:show] do
post "add", path: "add/:id", on: :member
get :checkout
end

resources :orders, only: [ :index, :show, :create, :update ] do
member do
get :new_payment
post :pay
end
end


root 'pages#index'

end

Answer

The routes are read top to bottom.

Having declared resources :products before /products/search causes a match.

Move this line

post 'products/search' => 'products#search', as: 'search_products'

before this line

   resources :products

Otherwise your app will try to update your product with id:'search'... which of course does not exist

Update

Also, you will have to remove :search from your filter

This line in your controller

before_action :set_product, only: [:show, :edit, :update, :destroy, :search]

Should become

before_action :set_product, only: [:show, :edit, :update, :destroy]

Because there is no :id in the route to associate a product

New update You are using a get form, yet you declared a post route.

Change your route to

get 'products/search' => 'products#search', as: 'search_products'