8

I am using Rails 4 and have the following error.

Routing Error
No route matches [POST] "/logs/1/meals/13/edit

I’m passing form_for the model object using :meal and the edit page is rendering correctly. However, Rails does not seem to be checking whether or not the meal object has already been saved, so it keeps trying to send the form to the #create action and tries make a POST request instead of sending the form to the update action and making a PUT request when I hit submit.

How do I get the form_for to recognize that I am trying to update an existing object and that PUT is needed instead of POST? Everything else is working and I’ve run all of my migrations. I’m pretty new to Rails, and I’ve spent almost all day trying to figure this out on my own. Please help!

And just to note, when I tried to pass in the model object as @meal.log instead of :meal, Rails was no longer able to recognize :calorie_estimate or :meal_description. Passing the model object as @meal.log left me with a no method error.

meals/edit.html.erb

<h3> EDIT MEAL </h3>
<%= form_for(:meal) do |f| %>
<div id="meal-form">
    <%= f.text_field :calorie_estimate, class: 'meal-form-fields', :placeholder => "Calorie Estimate" %>
    <%= f.text_field :meal_description, class: 'meal-form-fields', :placeholder => "Food Description" %>
  <div class="submit-form" style="width: 75px; height: 15px;">
    <%= f.submit 'UPDATE', :class => 'submit-form-text' %>
  </div>
</div>
<% end %>

meals_controller.rb

class MealsController < ApplicationController
  include MealsHelper

  def create
    @meal = Meal.new(meal_params)
    @meal.log_id = params[:log_id]
    @meal.save

    redirect_to log_path(@meal.log)
  end

  def edit
    @meal = Meal.find(params[:id])
  end

  def update
    @meal = Meal.find(params[:id])
    @meal.update(meal_params)
    redirect_to log_path(@log)
  end

  def meal_params
    params.require(:meal).permit(:calorie_estimate, :meal_description)
  end

end

possible routes:

Prefix Verb   URI Pattern                            Controller#Action
         root GET    /                                      logs#index
    log_meals GET    /logs/:log_id/meals(.:format)          meals#index
              POST   /logs/:log_id/meals(.:format)          meals#create
 new_log_meal GET    /logs/:log_id/meals/new(.:format)      meals#new
edit_log_meal GET    /logs/:log_id/meals/:id/edit(.:format) meals#edit
     log_meal GET    /logs/:log_id/meals/:id(.:format)      meals#show
              PATCH  /logs/:log_id/meals/:id(.:format)      meals#update
              PUT    /logs/:log_id/meals/:id(.:format)      meals#update
              DELETE /logs/:log_id/meals/:id(.:format)      meals#destroy
         logs GET    /logs(.:format)                        logs#index
              POST   /logs(.:format)                        logs#create
      new_log GET    /logs/new(.:format)                    logs#new
     edit_log GET    /logs/:id/edit(.:format)               logs#edit
          log GET    /logs/:id(.:format)                    logs#show
              PATCH  /logs/:id(.:format)                    logs#update
              PUT    /logs/:id(.:format)                    logs#update
              DELETE /logs/:id(.:format)                    logs#destroy

routes.rb

Rails.application.routes.draw do

  root to: 'logs#index'

  resources :logs do
    resources :meals
  end
end

schema.rb

ActiveRecord::Schema.define(version: 20160128205351) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "logs", force: :cascade do |t|
    t.string   "entry_date"
    t.integer  "calorie_goal"
    t.string   "notes"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "meals", force: :cascade do |t|
    t.integer  "calorie_estimate"
    t.string   "meal_description"
    t.integer  "log_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

end
Code Goose
  • 87
  • 1
  • 6

3 Answers3

5

The issue is that you're using nested resources, hence you're confused about which @objects to pass to your form_for.

#app/views/meals/edit.html.erb
<%= form_for [@log, @meal] do |f| %>

As you have it presently, passing :meal is ambiguous - Rails cannot discern the route / method to send its submission to, as it doesn't have that data available.

If you wanted to update an object, you'll have to pass the appropriate data to the form, including the object's id:

<%= form_for :meal, url: { controller: "meals", action: "update", id: "5" }, method: :put do |f| %>

Such as Rails is object orientated, you'll be best passing the actual object to your form_for:

<%= form_for @meal ...

--

The issue you have is that you have a nested resource:

resources :logs do
  resources :meals #-> url.com/logs/:log_id/meals/:id
end

This means you need to pass both the Log and Meal values to your form:

#app/controllers/meals_controller.rb
class MealsController < ApplicationController
  def edit
     @log  = Log.find params[:log_id]
     @meal = Meal.find params[:id]
  end

  def update
     @log  = Log.find params[:log_id]
     @meal = Meal.find params[:id]   
     @meal.update meal_params     
  end
end

#app/views/meals/edit.html.erb
<%= form_for [@log, @meal] do |f| %>
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • Yes! Thanks so much for the clear explanantion. I made the changes above to meals_controller.rb and edited meals/edit.html.erb so that I pass both the log and meal objects to the form like so ` <%= form_for [@log, @meal] do | f | %> `, but this left me with a No Method Error and undefined method `meal_path'. What worked for me was making the changes you suggested to meals_controller.rb and editing meals/edit.html.erb to render the form partial I use to create new meals. Thanks again for your help! – Code Goose Feb 06 '16 at 17:14
0

The error you get:

  Routing Error
  No route matches [POST] "/logs/1/meals/13/edit

indicates that your form does a POST instead of a PUT.

To make this work, just add method: :put to the form_for declaration:

<%= form_for(:meal, method: :put) do |f| %>
Tilo
  • 33,354
  • 5
  • 79
  • 106
  • This is weird advice, it will issue a PUT, but it will never allow to edit an existing record. – nathanvda Feb 03 '16 at 23:33
  • OP said they want to update a record using PUT, and this is how to achieve it. – Tilo Feb 04 '16 at 16:38
  • So, you clearly say "update" a record, your answer is not updating anything. Just using PUT to get new data to the server. – nathanvda Feb 04 '16 at 20:31
  • check: ` PUT /logs/:log_id/meals/:id(.:format) meals#update` ... how is this not updating the meals record? – Tilo Feb 04 '16 at 23:15
  • 2
    Because you are not filling the form with the data the record contained before? Difference between writing `form_for :meal` and `form_for @meal`: the first just uses the symbol to build a path, the second uses the name of the model _and_ the status (new/update) _and_ the data of the model to pre-fill the form. – nathanvda Feb 05 '16 at 10:35
0

If your controller is creating an instance variable called @meal you should use that instead of the symbol. So write:

form_for @meal do |f| 

and then rails can query the instance variable to see whether it is a new_record? (in which case it will POST the data) or an existing record (in which it will b e a PATCH most likely).

To build a nested route, you will need to set an instance variable @log (I do not see that in your code, but you probably do that already), and then you can write:

form_for [@log, @meal] do |f| 

which will calculate the correct path.

nathanvda
  • 49,707
  • 13
  • 117
  • 139