2

I have two models in the following releationship: Client has_many products, and Product belong_to client. In the client show view I present a form to create new products, that automatically belong to the current client. The show method in the client controller

def show
  @client    = Client.find(params[:id])
  @products  = @client.products.paginate(page: params[:page])
  @product = @client.products.new
  @product.client_id = @client.id
end

and the show view renders a partial

    <h1>New Product:</h1>
    <%= render 'shared/product_form' %>

That works, products are correctly created.

When an validation error occurs I set a flash in the product create method and redirect_to the client show page. There I loose the data that has been filled in correctly. I tried save the @product instance variable, which has all the data (including the wrong fields) doing

render client_path(client) 

from the product controller, but that produces an error

Missing template /clients/17

with the address being

http://localhost:3000/products

Am I calling this wrong? I know that render ususally renders action of the same controller. Can I somehow render Client::show from the product controller? Is there another way to save the data the user has typed in?

3 Answers 3

5

If a validation error occurs you should redirect back to the page that generated the validation error. Ie: if the user is at products/new when they submit the form, then your products#create action should end with render :new to present the products form again.

If your products#create action is receiving the form from clients#show, then you do want to render clients#show with validation errors. In that case, all the information that was completed in the form will be available at params[:product], just like it is coming in to products#create.

You might want to take a look at another answer I wrote recently to understand the flow between controllers.

Specifically, the misunderstanding in your case is as follows:

When you have a validation error the record will not save, so you cannot "go back to that data" because your app has not kept it anywhere. The only copy of the data that was submitted is in the request.

If you REDIRECT you are not forwarding a request, you are responding to the initial POST request (which includes all the form information as params[:product]) by making a new request to a different url. This is why you want to use RENDER.

However, if you try to render clients_path(client), what happens is Ruby will first evaluate clients_path(client) to the string clients/(client.id), or in the example you gave, clients/17.

Then render tries to call render 'clients/17', which you don't have a template for. It's looking for a file called clients/17.html.erb. That's why you get the error you're getting.

So, again, to sum up - your products#create action receives the information that was sent from the form as params[:products]. That information is not available outside of this controller action. So, if there is a validation error, instead of creating the product, this controller action should render the same page that the user came from originally (normally products/new) so that they can see the form they just had (with the information filled back into it if you're using a form builder) and also see the error that prevented saving.

I hope that makes sense, feel free to ask follow-up questions.

Sign up to request clarification or add additional context in comments.

3 Comments

So I thought I understood: I changed the line in the controller above to @[email protected](params[:product]) which should have populated the instance variable. @product is then used in the form_for(@product), where in edit view it correctly populates the fields. But that did not work... What am I doing wrong now?
params[:product] doesn't travel across a redirect - it is part of the original request that your controller is responding to. You need to render the form the user is filling out from the action that the form is posting to. If that still doesn't make sense, please post your two controllers and specify which view the user starts out on when they are filling out the form.
Now I got it, and it works. A lot to wrap your head around... THX!
0

Yes, you were calling it wrong.

There are three problems:

  1. render should render a template name, say client/new, shared/form etc. The argument could not be a path or variable. The variable is passed by controller to view, nothing to do with render.

  2. You should not use render for a saving fail. Even if you use a template name as #1 mentioned, you'll end up with a wrong URL like products/create with the client page. That's not acceptable.

    My suggestion is to always use redirect for saving fail.

  3. Minor problem. In controller, if you've used @product = @client.products.new, the @product object will have every attributes empty but with a valid client id. So you don't need to assign client id again by @product.client_id = @client.id. But this doesn't hurt the result.

2 Comments

I originally used redirect_to which didn't fill in the fields as expected, so for my question I had tried render. As Andrew mentioned, I use redirect again with @[email protected](params[:product]) in the controller code. But that doesn't work either. Is params[:product] empty??
You should have product_id to use. But use redirect :back is good enough.
0

Andrew's great answer plus this:
Where to render comments controller in Rails on model validations failure?
makes the solution more clear.

Your specific example: Just watch out that your create action in the ProductsController has all instance variables it needs to render 'clients/show' from it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.