ddzxc
ddzxc

Reputation: 65

Redirect to another controller's action with form validation error in Rails

I have parent and child, child form is in parent page so I can make children.

I want to redirect or render parent controller's show page in child's controller.

My controller is :

@child = Child.new(child_params)
@parent = Parent.find(params[:parent_id])
if @child.save
  redirect_to parent_path(@child.parent_id), flash: { alert: 'success' }
else
  render template: 'parents/show'
end

How can I redirect or render another controller's action with keeping form validation?

I have presence: true in my child model.

And I have error_message in parent's view file (form of child).

When I render another controller's action, my view's code (parent) with instance variable throws an error.

  # parents show page
  <% @children.each do |child| %>
  # blabla

  # child controller
      @child = SOME LOGICS
  # cannot use @parent.childs

  # render template: 'parents/show' makes nil error of @child

How can I pass variable to render template another controller's action or how can I redirect with form error?

I have tried:

if @child.save
  redirect_to parent_path(@child.parent_id)
else
  redirect_to parent_path(@child.parent_id), flash: { error_message: 'failed') }
  # did not work
end


if @child.save
    redirect_to parent_path(@child.parent_id)
else
    render template: 'parents/show', { @child }

    # did not work either
end

but nothing seems to work.

Upvotes: 1

Views: 574

Answers (1)

max
max

Reputation: 102368

When the client sends a request to create or update a resource with invalid data you should render - not redirect. You're responding specifically to a non-idempotent POST or PATCH request with information about the errors specific to that reqest.

A redirect on the other hand always results in a GET request (which should be idempotent). And if you wanted to carry the users input over you would have to either pass it as query string parameters or for example store it in the session (don't even try it - bad idea).

Typically you do this by re-rendering the new or edit view. When you have "embedded" the form somewhere else this can get kind of messy as your ChildrenController now has to setup all the preconditions for some other controllers view.

class ChildrenController < ApplicationController

  # POST /parents/1/children
  def create
    @parent = Parent.find(params[:parent_id])
    @child = @parent.children.new(child_params)
    if @child.save
      redirect_to @parent
    else
      # these are the preconditions required to render `parents/new`
      @foo = bar
      render template: 'parents/show'
    end
  end
end

This isn't great as it results in duplication and that ChildrenController now is also responsible for showing the parent.

It also often results in a far from ideal user experience. The solution to the problem is to instead send an XHR (ajax) request to create the child record without ever leaving the page. That way your ChildController just has to be concerned with creating the resource or just re-rendering the form if you're using Turbo/Rails UJS.

class ChildrenController < ApplicationController
  # POST /parents/1/children
  def create
    @parent = Parent.find(params[:parent_id])
    @child = @parent.children.new(child_params)
    if @child.save
      redirect_to @parent
    else
      render :new
    end
  end
end
# this replaces the turbo frame that you would have defined in the parents/show view.
# app/child/new.turbo_stream
<%= turbo_stream.update @child do %>
  <%= render "form", child: @child %>
<% end %>

This isn't a complete example and there are many ways of implementing it. I would recommend you look for tutorials for Rails CRUD with Turbo if you're on Rails 7.

Upvotes: 2

Related Questions