Let's say i have a form which is at:
/categories/new
When form input is invalid I call render. Classic example:
def create
#category = Category.new(category_params)
if #category.save
flash[:success] = 'Category successfully created.'
redirect_to #category
else
render 'new'
end
end
My question is, why site path after call to render is just:
/categories
and not:
/categories/new
As I understand 'new' form should be rendered on the same page and those two paths clearly aren't the same.
As commenters suggested here is additional info:
related routes:
resources :categories do
resources :topics, name_prefix: 'category_',
except: [:index, :show, :edit, :update, :destroy]
end
category new action:
def new
#category = Category.new
end
Don't confuse what the url shows with what is actually rendered.
Short Answer
When you initially input your form you are actually doing a POST to /categories. This of course maps to the create action in your controller which when failing validation causes the new form to render - it isn't a redirect so the url won't change - the url remains the same as it did when you entered the action.
Detailed Explanation
When you invoke /categories/new you are actually being routed to the new action on the categories controller.
Your new action then sets any instance variables, for example #category, and rails will render the new.html.erb which contains your new category form.
When you submit the form it actually is doing a http POST to /categories, which routes to the create action of your categories controller. That url and http method is a result of what is generated from the form_for helper:
<%= form_for #category do |f| %>
This is why the url changes from /categories/new to /categories
Now the create action if it fails the validation simply renders the new form - it is not redirecting anywhere - the url remains unchanged from what it was when it entered this action - meaning, it remains as /categories.
Don't confuse the change in url with a redirection it's just that the form is posting to a different url than /categories/new, and the change in url is actually just a cosmetic issue.
Now if it is a concern to you, you can change the routing to something like the following:
resources :categories, except: [:create, :new]
post 'new_category' => 'categories#create'
get 'new_category' => 'categories#new'
This is mapping the POST and GET http methods to /new_category so the url appears the same for the new and create action invocations. Note you do need to change the url in the form_for helper to use this new route:
<%= form_for #category, url: 'new_category' do |f| %>
Related
A typical Rails update action looks something like this
def update
respond_to do |format|
if #object.update(object_params)
# success results
else
format.html { render :edit }
end
end
end
I would therefore expect that, if update is not successful, the user will end up on path /objects/:id/edit.
Why then is the actual path /objects/:id, but with the edit form displayed?
Is this normal behavior, or something odd happening in this app?
Edit
#routes.rb
devise_for :users
resources :objects do
resources :children, only: [:index, :new, :create]
member do
get :customaction
end
end
#ability.rb
can :create, Object
can :manage, Object, id: #user.object_id
cannot [:index, :destroy], Object
Let me explain,
I hope you have noticed in the edit form its like
<form class="edit_post" id="edit_post_1" action="/posts/1" accept-charset="UTF-8" method="post">
the form data is posted at /posts/1; this is the URL hit by the browser to send the form data.
So when your update tasks fails or any validation error occurs; you do not redirect the http request rather render the edit-form and return to the browser. And the browser displays the form without changing the web address.
Note: only redirect changes the web address, render doesn't.
You assumption would be correct if the form data were submitted to /posts/1/edit rather than /posts/1
I am trying to create a button to change a model record attribute from false to true. I'm using a form_tag as follows:
=form_tag edit_goal_path(goal), method: :post do
=hidden_field_tag :purchased, value: true
=submit_tag "Purchase"
It's haml, but feel free to post suggestions with ERB. I'm getting the following error:
No route matches [POST] "/goals/4/edit"
Rails.root: /home/ben/rails_projects/hartwig
However, I already have the following route from resources:
PUT /goals/:id(.:format) goals#update
My controller looks as following:
def edit
#goal = Goal.find(params[:id])
end
def update
#goal = Goal.find(params[:id])
if #goal.update_attributes(goal_params)
redirect_to '/goals', notice: "Update successful!"
else
render '/'
end
end
def goal_params
params.require(:goal).permit(:item, :description, :picture, :purchased)
end
How do I get this to work? Or is there a better way to solve this?
Your question says:
I am trying to create a button to change a model record attribute from false to true
so why are you using a form for it? I think a better approach would be to create a link or button that will call an ajax method or a normal method with post route and update your attribute. You can achieve it by following these steps:
a. Create a route for your custom action where you'll update your attribute:
post 'purchase_update/:id' => "goal#update_purchase", as: update_purchase #post as you want to send your goal id
b. create your custom method inside your controller:
def update_purchase
#goal = Goal.find(params[:id])
#goal.update_attribute(:purchased, true)
respond_to do |format|
format.html {redirect_to your_path, notice: 'purchase updated'}
format.js {} #if you want to do something by ajax
end
end
c. Create your link that will call this method:
=link_to "Purchase", update_purchase_path(#goal), method: post
and if you want to do it by ajax then
=link_to "Purchase", update_purchase_path(#goal), method: post, remote: true
Another solution to your problem could be adding a new method to the Goal Controller:
in goals_controller.rb
def purchase
#goal.update_attribute(:purchased, true)
end
and also add on top (just add :purchase)
before_action :set_goal, only: [:show, :edit, :update, :destroy, :purchase]
in routes.rb change to
resources :goals do
member do
post 'purchase'
end
end
to add a new post routes to your goals
now you will have a purchase_goal_path that you can use in your view like this:
link_to 'Purchase', purchase_goal_path(#goal), method: :post
I have created a method called verify in a controller (events_controller.rb), and I want to allow that page (verify.html.erb) to accept an object (#event), and show of that objects parameters. I'm creating a show page in essence, but I need to build some special logic into this page that I don't want to build into the show page. I have created the route, but I still get an error when I tell it to find an Event by params[:id]. The actual url it is going to is /verify.(event :id) and I believe it should be routing to events/verify/(event :id).
My error
Couldn't find Event without an ID.
routes.rb
get "verify", to: 'events#verify'
resources :events
events_controller.rb
def verify
#event = Event.find(params[:id])
respond_to do |format|
format.html # verify.html.erb
format.json { render json: #event }
end
end
Thanks Stack!
get "verify/:id", to: 'events#verify', as: "verify"
in browser go to url for example:
localhost:3000/verify/1
I have a resource called Books. It's listed as a resource properly in my routes file.
I have a new action, which gives the new view the standard:
#book = Book.new
On the model, there are some attributes which are validated by presence, so if a save action fails, errors will be generated.
In my controller:
#book = Book.create
... # some logic
if #book.save
redirect_to(#book)
else
render :new
end
This is pretty standard; and the rationale for using render:new is so that the object is passed back to the view and errors can be reported, form entries re-filled, etc.
This works, except every time I'm sent back to the form (via render :new), my errors show up, but my URL is the INDEX URL, which is
/books
Rather than
/books/new
Which is where I started out in the first place. I have seen several others posts about this problem, but no answers. At a minimum, one would assume it would land you at /books/create, which I also have a view file for (identical to new in this case).
I can do this:
# if the book isn't saved then
flash[:error] = "Errors!"
redirect_to new_book_path
But then the #book data is lost, along with the error messages, which is the entire point of having the form and the actions, etc.
Why is render :new landing me at /books, my index action, when normally that URL calls the INDEX method, which lists all the books?
It actually is sending you to the create path. It's in the create action, the path for which is /books, using HTTP method POST. This looks the same as the index path /books, but the index path is using HTTP method GET. The rails routing code takes the method into account when determining which action to call. After validation fails, you're still in the create action, but you're rendering the new view. It's a bit confusing, but a line like render :new doesn't actually invoke the new action at all; it's still running the create action and it tells Rails to render the new view.
I just started with the Rails-Tutorial and had the same problem.
The solution is just simple: If you want the same URL after submitting a form (with errors), just combine the new and create action in one action.
Here is the part of my code, which makes this possible (hope it helps someone^^)
routes.rb (Adding the post-route for new-action):
...
resources :books
post "books/new"
...
Controller:
...
def create
#book = Book.new(book_params)
if #book.save
# save was successful
print "Book saved!"
else
# If we have errors render the form again
render 'new'
end
end
def new
if book_params
# If data submitted already by the form we call the create method
create
return
end
#book = Book.new
render 'new' # call it explicit
end
private
def book_params
if params[:book].nil? || params[:book].empty?
return false
else
return params.require(:book).permit(:title, :isbn, :price)
end
end
new.html.erb:
<%= form_for #book, :url => {:action => :new} do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :isbn %>
<%= f.text_field :isbn %>
<%= f.label :price %>
<%= f.password_field :price %>
<%= f.submit "Save book" %>
<% end %>
Just had the very same question, so maybe this might help somebody someday. You basically have to make 3 adjustments in order for this thing to work, although my solution is still not ideal.
1) In the create action:
if #book.save
redirect_to(#book)
else
flash[:book] = #book
redirect_to new_book_path
end
2) In the new action:
#book = flash[:book] ? Book.new(flash[:book]): Book.new
3) Wherever you parse the flash hash, be sure to filter out flash[:book].
--> correct URL is displayed, Form data is preserved. Still, I somehow don't like putting the user object into the flash hash, I don't think that's it's purpose. Does anyboy know a better place to put it in?
It doesn't land you at /books/new since you are creating resource by posting to /books/. When your create fails it is just rendering the new action, not redirecting you to the new action. As #MrYoshiji says above you can try redirecting it to the new action, but this is really inefficient as you would be creating another HTTP request and round trip to the server, only to change the url. At that point if it matters you could probably use javascript change it.
It can be fixed by using same url but different methods for new and create action.
In the routes file following code can be used.
resources :books do
get :common_path_string, on: :collection, action: :new
post :common_path_string, on: :collection, action: :create
end
Now you new page will render at url
books/common_path_string
In case any errors comes after validation, still the url will be same.
Also in the form instead using
books_path
use
url: common_path_string_books_path, method: :post
Choose common_path_string of your liking.
If client side validation fails you can still have all the field inputs when the new view is rendered. Along with server side errors output to the client. On re-submission it will still run create action.
In books_controller.rb
def new
#book = current_user.books.build
end
def create
# you will need to have book_params in private
#book = current_user.books.build(book_params)
if #book.save
redirect_to edit_book_path(#book), notice: "Book has been added successfully"
# render edit but you can redirect to dashboard path or root path
else
redirect_to request.referrer, flash: { error: #book.errors.full_messages.join(", ") }
end
end
In new.html.erb
<%= form_for #book, html: {class: 'some-class'} do |f| %>
...
# Book fields
# Can also customize client side validation by adding novalidate:true,
# and make your own JS validations with error outputs for each field.
# In the form or use browser default validation.
<% end %>
I am attempting to create a login system. My UserController has this to control the new action:
def new
#user = User.new
respond_to do |format|
format.json { render :json => #user }
format.html
end
end
and my routes.rb has this to link:
resources :user
The View's form to create a new user is this:
<%= form_for #user do |f| %>
however, I receive an Action Controller error with this:
undefined method `users_path' for #<#<Class
What has baffled me is why it is using users_path. This is a plural reference to my route. Why is it returning a plural error of user_path?? When I route `resources :users' it clears the error, but of course I don't have anything setup for that resource and thus produces other errors.
Inside form_for it creates an appropriate action and method based on whether or not the model is persisted.
If a model is unpersisted it will create an action of :new and method of :post. If it is persisted then it will be :update and :put instead.
For :new the default url is '/users' and for :edit it's '/users/:id'.
The fix is as Jim said. (Beat me to it.) Apply a url option to form_for.
resources :users is actually the correct structure, since it is a collection of users, not a single user. The route construction also expects a plural path (IIRC, it expects a plural path unless it finds a resource, as opposed to resources route), hence the attempt to use users_path.
Passing an explicit url parameter is another option <%= form_for #user, :url => user_path do |f| %>, since you already have things expecting singular routes