Error: Form responses must redirect to another location - ruby-on-rails-7

I need to render an html code I receive from an API.
In Rails 6 : I was doing this in my controller, and it was working fine. I called the webservice I received the response, and I was redirected to the code generated by the render. Fine !
class GatewayController < ApplicationController
def new
init_gateway_call
end
def create
call_gateway
render_gateway_response
end
private
...
def render_gateway_response
render(html: #gateway_response.message.html_safe)
end
end
new.html.erb :
<%= form_with url: gateway_path, local: true do |f| %>
...
<% end %>
And no : create.html.erb
** Rails 7 **
I call the webservice. I get the answer but my page idle and I get this error.
Error: Form responses must redirect to another location at FormSubmission.requestSucceededWithResponse (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1614)
at FetchRequest.receive (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1390)
at FetchRequest.perform (application-0f0c10fb8f5683e32fc53a93a8a323c328de61682ca16fb65a6a2b8a3ba5d087.js:1374)
So far, I tried:
# GatewayController
respond_to :create, format: :html, gateway_response: #gateway_response.message.html_safe
<%= gateway_response %>
Without success ... Do you have any idea? Otherwise it is going to be a long weekend ^^

I figured it out you while posting my question. The error message seems like a Turbo error. I had to had data-turbo false to my form.
<%= form_with url: gateway_path, local: true, data: { turbo: false } do |f| %>
...
<% end %>
And keep my controller like it was.
render(html: #gateway_response.message.html_safe)
Happy upgrade anyone

Setting data: {turbo: false} will cause the page to reload entirely. This takes away the entire point of turbo which is to reduce page reloads.
The reason the error occurs is because Turbo expects a 303 redirect response. The solution is to have the server respond with 422 or 500 status code when you are not redirecting.
if save
redirect_to root_path
else
render :new, status: 422
You can read about this here: https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission
Of course, if you want the page to reload, you can use data-turbo: false

Thanks! I'm also developing on Rails 7 and data: { turbo: false } fixed my issue.

Instead of using turbo: false as suggested on the answer you need to add appropriate status like :see_other for redirects and 422 (unprocessible_entity) in case render.
render :new, status: 422
redirect_to xyz_path, status: :see_other

Related

Rspec test cookie value always nil

guys! I'm facing a problem (rails 7.0.4) I need to test a controller's method called set_locale. It sets a cookie[:locale] with one of these values: :en, :pt-BR, sets the locale with cookies[:locale] value and then redirects to the current page translated with the chosen language. Everything is working fine when using the app but I can't write a good test. This is the LocalesController:
class LocalesController < ApplicationController
before_action :set_locale
def default_locale_option
cookies.delete(:locale)
cookies.permanent[:locale] = I18n.default_locale # save cookies with default language
end
def set_locale
if params[:locale].present?
if params[:locale] == 'default'
default_locale_option
else
cookies.permanent[:locale] = params[:locale] # save cookies
end
end
locale = cookies[:locale]&.to_sym # this reads cookies
if I18n.available_locales.include?(locale)
I18n.locale = locale # use cookies locale
redirect_to request.referrer # to the same page
end
end
end
This is the test that I wrote. I'm trying to pass the value :en but it says : "expected: :en got: nil". I'm new in testing stuff...any idea?
require 'rails_helper'
RSpec.describe "Locales", type: :request do
describe "GET /set_locale" do
it "returns http found" do
get set_locale_path
expect(response).to have_http_status(:found)
end
end
describe "check language switch" do
it "should change the language from pt_BR to English" do
#pt-Br is the default language in I18n
get set_locale_path, params: {locale: :en}
expect(cookies[:locale]&.to_sym).to eq(:en)
end
end
end
Thanks in advance!

Rails 7 redirect error: An import map is added after module script load was triggered

I have a Rails 7 app. One of my controller actions (logout action), redirects the user to the root_url.
For some reason, upon redirect, I see following error on Chrome's console and the one button (login button) on that page stops working.
turbo.es2017-esm.js:2407 An import map is added after module script load was triggered.
If I expand the error, following is the stack trace:
turbo.es2017-esm.js:2407 An import map is added after module script load was triggered.
assignNewBody # turbo.es2017-esm.js:2407
(anonymous) # turbo.es2017-esm.js:2369
preservingPermanentElements # turbo.es2017-esm.js:961
preservingPermanentElements # turbo.es2017-esm.js:1039
replaceBody # turbo.es2017-esm.js:2367
render # turbo.es2017-esm.js:2342
renderSnapshot # turbo.es2017-esm.js:892
render # turbo.es2017-esm.js:862
renderPage # turbo.es2017-esm.js:2483
(anonymous) # turbo.es2017-esm.js:1517
render # turbo.es2017-esm.js:1682
await in render (async)
loadResponse # turbo.es2017-esm.js:1512
visitRequestCompleted # turbo.es2017-esm.js:1724
recordResponse # turbo.es2017-esm.js:1498
simulateRequest # turbo.es2017-esm.js:1485
issueRequest # turbo.es2017-esm.js:1475
visitStarted # turbo.es2017-esm.js:1710
start # turbo.es2017-esm.js:1436
startVisit # turbo.es2017-esm.js:2055
visitProposedToLocation # turbo.es2017-esm.js:1706
visitProposedToLocation # turbo.es2017-esm.js:2638
proposeVisit # turbo.es2017-esm.js:2045
formSubmissionSucceededWithResponse # turbo.es2017-esm.js:2096
await in formSubmissionSucceededWithResponse (async)
requestSucceededWithResponse # turbo.es2017-esm.js:685
receive # turbo.es2017-esm.js:450
perform # turbo.es2017-esm.js:431
await in perform (async)
start # turbo.es2017-esm.js:644
submitForm # turbo.es2017-esm.js:2060
formSubmitted # turbo.es2017-esm.js:2662
Q.submitBubbled # turbo.es2017-esm.js:1826
document.body.replaceWith(this.newElement); is the line of code where the error happened:
assignNewBody() {
if (document.body && this.newElement instanceof HTMLBodyElement) {
document.body.replaceWith(this.newElement);
}
else {
document.documentElement.appendChild(this.newElement);
}
}
Controller action:
def destroy
log_out
respond_to do |format|
format.html {redirect_to root_url, status: 303}
format.json {render json: {} }
end
end
If I refresh the page, the error goes away and the button starts working again.
This error can happen if you are accidentally including the import map (<%= javascript_importmap_tags %>) twice in your page.
I had following line of code in my application.html.erb which subscribed to updates to the user model:
<%= turbo_stream_from current_user, "counter" %>
I wrapped this under the condition that current_user is not nil (current_user is nil after logout action):
<% if logged_in? %>
<%= turbo_stream_from current_user, "counter" %>
<% end %>
Now the error is gone and the login button does not break.
I still don't quite understand the root cause.

play framework server-side redirect on unauthorized async request

I am using Play Framework / Reactjs in a single page application.
The authentication is built using ActionBuilders, such that any async request made from the browser is protected by the "AuthorizedAction".
Here is the implementation of the ActionBuiler's invokeBlock:
override def invokeBlock[A](request: Request[A], block: (AuthorizedRequest[A]) ⇒ Future[Result]): Future[Result] = {
val maybeToken = request.headers.get(AuthTokenHeader).orElse(request.getQueryString(AuthTokenUrlKey))
maybeToken match {
case Some(token) => cache.get[Long](token) match {
case Some(u_id) =>
val user = User.findByID(u_id).get
val req = AuthorizedRequest(request, user)
block(req)
case None => Future.successful(Unauthorized) // REDIRECT HERE
}
case None => Future.successful(Unauthorized) // REDIRECT HERE
}
}
This is working great, with one exception: I would like instead of simply returning Unauthorized to also redirect to the login page. Because as of now, if the token times out, and the user causes an async request to be fired, it will simply error out in the console, and the page will look broken.
method 1:
replace the two Future.successful lines above with:
Future.successful(Redirect("/login"))
this actually does call the controller for the /login route, however the URL does not change. Instead, the response of the async request that was fired contains the data =
`<!DOCTYPE html ...
Here's an example of async request whose response.data contains the screenshot text below:
axios('/ping')
.then((response) => {
let userID = response.data; // contains junk
store.dispatch(SomeAction.xyz(userID))
})
.catch((error) => {
browserHistory.push('/login');
})
Looks like I'm missing something crucial here. Any ideas how to proceed from here on?
method 2:
on the front-end, use React router's browserHistory.push('/login')
in all the async requests' .catch()
Though I would like to avoid this as much as possible, if method 1 can successfully work.
Thanks
Turns out this has less to do with Play Framework and more with HTTP codes/responses and how they are interpreted by the browser.
It seems we should not return Redirects to async requests, as they would cause the issue of method 1 in the question.
Instead, people have been suggesting one of the following:
return Ok/200 with some specific header indicating to the front-end that a redirect must take place
return some custom error code, e.g. Status(xyz) that the front-end code would understand and interpret as a request to redirect to some route.
Choosing the appropriate HTTP code is another topic, and from the two options above, we have three choices:
200 or custom 2xx code, interpreted as a success and handled as such in the promise
3xx custom code, may seem appropriate as we are asking for a redirect
401 or custom 4xx code. Technically, the incoming async request was Unauthorized (401), and so should be an error level response.
The crucial part is that handling the response code seems like it will have to be at the front-end, along the lines of method 2 in the question.
I'm leaning towards the default 401

Error: Your session expired due to inactivity. ATG REST API session confirmation Number Missmatch

I am working on ATG portal using REST API, all ATG API's are tested using PostMan.
It is giving an error when I started working in JS. below is the Test Code:
http({
method : "GET",
url : "http://IP:PORT/rest/model/atg/rest/SessionConfirmationActor/getSessionConfirmationNumber"
}).then(function mySucces(response){
// localStorage.setItem("getSessionConfirmationNumer", response.data.sessionConfirmationNumber);
// localStorage.getItem("getSessionConfirmationNumer");
http({
method : "GET",
url : "http://IP:PORT/rest/model/atg/userprofiling/ProfileActor/login?_dynSessConf="+response.data.sessionConfirmationNumber+"&login=atgcust1&password=atgcust1",
// url : "http://IP:PORT/rest/model/atg/userprofiling/ProfileActor/login",
// data:formData
}).then(function mySucces(response){
console.log("done");
},function errorCallback(response) {
console.log(response);
});
});
OutPut from Console:
angular.js:10722 GET http://IP:PORT/rest/model/atg/userprofiling/ProfileActor/login?_dynSessConf=9030570900570011195&login=atgcust1&password=atgcust1 409 (Conflict)(anonymous function) # angular.js:10722p # angular.js:10515g # angular.js:10222(anonymous function) # angular.js:14745n.$eval # angular.js:15989n.$digest # angular.js:15800n.$apply # angular.js:16097(anonymous function) # angular.js:23554n.event.dispatch # jQuery-2.1.4.min.js:3r.handle # jQuery-2.1.4.min.js:3
functions.js:75
Object {data: "Your session expired due to inactivity.", status: 409, config: Object, statusText: "Conflict"}
Session Confirmation Number Can be Accessible only once after that it will give 500 internal server error (This Logic I have written not included here),
Login is working when I get session confirmation Number manually from browser and giving that as _dynSessConf value manually in the code
Please help.
Some JSON libraries like org.json have a problem parsing large longs.
They yield a slightly different value for large longs which is exactly happend to my code,
SessionConfirmationNumber Returning Via JSON as Data type of Long and I was getting rounded value as the SessionConfirmationNumber was large.
I have used xmlHttp request for solving this issue.
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", "http://IP:PORT/rest/model/atg/rest/SessionConfirmationActor/getSessionConfirmationNumber", false );
xmlHttp.send();
console.log(xmlHttp.responseText);
Thank God :)

Adding devise confirmable module within web api or any other technique for same functionality?

I would like to add devise confirmable functionality to web service, I would like to know steps to integrate it, code in registration_controller.rb :-
class Api::RegistrationsController < Devise::RegistrationsController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.sucess true
j.count 1
j.type "userObject"
j.message "signed up successfully."
j.data user, :email, :role, :authentication_token, :activation_state
end,
status: 201
)
return
else
warden.custom_failure!
render :json=> user.errors, :status=>422
end
end
protected
def user_params
params.require(:user).permit!
#params[:user].permit(:email, :password, :password_confirmation)
end
end

Resources