rails POST on /objects calling index method - ruby-on-rails

Normally in rails, sending a POST to a RESTful controller, say, /orders, calls the #create action. This is what I want to happen. Instead, the #index method gets called. How do I fix this?
The server log from a POST to /orders:
Started POST "/orders" for 173.8.132.62 at 2013-03-24 14:45:23 -0700
Processing by OrdersController#index as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"EcsAZxbyd5OVVo5oIJZM/CNoyP7Cz6dRVBU7I41xENY=", "order"=>{"order_lines_attributes"=>{"0"=>{"item_id"=>"", "qty"=>"", "_destroy"=>"false"}}, "customer_id"=>""}, "commit"=>"Create Order"}
Order Load (0.1ms) SELECT "orders".* FROM "orders"
Rendered text template (0.0ms)
Completed 200 OK in 5ms (Views: 1.1ms | ActiveRecord: 1.2ms)
The entire routes.rb:
Wines::Application.routes.draw do
match ':controller(/:action(/:id))(.:format)'
resources :customers do
resources :phones
resources :addresses
end
resources :distributors do
resources :distributor_phones
resources :distributor_addresses
end
resources :items do
post 'sample', :on => :member
end
resources :orders do
post 'place', :on => :member
post 'deliver', :on => :member
resources :order_lines
end
resources :db do
post 'query', :on => :collection
end
resources :images, :beverage_types, :grapes
end
Some possibly relevant lines routes from rake routes:
place_order POST /orders/:id/place(.:format) orders#place
deliver_order POST /orders/:id/deliver(.:format) orders#deliver
order_order_lines GET /orders/:order_id/order_lines(.:format) order_lines#index
POST /orders/:order_id/order_lines(.:format) order_lines#create
new_order_order_line GET /orders/:order_id/order_lines/new(.:format) order_lines#new
edit_order_order_line GET /orders/:order_id/order_lines/:id/edit(.:format) order_lines#edit
order_order_line GET /orders/:order_id/order_lines/:id(.:format) order_lines#show
PUT /orders/:order_id/order_lines/:id(.:format) order_lines#update
DELETE /orders/:order_id/order_lines/:id(.:format) order_lines#destroy
orders GET /orders(.:format) orders#index
POST /orders(.:format) orders#create
new_order GET /orders/new(.:format) orders#new
edit_order GET /orders/:id/edit(.:format) orders#edit
order GET /orders/:id(.:format) orders#show
PUT /orders/:id(.:format) orders#update
DELETE /orders/:id(.:format) orders#destroy
I'm using formtastic, and my _form.html.erb looks like this:
<%= semantic_form_for(#order) do |f| %>
<% if #order.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#order.errors.count, "error") %> prohibited this order from being saved:</h2>
<ul>
<% #order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.semantic_fields_for :order_lines do |builder| %>
<%= render 'order_line_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add Order Line", f, :order_lines %>
<%= f.inputs :customer %>
<%= f.actions %>
<% end %>
The generated html <form> tag looks like this:
<form accept-charset="UTF-8" action="/orders" class="formtastic order" id="new_order" method="post" novalidate="novalidate"><div style="margin:0;padding:0;display:inline">
The generated submit action looks like this:
<input name="commit" type="submit" value="Create Order" />
Relevant code from the controller is as follows. When I submit a POST to /orders, I get
this is the index method, called with URL http://w:3000/orders and
method POST.
def index
#orders = Order.all
respond_to do |format|
format.xml # index.xml.erb
format.html {render :text => "this is the index method, called with URL #{request.url} and method #{request.method}"}
end
end
# GET /orders/1.xml
def show
#order = Order.find(params[:id])
respond_to do |format|
format.xml { render xml: #order.to_xml }
end
end
# GET /orders/new.xml
def new
#order = Order.new
1.times {#order.order_lines.build}
respond_to do |format|
format.xml { render xml: #order.to_xml }
format.html {}
end
end
# POST /orders.xml
def create
#order = Order.new(params[:order])
respond_to do |format|
if #order.save
format.xml { render xml: #order.to_noko_doc }
format.html { redirect_to #order, notice: 'Order was successfully created.'}
else
format.xml { render xml: #order.errors }
format.html {render action: 'new'}
end
end

Your match ':controller(/:action(/:id))(.:format)' route is getting matched first for the POST to orders#create, move it to the bottom of your routes file

Routes are matched in the order they are defined, so if your index route /orders comes before your create route /orders :via => [:post], the former will be invoked. Try placing your post route first.
match "orders" => "orders#create", :via => [:post]
# then resources :orders

Related

ID param not being sent to Controller when manually changing routes

I have looked through the other answers provided on StackOverflow, and none of them answered my question. Here is what is happening with my code.
Error
undefined method `update' for nil:NilClass
Problem:
It looks like the param id is not being sent to the controller from the form as they show up as nil in the console using byebug.
console readout:
(byebug) params[:id]
nil
(byebug) #support
nil
(byebug) params[:title]
nil
(byebug) params[:support]
<ActionController::Parameters {"title"=>"Test", "subtitle"=>"testing",
"website"=>"www.test.com", "type_of_support"=>"", "description"=>""}
permitted: false>
(byebug) params[:support][:id]
nil
(byebug) params[:support][:title]
"Test"
I do not believe that the problem is with the form as it is the same form partial used for the new/create action and the params are sent to the controller then and the object is created (though in that case there is no id, since it is generated when creating the object, not passed from the form).
You can see in my code below that the route for PATCH is just 'support' without the :id param. If I try to add that to the route, I get an error stating that there is no route matching 'support/'. So, I have to take away the :id param in the route for it to pass the information to the controller.
I am at a loss here. How do I pass the :id to the controller? How does rails do this? Before I manually change the routes, the automatic routes from resources :supports includes an :id param for the PATCH route and it works. What am I doing wrong that it won't allow me to add that to the route?
Code:
config/routes.rb
get 'support', as: 'supports', to: 'supports#index'
post 'support', to: 'supports#create'
get 'support/new', as: 'new_support', to: 'supports#new'
get 'support/:id/edit', as: 'edit_support', to: 'supports#edit'
get 'support/:title', as: 'support_page', to: 'supports#show'
patch 'support/', to: 'supports#update'
put 'support/:id', to: 'supports#update'
delete 'supports/:id', to: 'supports#destroy'
Results this for rake routes:
supports GET /support(.:format) supports#index
support POST /support(.:format) supports#create
new_support GET /support/new(.:format) supports#new
edit_support GET /support/:id/edit(.:format) supports#edit
support_page GET /support/:title(.:format) supports#show
PATCH /support(.:format) supports#update
PUT /support/:id(.:format) supports#update
DELETE /supports/:id(.:format) supports#destroy
app/controllers/supports_controllers.rb
class SupportsController < ApplicationController
before_action :set_support_by_title, only: [:show]
before_action :set_support_by_id, only: [:edit, :update, :destroy]
def index
#supports = Support.all
end
def show
end
def new
#support = Support.new
end
def edit
end
def create
#support = Support.new(support_params)
respond_to do |format|
if #support.save
format.html { redirect_to #support,
notice: 'Support was successfully created.' }
else
format.html { render :new }
end
end
end
def update
# byebug
respond_to do |format|
if #support.update(support_params)
format.html { redirect_to #support,
notice: 'Support was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#support.destroy
respond_to do |format|
format.html { redirect_to supports_url,
notice: 'Support was successfully destroyed.' }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_support_by_title
#support = Support.find_by(title: params[:title])
# byebug
end
def set_support_by_id
#support = Support.find(params[:id])
# byebug
end
# Never trust parameters from the scary internet,
# only allow the white list through.
def support_params
params.require(:support).permit(:title,
:subtitle,
:website,
:type_of_support,
:description)
end
end
app/views/supports/edit.html.erb
<h1>Editing Support</h1>
<%= render 'form', support: #support %>
<%= link_to 'Show', support_page_path(#support.title) %> |
<%= link_to 'Back', supports_path %>
app/views/supports/_form.html.erb
<%= form_with(model: support, local: true) do |form| %>
<% if support.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(support.errors.count, "error") %>
prohibited this support from being saved:
</h2>
<ul>
<% support.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
Title:
<%= form.text_field :title, id: :support_title %>
</div>
<div class="field">
Subtitle:
<%= form.text_field :subtitle, id: :support_subtitle %>
</div>
<div class="field">
Website:
<%= form.text_field :website, id: :support_website %>
</div>
<div class="field">
Type of Support:
<%= form.text_field :type_of_support, id: :support_type %>
</div>
<div class="field">
Description:
<%= form.text_area :description, id: :support_description %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
While writing this question, I thought of something and tried it. And it worked. Instead of re-writing all of the routes myself, I wrote only 2 and used the resources :supports, except: [:index, :show] to have rails generate the others. This solved my issue.
Explanation
I knew that something was going on behind the scenes that I did not understand. The entire process worked fine before I started to change the routes. So, something in there was incorrect. (I still don't know what it is and how to change it.)
The only two routes that I really want to be changed are the two that users see. I don't care about how the routes look in the admin backend. So, that meant that I only needed to change the routes for index and show to be SEO friendly and look better in the browser. So, to do that I wrote the routes like this:
config/routes.rb
resources :supports, except: [:index, :show]
get 'support', as: 'support_index', to: 'supports#index'
get 'support/:title', as: 'support_page', to: 'supports#show'
This then created all of the new, create, edit, update, destroy routes for me. After doing this, this is how my routes now look:
supports POST /supports(.:format) supports#create
new_support GET /supports/new(.:format) supports#new
edit_support GET /supports/:id/edit(.:format) supports#edit
support PATCH /supports/:id(.:format) supports#update
PUT /supports/:id(.:format) supports#update
DELETE /supports/:id(.:format) supports#destroy
support_index GET /support(.:format) supports#index
support_page GET /support/:title(.:format) supports#show
As you can see, the PATCH route is now getting the param :id to be able to update the record.
Now, I just had to change a few of the redirects in the controller after create, update and destroy like this:
def create
#support = Support.new(support_params)
respond_to do |format|
if #support.save
format.html { redirect_to support_page_path(title: #support.title),
notice: 'Support was successfully created.' }
else
format.html { render :new }
end
end
end
def update
respond_to do |format|
if #support.update(support_params)
format.html { redirect_to support_page_path(title: #support.title),
notice: 'Support was successfully updated.' }
else
format.html { render :edit }
end
end
end
def destroy
#support.destroy
respond_to do |format|
format.html { redirect_to support_index_path,
notice: 'Support was successfully deleted.' }
end
end
These redirect_to statements now match the routes that I generated for index and show.
And now everything works.
So, problem solved, though I still don't know what I was doing wrong before. Any light that can be shed would be appreciated.

Embed a Rails form partial into another page

I'm building a rails 4.2.0 app with a contact us page (this page does have a semi-empty controller). I'm trying to embed a form partial from another controller.
Here is the code (minus the text):
<% if user_signed_in? %>
<% render 'enquiries/form' %>
<% end %>
When I run this I get the error 'First argument in form cannot contain nil or be empty'.
My enquiries form looks like a basic rails form:
<%= form_for #enquiry do |f| %>
<% if #enquiry.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#enquiry.errors.count, "error") %> prohibited this enquiry from being saved:</h2>
<ul>
<% #enquiry.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :subject, "Subject:" %><br>
<%= f.text_field :subject %>
</div>
<div class="field">
<%= f.label :e_description, "Description:" %><br>
<%= f.text_area :e_description %>
</div>
<div class="actions">
<%= f.submit %>
</div>
What could be the possible reason for the error? Or is there a better way of embedding a view into another?
Update/Edit:
Here's the routes:
devise_for :users
resources :rooms do
resources :viewings
end
resources :rmcats
resources :extras
resources :extracats
resources :enquiries
root :to => redirect('/pages/home')
get 'pages/home'
get 'pages/contactus'
And the enquiry controller:
class EnquiriesController < ApplicationController
before_action :set_enquiry, only: [:show, :edit, :update, :destroy]
# GET /enquiries
def index
#enquiries = Enquiry.all
end
# GET /enquiries/1
def show
end
# GET /enquiries/new
def new
#enquiry = Enquiry.new
end
# GET /enquiries/1/edit
def edit
end
# POST /enquiries
def create
#enquiry = Enquiry.new(enquiry_params)
if #enquiry.save
redirect_to #enquiry, notice: 'Enquiry was successfully created.'
else
render :new
end
end
# PATCH/PUT /enquiries/1
def update
if #enquiry.update(enquiry_params)
redirect_to #enquiry, notice: 'Enquiry was successfully updated.'
else
render :edit
end
end
# DELETE /enquiries/1
def destroy
#enquiry.destroy
redirect_to enquiries_url, notice: 'Enquiry was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_enquiry
#enquiry = Enquiry.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def enquiry_params
params.require(:enquiry).permit(:subject, :e_description)
end
end
This is the pages controller:
class PagesController < ApplicationController
around_filter :resource_not_found
# def home
# end
private
# If resource not found redirect to root and flash error.
# => For pages this will rarely be needed as it should 404.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Page not found."
end
end
Edit:
Log:
Started GET "/pages/contactus" for ::1 at 2015-03-21 01:05:25 +0000
Processing by EnquiriesController#new as HTML
[1m[35mUser Load (0.0ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Rendered enquiries/_form.html.erb (0.0ms)
Rendered pages/contactus.html.erb within layouts/application (0.0ms)
Completed 200 OK in 235ms (Views: 234.6ms | ActiveRecord: 0.0ms)
It is telling you that #enquiry is nil at the time it is trying to render the form. You need to call the new action to create the #enqiury for the form to represent.
You could change your route to:
get 'pages/contactus' => 'enquiries#new'
Then in your Enquiry controller:
def new
#enquiry = Enquiry.new
render 'pages/contactus'
end
EDIT:
Ok, so now we combine what Friends Systems put in his answer:
<% if user_signed_in? %>
<%= render 'enquiries/form' enquiry: #enquiry %>
<% end %>
And now change any instance of #enquiry in the form to enquiry
This is because you need to pass the variable to the partial.
the problem is, that your #enquiry variable is not defined in the context you are rendering the partial.
its not defined by the controller action that gets called, you should create a instance of Enquiry by calling
#enquiry = Enquiry.new
in your action.
In Addition
to use it somewhere else i would pass the #enquiry instance variable as a locale variable to the partial
<% render 'enquiries/form', :enquiry => #enquiry %>
your form method should then look like this:
<%= form_for enquiry do |f| %>
...
<% end %>
of course all the instances vars should be replaced then. just remove the '#'
EDIT:
According to your controller setup you posted above the best way would be to use something like
#enquiry ||= Enquiry.new
in your form partial to make shure a new instance is created if #enquiry is nil.

twice nested form path issue

i have the following form for my user_calendar model:
<%= form_for([company,user,calendar], :remote => true) do |f| %>
....
<% end %>
route is the following:
scope '(:locale)' do
resources :companies do
resources :users do
resources :user_calendar
end
end
end
When user clicks Edit on users/home:
<%= link_to 'E', edit_company_user_path(user.company, user), :remote => true %>
The method is called from users_controller:
def edit
#calendar = #user.user_calendars.build
respond_to do |format|
format.js
end
end
And edit.js should load the models data for the form:
$('#calendar-form').html("<%= escape_javascript(render :partial => 'user_calendars/form', :locals => { :company => #company, :user => #user, :calendar => #calendar }) %>");
But instead of rendering the form i get the following error:
ActionView::Template::Error (undefined method `company_user_user_calendars_path' for #<#<Class:0x007fc351d12808>:0x00000001cf6128>):
The three #company, #user and #calendar have the right data, but still the path shows the error. Here the result of rake routes on user_calendars controller:
Controller#Action
company_user_user_calendar_index GET (/:locale)/companies/:company_id/users/:user_id/user_calendar(.:format) user_calendar#index
POST (/:locale)/companies/:company_id/users/:user_id/user_calendar(.:format) user_calendar#create
new_company_user_user_calendar GET (/:locale)/companies/:company_id/users/:user_id/user_calendar/new(.:format) user_calendar#new
edit_company_user_user_calendar GET (/:locale)/companies/:company_id/users/:user_id/user_calendar/:id/edit(.:format) user_calendar#edit
company_user_user_calendar GET (/:locale)/companies/:company_id/users/:user_id/user_calendar/:id(.:format) user_calendar#show
PATCH (/:locale)/companies/:company_id/users/:user_id/user_calendar/:id(.:format) user_calendar#update
PUT (/:locale)/companies/:company_id/users/:user_id/user_calendar/:id(.:format) user_calendar#update
DELETE (/:locale)/companies/:company_id/users/:user_id/user_calendar/:id(.:format) user_calendar#destroy
it could happens because of: resources :user_calendar
if you need resources you should write resources :user_calendars - with (s)
also you could look to guid about resources/resource

Form for nested resource: undefined method `model_name' for NilClass:Class

I'm doing an online tutorial and was tasked with implementing a comment resource that is nested under a post resource, which in turn is under a topic resource. I've figured out all of the steps of the exercise except getting the 'comment create' form to show up on post#show. I have been trying for several hours to fix it but keep getting the following error: undefined method "model_name" for NilClass:Class. I have no idea what it is referring to by model_name and in my desperation I even looked at the tutorial's code on GitHub, but I still can't see where I'm making the mistake.
My form is in a partial and the error points to the first line, so I'll paste that code here first:
<%= form_for [topic, post, comment] do |f| %>
<div class="controls">
<%= f.text_area :body, rows: 8 %>
</div>
<div class="control-group">
<div class="controls">
<%= f.submit "Add Comment", class: 'btn' %>
</div>
</div>
<% end %>
I'll post my routes so you can see the nesting:
X::Application.routes.draw do
devise_for :users
resources :topics do
resources :posts, except: [:index] do
resources :comments, only: [:create]
end
end
match "about" => 'welcome#about', via: :get
root to: 'welcome#index'
end
Here is my Comments controller:
class CommentsController < ApplicationController
def new
end
def create
#topic = Topic.find(params[:topic_id])
#post = Post.find(params[:post_id])
#comment = current_user.comments.build(params[:comment])
#comment.post = #post
if #comment.save
flash[:notice] = "Comment was saved."
redirect_to #post
else
flash[:error] = "Error! Try again."
render :new
end
end
end
Here is my post#show view (form should be rendering at the bottom):
<h1><%= markdown #post.title %></h1>
<div class="row">
<div class="span8">
<small>
<%= image_tag(#post.user.avatar.tiny.url) if #post.user.avatar? %>
submitted <%= time_ago_in_words(#post.created_at) %> ago by
<%= #post.user.name %>
</small>
<br><br>
<p><%= image_tag(#post.image.url) if #post.image? %></p>
<p><%= markdown #post.body %></p>
</div>
<div class="span2">
<% if can? :edit, #post %>
<%= link_to "Edit Post", edit_topic_post_path(#topic,#post), class: 'btn btn-small' %>
<% end %>
</div>
</div>
<br><br>
<h4>Comments</h4>
<%= render #comments %>
<br>
<%= render :template => "/comments/_form.html.erb", locals: { topic: #topic, post: #post, comment: #comment } %>
I would be SO grateful if anyone could help me with this problem. And let me know if there's any info I should add. Thank you!
Generally from what i have read its not recommended using 3 level nested resources. Could you link to the tutorial you are following?
Anyhow I would suggest using shallow on your routes instead.
resources :topics, shallow: true do
resources :posts, except: [:index], shallow: true do
resources :comments, only: [:create]
end
end
Will give you the following resources, which in return will make it easier to create correct forms in your view.
post_comments POST /posts/:post_id/comments(.:format) comments#create
topic_posts POST /topics/:topic_id/posts(.:format) posts#create
new_topic_post GET /topics/:topic_id/posts/new(.:format) posts#new
edit_post GET /posts/:id/edit(.:format) posts#edit
post GET /posts/:id(.:format) posts#show
PATCH /posts/:id(.:format) posts#update
PUT /posts/:id(.:format) posts#update
DELETE /posts/:id(.:format) posts#destroy
topics GET /topics(.:format) topics#index
POST /topics(.:format) topics#create
new_topic GET /topics/new(.:format) topics#new
edit_topic GET /topics/:id/edit(.:format) topics#edit
topic GET /topics/:id(.:format) topics#show
PATCH /topics/:id(.:format) topics#update
PUT /topics/:id(.:format) topics#update
DELETE /topics/:id(.:format) topics#destroy
Now you should only have to send post_id to your comments form. Read more here I haven't worked much with 3 level nesting but this should give you an idea of how you could make it easier for yourself.
EDIT:
In the create action you should build the comment using #post and then set the user to current_user.
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(params[:comment])
#comment.user = current_user
if #comment.save
flash[:notice] = "Comment was saved."
redirect_to #post
else
flash[:error] = "Error! Try again."
render :new
end
end
And now no need for #topic
<%= render :template => "/comments/_form.html.erb", locals: { post: #post, comment: #comment } %>
Add '#comment = Comment.new' to the "show" action of the PostController. It had been defined only in the CommentController "create" action.

Why do I get this error?

I'm getting this error:
ActiveRecord::RecordNotFound (Couldn't find Video without an ID):
app/controllers/videos_controller.rb:52:in `topic_update'
It's referring to this action in my videos controller:
def topic_update
#video = Video.find(params[:id])
respond_to do |format|
if #video.update_attributes(params[:video])
format.html { redirect_to(#video) }
format.js
else
format.html { render :action => "edit" }
end
end
end
The error is thrown after this form sends a PUT request:
<%= form_for #video, :url => {:action => "topic_update"}, :remote => true do |f| %>
<div class="field">
<%= f.text_field :topic_names, :class => "topic_field" %>
</div>
<%= f.submit "Add Topic", :id => 'topic_submit' %>
<% end %>
This is what happens according to my logs:
Started PUT "/topic/update.js" for 127.0.0.1 at Mon Apr 11 00:12:19 -0700 2011
Processing by VideosController#topic_update as JS
Parameters: {"video"=>{"topic_names"=>"eedefva"}}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 57 LIMIT 1
I have this in my routes.rb file:
resources :videos
match '/topic/update' => "videos#topic_update"
This is because your 'topic_update' method will treat as a 'GET' method where as you want it as a post method,
try this in your routes.rb
resources :videos do
member do
put 'topic_update'
end
end
I haven't tested this but :D
read here for more info (http://guides.rubyonrails.org/routing.html)
HTH
cheers
sameera
Basically, you are trying to update a non-existent (not saved before) object.
This method form_for #video would add id parameter if #video is referring to an existent record that was saved before.
Please make sure you're calling update procedure (showing "edit" form) only if #video corresponds to a stored record.

Resources