DaveR
DaveR

Reputation: 2483

Rendering a view after form submission not working correctly in Rails 7

I have set up a tableless model as described in the Rails 7 guides:

class ContactForm
    include ActiveModel::Model
  
    attr_accessor :name, :email, :message
    validates :name, :email, :message, presence: true

end

I have set up an action to check if the submitted content is valid and send the email if it is:

  def contact_process 
    @contact_form = ContactForm.new(contact_form_params)
    
    if @contact_form.valid?
      UserMailer.with(@contact_form).contact_form.deliver_later 
      redirect_to contact_path
    else  
      render :contact
    end 
  end 

When there are errors and the contact template is rendered again @contact_form seems to be a blank ContactForm instance and for example @contact_form.errors.count returns 0 even though it was printing the correct number in the console just before the render command.

Upvotes: 7

Views: 2684

Answers (2)

DaveR
DaveR

Reputation: 2483

The problem was due to trying to render a page following a form submission with a 200 rather than a 4xx or 5xx code. A 422 code can be added as follows and is recommended when there's a problem with a model:

render 'contact', status: :unprocessable_entity

Rails 7 ships with Turbo. It expects a form submission to be followed by a 303 code redirect, one exception to this being when you need to render validation errors. Here is how it handles form submission, and why a render command on its own isn't going to work and you're probably going to just get some version of the previous page:

Turbo Drive handles form submissions in a manner similar to link clicks. The key difference is that form submissions can issue stateful requests using the HTTP POST method, while link clicks only ever issue stateless HTTP GET requests.

After a stateful request from a form submission, Turbo Drive expects the server to return an HTTP 303 redirect response, which it will then follow and use to navigate and update the page without reloading.

The exception to this rule is when the response is rendered with either a 4xx or 5xx status code. This allows form validation errors to be rendered by having the server respond with 422 Unprocessable Entity and a broken server to display a “Something Went Wrong” screen on a 500 Internal Server Error.

The reason Turbo doesn’t allow regular rendering on 200 is that browsers have built-in behavior for dealing with reloads on POST visits where they present a “Are you sure you want to submit this form again?” dialogue that Turbo can’t replicate. Instead, Turbo will stay on the current URL upon a form submission that tries to render, rather than change it to the form action, since a reload would then issue a GET against that action URL, which may not even exist.

Upvotes: 15

Jai Kumar Rajput
Jai Kumar Rajput

Reputation: 4217

The above solution will work perfectly but in case we are using Devise above will add lots of effort because Devise uses render internally and we need to customize at everywhere.

Apart from the above, another permanent solution is here.

Create a turbo controller:

class TurboController < ApplicationController
  class Responder < ActionController::Responder
    def to_turbo_stream
      controller.render(options.merge(formats: :html))
    rescue ActionView::MissingTemplate => error
      if get?
        raise error
      elsif has_errors? && default_action
        render rendering_options.merge(formats: :html, status: :unprocessable_entity)
      else
        redirect_to navigation_location
      end
    end
  end

  self.responder = Responder
  respond_to :html, :turbo_stream
end

In initializers/devise.rb use this:

config.parent_controller = 'TurboController'

In the case of another controller, you can inherit this controller.

Rails 7 uses Turbo and stimulus framework to increase frontend performance.

You need to install turbo and stimulus.

Upvotes: 0

Related Questions