I am mostly familiar with a full JavaScript stack and ever since I have been working in Ruby on Rails, I was never fully confident with the way I make XHR requests, hence, I want to validate my understanding. I looked at the following questions
Ajax request throws 500 error, but the request works
Processing after the request was completed
Rails 3: How to "redirect_to" in Ajax call?
but didn't quite answer my issue.
Here is what is happening from my understanding. In Rails we have our MVC, and the Controller is essentially where we control what the route will render. It can also be designed as an API, for doing things like CRUD.
Next, on our front end, for the sake of simplicity let's use jQuery. We want to make an AJAX request and handle the response.
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.done(function() {
// if it works, refresh the page
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
Suppose the Controller for this is very thick, so the response takes up to a couple seconds.
After going through about 100 lines or so... the problem then happens with the response.
If it fails, it alerts the user and after refresh it still processes as if everything was okay. To fix that, I believe I have to add better validation at the top of the controller action.
Next, the problem is that since this a post request, I don't can't figure out where to REDIRECT? If the request is succesful, I want it refresh, but do I do that from the client or the server? If I don't add a render :nothing => true at the end of the POST Controller Action I get...
ActionView::MissingTemplate - Missing template sandwich/create_sandwich, application/create_sandwich
but if I do, then is there a point of doing it in the JavaScript?
Well, in my controller, I typically do something along the lines of:
class FooController < ApplicationController
def create
... do some stuff
render partial: 'some_partial', locals: {if: :needed}
end
end
And then in the javascript, something like:
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.success(function(data) {
$('#someSelector').html(data)
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
So, the controller returns some HTML and the js inserts that HTML into the page in the location defined by $('#someSelector').
If my controller method really takes a lot of time, then I would typically do something along the lines of:
class FooController < ApplicationController
def create
render json: {job_id: FooCreateService.call(some: params[:thing])}, status: :ok
end
end
And then my ajax call might look something like:
$.ajax({
url: '/new-sandwich/'+food_id,
type: 'post',
data: { food_id: food_id}
})
.done(function(data) {
#.$jobId = data['job_id']
#showAWorkingOnItNotification()
#pollForResults()
})
.fail(function(e) {
// if it works alert the user and refresh the page
alert(e.responseText)
return location.reload();
})
My #pollForResults function would use #.$jobId to poll a background job service for completion, and then respond appropriately when the job is done (successfully or unsuccessfully).
It is, naturally, a bit more complicated than all of that. But, that's the direction I usually head.
Related
I would like a method on a button to execute a post redirect, and then have the page redirected by rails according to the controller's action.
Now I have the following method attached to a button on an angular page running in the page's Angular controller:
$scope.addClass = function(class_id_string) {
let payload = {class_id: class_id_string};
$http({
method: 'POST',
url: ADD_CLASS_ACCOUNT_URL,
data: payload
});
}
In my Rails controller, I have the following code:
def save_class_account
class_id = params[:class_id]
return redirect_to display_chart_path unless class_id.nil?
[...]
end
I have confirmed that the controller method is being called, and that the class_id parameter is coming through in the params collection. But still, nothing happens on the page when this method is called. I assume what is actually happening is that the entire page is being returned to the $http post method and ignored, instead of redirecting the browser, but I am not sure what is really going on here. But the page doesn't redirect as desired.
How can I get the redirect to happen?
Angular is using Ajax calls using $http service. It means update a web page/Send data to server without reloading the page.
When you post data using HTTP Service, it will call Rails Controller methods and do the action and send the response back to Ajax.
Here page redirection doesn't work from Rails Controller. If you want to redirect page, Please do as shown below in the $http service itself.
$scope.addClass = function(class_id_string) {
let payload = {class_id: class_id_string};
$http({
method: 'POST',
url: ADD_CLASS_ACCOUNT_URL,
data: payload
}).then(function mySuccess(response) {
window.location = "Paste Redirection URL here"
}, function myError(response) {
"Show Error message here"
});
}
And also in the Rails Controller, When you call methods from Ajax, It will send JSON response as shown below
def save_class_account
class_id = params[:class_id]
render json: {url: display_chart_path, mgs: 'Successfully created'}, status: :created}
end
In my Rails app, stringified JSON form input is passed to a controller via AJAX - on success, the user is to be redirected to a summary page, but the AJAX redirect doesn't seem to be working...
$.ajax({
url: "report/submission",
type: "POST",
beforeSend: function(xhr) {xhr.setRequestHeader("X-CSRF-Token", $("meta[name='csrf-token']").attr("content"))},
data: {"report" : reportParameter},
success: function(response) {
window.location.href = "/report/summary";
}
});
and the associated controller
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#newReportIDArray = Array.new
#incomingReport.each do |x|
hash = ActionController::Parameters.new(x)
#new_report = Report.new(report_params(hash))
#new_report.save
end
end
Everything else seems to work just fine - the data is entered, but the redirect does not trigger. I've searched all around and it looks like this is the syntax that everyone says to use, but it doesn't seem to work for me. I'm sure that I am doing something wrong, but I'm not sure what.
Editing to clarify problem/solution
During a chat with #Jonathan and #Kumar, I noted that window.open("/report/summary") did work correctly - #Jonathan suggested that I just try console.log(window.location) before the ajax call, and to my surprise, the script from a function from elsewhere in my app was logged. Big shocker now - THE FUNCTION WAS CALLED location()!!! Renaming the function and then restarting the app in a new window solved the problem. Learn from my mistake, kids - don't name a function location().
Ruby isn't my first language but it doesn't look like you're sending a response back. Try returning something or putsing. Look up how to do that with rails, a proper response. Maybe render json: [success: 200] or something like that. Maybe it's irrelevant. In any case, if it's not working try changing success for complete and log out the response to debug. The complete will always fire, but success won't always.
Try this:
respond_to do |format|
format.json do
render json: {
success: 200
}.to_json
end
end
In your AJAX setup, add "datatype": "json".
You could improve the response to conditionally send a failure like success: 500 if something went wrong.
You don't really need respond_to block here because you're always expecting JSON, but that's the kind of format that's often used in Rails if not mistaken.
If that doesn't work just use the render json: part as that is definitely a return.
Update
Further from our discussion it turns out that after making a robust Ajax call and tweaking the action, the final hurdle was a window.location that was not working. The cause of the problem was that location had been rebound to another function. All that needed to be done in the end is to rename that custom function and Bob's your uncle.
Add a datatype
$.ajax({
url: "report/submission",
type: "POST",
dataType: 'json', #Add json data type, as we'll render json from controller
beforeSend: function(xhr) {xhr.setRequestHeader("X-CSRF-Token", $("meta[name='csrf-token']").attr("content"))},
data: {"report" : reportParameter},
success: function(response) {
console.log("Response is ", response);
//When we get 200, this function should execute
window.location.href = "/report/summary";
},
error: function(error){
console.log("Error is ", error);
}
});
And in the controller
def submission
#incomingReport = ActiveSupport::JSON.decode(params[:report])
#newReportIDArray = Array.new
#incomingReport.each do |x|
hash = ActionController::Parameters.new(x)
#new_report = Report.new(report_params(hash))
#new_report.save
end
respond_to do |format|
format.json { head :ok } #This will return 200 status back to ajax call
end
end
I am making an application where each text box on a page has a "translate" button next to it. When the user clicks on this button, the text in the box would translate to english, via a function I would define in that page's controller. (If the text is already english, the button would not show up)
Where would I start going about how to do this? There are several rails/ajax tutorials on the web. Are there any that go about solving a use-case semi-similar to mine? I'm contacting the server for translating the text, so is this even an ajax request anymore?
I'm contacting the server for translating the text, so is this even an
ajax request anymore?
Yep.
Ajax is basically just a technology which allows Javascript to send requests on your behalf. It stands for Asynchronous Javascript And XML - basically meaning it can send data / requests "behind the scenes"
When you say your function needs to send a request to the server, you're going to be using ajax to do that, unless you want to reload the page.
--
Button
The way you'd handle the translation is as follows:
#app/assets/javascripts/application.js
$(document).on("click", ".translate", function(){
var text = $(".text_element).val();
$.ajax({
url: "pages/translate",
data: {text: text},
success: function(data){ }
});
});
This will allow you to send the request you require to the server, of which you can then process the response.
--
Controller
#config/routes.rb
resources :controller do
collection do
get :translate
end
end
To do this, you'll be best using the respond_to block in your controller:
#app/controllers/your_controller.rb
Class YourController < ApplicationController
respond_to :js, :json, only: :translate
def translate
respond_with ...
end
end
I'm having trouble making a cross-domain request from my shopify site to my rails app, which is installed as a shopify app. The problem, as stated in the title, is that my server warns me that it Can't verify CSRF token authenticity I'm making the request from a form returned by my rails app, which includes the relevant CSRF token. The request is done with jQuery's ajax method, and the preflight OPTIONS request is being handled by rack-cors.
I've included the X-CSRF-Token in my headers as was suggested in this answer. My post request is being made from a form, so my question is not answered here. The options request (mentioned in this question) is indeed being handled, as I just confirmed by asking this question. I've been stuck on this for a while, and have done a bit of reading.
I'm going to try walking through the process code-snippet by code-snippet, and maybe by the time I finish writing this post I will have discovered the answer to my problem (if that happens, then you won't ever get a chance to read this paragraph).
Here are the new and create methods from my controller.
class AustraliaPostApiConnectionsController < ApplicationController
# GET /australia_post_api_connections/new
# GET /australia_post_api_connections/new.json
def new
# initializing variables
respond_to do |format|
puts "---------------About to format--------------------"
format.html { render layout: false } # new.html.erb
format.json { render json: #australia_post_api_connection }
end
end
# POST /australia_post_api_connections
# POST /australia_post_api_connections.json
def create
#australia_post_api_connection = AustraliaPostApiConnection.new(params[:australia_post_api_connection])
respond_to do |format|
if #australia_post_api_connection.save
format.js { render layout: false }
else
format.js { render layout: false }
end
end
end
end
(I wonder about the respond_to block in the create method, but I don't think that would cause the CSRF token to fail verification.)
Within my app, at /AUSController/index, I have an ajaxified GET request that brings back the form from /AUSController/new. My goal is to be able to make all the same calls from a cross-domain origin as I can from within my app. Right now the GET request works for both, and so I will neglect to include the 'new' form. When the HTML is finally rendered, the form element has the following:
<form method="post" id="new_australia_post_api_connection" data-remote="true" class="new_australia_post_api_connection" action="http://localhost:3000/australia_post_api_connections" accept-charset="UTF-8">
<!-- a bunch more fields here -->
<div class="field hidden">
<input type="hidden" value="the_csrf_token" name="authenticity_token" id="tokentag">
</div>
</div>
</div>
</form>
The CSRF token is generated by a call to form_authenticity_token as detailed in one of the references mentioned above.
The next step is done differently in the two cases:
My app successfully returns the new form to the shop upon an ajax request. I've tested this within the app, that is by making an ajax call to /controller/new from /controller/index, and then submitting the form. This works like a charm. The js that is returned from a successful POST within my app is as follows:
/ this is rendered when someone hits "calculate" and whenever the country select changes
:plain
$("#shipping-prices").html("#{escape_javascript(render(:partial => 'calculations', :object => #australia_post_api_connection))}")
Which renders the following partial,
= form_tag "/shipping_calculations", :method => "get" do
= label_tag :shipping_type
%br
- #service_list.each_with_index do |service, index|
- checked = true if index == 0
= radio_button_tag(:shipping_type, service[:code], checked)
= label_tag(:"shipping_type_#{service[:code]}", service[:name])
= " -- $#{service[:price]}"
%br
When I call it from the same domain, request.header contains the following:
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{
"session_id"=>"db90f199f65554c70a6922d3bd2b7e61",
"return_to"=>"/",
"_csrf_token"=>"the_token_I_expect=",
"shopify"=>#<ShopifyAPI::Session:0x000000063083c8 #url="some-shop.myshopify.com", #token="some_token">
}
And the HTML is rendered and displayed nicely.
From the cross domain source, however, things are understandibly more complicated. This is where CORS and CSRF tokens and routes and all these little details start creeping in. In particular, when I make the ajax call I use the following script (which does not live in my rails app, it lives on the cross-domain server). The action of this ajax request is attached to the submit button by the callback function from the GET request, and I've included the GET request for the sake of completion.
<script>
var host = "http://localhost:3000/"
var action = "australia_post_api_connections"
console.log("start")
$.ajax({
url: host + action,
type: "GET",
data: { weight: 20 },
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function(data) {
console.log("success");
$('#shipping-calculator').html(data);
$('#new_australia_post_api_connection')
.attr("action", host + action);
$('.error').hide();
$(".actions > input").click(function() {
console.log("click")
// validate and process form here
$('.error').hide();
var to_postcode = $("input#australia_post_api_connection_to_postcode").val();
// client side validation
if (to_postcode === "") {
$("#postcode > .error").show();
$("input#australia_post_api_connection_to_postcode").focus();
return false;
}
tokentag = $('#tokentag').val()
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
// included based on an SO answer
$.ajaxSetup({
beforeSend: function(xhr) {
xhr.setRequestHeader('X-CSRF-TOKEN', tokentag);
}
});
$.ajax({
type: "POST",
url: host + action,
data: dataHash,
success: function(data) {
$('#shipping-prices').html(data);
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
return false;
});
}
}).fail(function() { console.log("fail") })
.always(function() { console.log("always") })
.complete(function() { console.log("complete") });
$(function() {
});
</script>
However, when I call it from this remote location (the distant slopes of Shopify), I find the following in my request headers,
HTTP_X_CSRF_TOKEN
the_token_I_expect=
rack.session
{ }
And I receive a very unpleasant NetworkError: 500 Internal Server Error rather than the 200 OK! that I would like... On the server side we find the logs complaining that,
Started POST "/australia_post_api_connections" for 127.0.0.1 at 2013-01-08 19:20:25 -0800
Processing by AustraliaPostApiConnectionsController#create as */*
Parameters: {"weight"=>"20", "to_postcode"=>"3000", "from_postcode"=>"3000", "country_code"=>"AUS", "height"=>"16", "width"=>"16", "length"=>"16", "authenticity_token"=>"the_token_I_expect="}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 6350ms
AustraliaPostApiConnection::InvalidError (["From postcode can't be blank", "The following errors were returned by the Australia Post API", "Please enter Country code.", "Length can't be blank", "Length is not a number", "Height can't be blank", "Height is not a number", "Width can't be blank", "Width is not a number", "Weight can't be blank", "Weight is not a number"]):
app/models/australia_post_api_connection.rb:78:in `save'
The lack of a rack.session seems suspicious like the cause of my misery... but I haven't been able to find a satisfying answer.
Finally I have seen fit to include my rack-cors setup, in case it is useful.
# configuration for allowing some servers to access the aus api connection
config.middleware.use Rack::Cors do
allow do
origins 'some-shop.myshopify.com'
resource '/australia_post_api_connections',
:headers => ['Origin', 'Accept', 'Content-Type', 'X-CSRF-Token'],
:methods => [:get, :post]
end
end
Thank you so much for even reading all of this. I hope the answer has to do with that empty rack.session. That would be satisfying, at least.
Well one of my coworkers figured it out. The problem was, the has I was sending didn't have the same structure as the hash I was expecting in my controller.
In my controller I instantiate a new API connection as follows,
AustraliaPostApiConnection.new(params[:australia_post_api_connection])
I am looking for params[:australia_post_api_connection], but there is no such index in my data hash, which looks like,
var dataHash = {
to_postcode: to_postcode,
authenticity_token: tokentag // included based on an SO answer
}
To fix this I changed the JS file to contain,
var dataHash = {
to_postcode: to_postcode,
}
var params = {
australia_post_api_connection: dataHash,
authenticity_token: tokentag // included based on an SO answer
}
And now it works! Thanks co-worker!
I have a simple scenario where I want to request a page. The request format is AJAX. If there is some error in the controller/action logic for that request, I want to redirect to an error page. The issue is that a redirect is not a JavaScript response type, so I am not sure whether it will work.
If there are no errors, then I want the page to be updated via the appropriate JavaScript response type.
What is best practice to achieve redirect responses given that the request format is AJAX?
This blog post enlightened me on what I think is the right way to do this, if your ajax response is ajax; at least, in the unobtrusive javascript paradigm. In essense, the ajax call always returns a standard json package, which can be parsed for information payload or a redirect url.
You can also put this in your ApplicationController to redirect properly for AJAX requests:
class ApplicationController < ActionController::Base
# Allows redirecting for AJAX calls as well as normal calls
def redirect_to(options = {}, response_status = {})
if request.xhr?
render(:update) {|page| page.redirect_to(options)}
else
super(options, response_status)
end
end
end
If you use jQuery, you can use .ajaxError()
$(document).ajaxError(function(event, request, settings){
location.href = '/error.html';
});
or assume you do a ajax post
var jqxhr = $.post("example.php", function() {
// Do what it completed will do
})
.error(function() {
location.href = '/error.html';
})