render :action for polymorphic nested resource - ruby-on-rails

I have a polymorphic nested resource for user
companies/:id/users/new
departments/:id/users/new
Now, if the create action succeeds I can redirect to proper path (I redirect back to new) but if it fails how do I render the same page again as I need to display errors and let the filled in values as is. 'render action: new' defaults to companies/:id/users/new
if #user.save
redirect_to send("new_#{#parent.class.to_s.underscore}_user_path", #parent
else
render action: new

You can try make routes through values
redirect_to [:new, #parent, :user]

Nevermind, if it helps someone, it turns out in my nested form I had used #company, #user instead of #parent, #user. It should have been
form_for [#parent, #user] do |f|
Now it works fine. Thanks for all answers.

Related

In Rails when a resource create action fails and calls render :new, why must the URL change to the resource's index url?

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 %>

Loading the new action creates strange error

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

Rails render named route with correct URL

I have the following route in my rails application
match '/settings', to: 'Users#edit', as: 'settings'
And corresponding controller code
def edit
#user = current_user
end
def update
correct_user
#user = User.find(params[:id])
if !#user.authenticate(params[:old_password])
flash[:error] = 'Old password did not match'
redirect_to settings_path
elsif #user.update_attributes(params[:user])
flash[:success] = "Settings updated"
redirect_to settings_path
else
render 'edit'
end
end
My edit page is as of now just a password change page, and when I visit /settings I see the page I'd except. When I redirect_to settings_path, the url remains /settings, which is the behavior I want.
In my edit template, I have code to handle object errors and render them on the page. When render 'edit, this code is triggered if there was an object error. If I redirect to the page, however, the code is not there. So I need to call render 'edit' to see the errors.
However, calling render 'edit' causes the URL to change to /users/:id/edit, which is exactly what I don't want. I'd like the URL to remain /settings after calling render 'edit'. How can I achieve this?
NOTE: I've already searched SO and other parts of the internet but have found nothing that suits my needs. There are one or two SO topics with similar issues but they all use flashing and hacky redirect-based workarounds, which is not what I want.
You would want to set up your routes like this:
match 'settings': 'users#edit', via: :get
match 'settings': 'users#update', via: :put
Then the form should be declared like this:
<%= form_for #user, url: settings_path %>
In your controller, make sure you're calling render like this:
render action: "edit"
You probably have a bad link_to in your view, one using the old-style :controller and :action arguments separately instead of using settings_path.
Keep in mind that if there are two routes to the same controller and action pair, the first route defined has priority. Define your custom route first to ensure it's used by default.

Rendering new form for a nested Ruby on Rails Resource

In my Ruby on Rails application, each group has_many :expenses. I have nested my routes, so expenses are entered only as child entities of their parent groups. Here's an excerpt from routes.rb.
resources :groups do
resources :expenses
end
I cannot figure out how to render the 'new' action in the case of an expense not saving when it is submitted through /groups/:group_id/expenses/new. In my expenses_controller.rb, here is how the create action is defined:
def create
#expense = Expense.new(params[:expense])
#expense.group_id = params[:group_id]
if #expense.save
redirect_to group_expense_path(#expense.group.id, #expense.id)
else
render 'new'
end
end
Everything works fine if I satisty expense validation and #expense.save winds up working. However, when it fails and the code tries to render 'new' I get:
undefined method `expenses_path' for #<#<Class:0x007fd408b1fd58>:0x007fd408f21ca8>
So, I am assuming I have something about my nested routing wrong. How do I return the user to the new form but still display to him/her through the flash[] params the errors with the data they originally attempted to submit?
The problem is that #group is not initialized
So in your controller just do
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Looks like you need to explicitly specify the url for form_for in your view.
Something likeā€¦
<%= form_for #expense, :url => group_expenses_path(#group.id) do |f| %>
...
<% end %>
In your <%= form_for %> you have used #group for url, because expenses belongs_to groups. But inside your create action in the controller you have not defined what is #group, so first you should define it as:
#expense = Expense.new(params[:expense])
#group = Group.find(params[:group_id])
#expense.group_id = #group.id
Also I would suggest to use respond_to in your controller:
respond_to do |format|
if #expense.save
format.html { redirect_to group_expense_path(#group.id, #expense.id), :notice => "Any msg you want" }
else
format.html { render :action => "new" }
end
end
All of these are in your create action inside the controller.
Also for different rendering methods look up: http://guides.rubyonrails.org/layouts_and_rendering.html
Hope this helps!

Rails validation conditional redirect

I am currently having an issue with how Rails is performing and responding to a validation result. I have a user registration form. The user could hit this form in two different places. They could hit the form from the homepage or from users/new. Both forms will post to the same place as I am trying to keep it DRY.
The users/new page works as is expected. If the user has a validation issue it will return and populate the form. Where I get a problem is on the home page. If a user has a validation issue it now redirects to the users/new page. I would much prefer that when on the home page I would return the user to that same page and show the validation results there. Is there a way in the controller to redirect to the form the user was at?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.xml { render :xml => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" } # I'm thinking I can do something here?
format.xml { render :xml => #user.errors, :status => :unprocessable_entity }
end
end
end
I have tried to change the render :action => 'new' line to redirect to the user url but it hasn't worked. Is there something I'm missing?
First, I would add querystring parameters to the URL it is posting to with the controller and action that it came from with something like this:
# Using form_tag
<%= form_tag user_path(#user, :controller_name => controller.controller_name, :action_name => controller.action_name) do %>
# Using form_for
<%= form_for #user, :url => user_path(#user, :controller_name => controller.controller_name, :action_name => controller.action_name) do %>
Then, you can update that line in the create action of your controller like this:
render '#{params[:controller_name]}/#{params[:action_name]}'
Update
I just realized that using the code above, will render the correct view the first time validation fails, but if validation fails a second time, it will try to render the users/create view. If this is the route you want to take, you should not use controller.controller_name, etc in the view, but assign #controller_name correctly and use that variable instead. However, this only adds to the 'overkill' comment made by Xavier.
Art's on the right track, but you can't use a redirect, as you need the instance variable #user that's set in your controller, which'll be lost on a new HTTP request (because ever request is handled by a new, clean controller instance).
But you can use the referer information yourself, and use that to pick the right page to render:
render :action => (request.referer =~ /\/users\/new/)? :new : :index
Note: Another answer popped up while I was posting that suggests adding the old controller / action fields to your form, but that seems like overkill to me - you already have all the information you need in request.referer.
Hope that helps!
Try redirect_to :back
It's a shorthand for redirect_to(request.env["HTTP_REFERER"])
oops, it only works for success. sorry
well, then you have to check inside the block (after format.html) where he came from (by looking at request.env["HTTP_REFERER"]) and render respective action.

Resources