[Ruby] [Rails] Why is it successful if save succeeds and redirect_to fails and render?

4 minute read

Introduction

I was worried about how to handle render when implementing forms while developing with Rails, so I was able to reconfirm the difference between redirect_to and render again during investigation, so it is left as a memo. Incidentally, I will also write about the troubles with render for a moment. (Specifically handling before_action)

Environment

・Ruby 2.6.5 ・Rails 6.0.3

Situation

I made an application to register restaurant information and set the following actions in the controller.

shops_controller.rb


 def new
    @shop = Shop.new
 end

 def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
      redirect_to :index
    else
      render :new
    end
  end

*Strong parameters are not relevant at this time, so they are omitted.

I didn’t think about it anymore, but when it fails to save, why do I use render to return to the submission form again? Let’s look back at that place.

In conclusion, I want to return to the input form again while keeping the entered contents.

So why is it possible to use render instead of redirect_to in that case? Let’s look back at the behavior of redirect_to and render.

What is # redirect_to

**redircet_to is a process that causes the client side to access the specified action (or path). ** This will allow you to access the page after revising the flow of MVC. Specifically, the flow is “Access the specified action again ⇨ Execute the controller action ⇨ Display the corresponding view”.

It does exactly the same as accessing the view of an action from a url.

What is # render

Render, on the other hand, will just display the view corresponding to the specified action without going through the **action again. ** Unlike redirect_to, it returns the view without executing the contents of the new action.

The reason why the input content is retained by # render Let’s look at the code again based on the explanation above.

shops_controller.rb


 def new
    @shop = Shop.new
 end

 def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
      redirect_to :index
    else
      render :new
    end
  end

Since the new action is a new registration page for store information, @shop = Shop.new is defined in the action. (Create an empty instance) After that, the value entered in the form is passed as params, and it is saved as it is successfully passed as the information of @shop created this time by the create action.

If saving fails (in case of else), it will be returned to the form, but if you use **redirect_to at this time, the new action will be executed again and @shop will become an empty instance. **

On the other hand, using **render does not go through the new action, so @shop will display the form view again while holding the params value received in the create action. **

haml:new.html.haml



.shop-wrapper
  = form_with model: [@owner, @shop], html: {class: "shopform"}, local: true do |f|

The above is an excerpt of the view, but the form receives @owner and @shop and displays the contents, so if @shop has contents, it will display the contents properly.

That’s why you should use render when the create action fails.

Error encountered this time

By the way, this time, when setting render, I first encountered the following error. ![Screenshot 2020-06-26 21.45.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619380/62f7b99e-a94a-86e0-1487-(db46dbfaa272.png)

This happened for a moment, but the following before_aciton was set for the new action.

shops_controller.rb



before_action :set_select_lists, only: [:new]

# Omitted

private
def set_select_lists
    @stations = Station.all.map {|station| [station.name, station.id] }.unshift(["Please choose from below", nil])
    @genres = Genre.all.map {|genre| [genre.name, genre.id] }.unshift(["Please choose from the following (required)", nil])
    @marks = Mark.all.map {|mark| [mark.favorite, mark.id] }.unshift(["Please choose from the following (required)", nil])
  end

I made a pull-down selection list in the form and set it in before_action.

As I wrote earlier, since render does not go through the new action again, before_action is not performed →There is no set value and an error occurs in view display.

For a moment, I wondered, “Isn’t it possible to use render if there is a variable that @shop has to define unexpectedly?”

Rewrite the create action as follows.

shops_controller.rb


def create
    @shop = @owner.shops.new(shop_params)
    if @shop.save
    else
      set_select_lists
      render :new
    end
  end

By performing set_select_lists which was done in before_action before rendering with create action, it is possible to render with the value also obtained (render is just creating action and then new view (Because it is displayed)

A moment? ? ? It wasn’t that complicated.

At the end

Actually, the behavior of redirect_to and render was the most troubled place in about a month after I first got into programming with Progate. When I noticed the difference here, I had an image that I deepened my understanding of MVC at the same time.

I would like to give some information to beginners like me.

*Because I am a beginner, I would be very grateful if you could point me out.