Rails controller renders JSON in browser - ruby-on-rails

I have a simple controller that I have responding to both html and json. I'm using the json response for a Backbone app. Everything works as expected, except that when I click a link that uses the show method, and then click the back button, the index method just prints a big string of JSON into the browser. If I refresh, it displays HTML as expected. Here's the controller.
class RecipesController < ApplicationController
def index
#user = User.find(params[:user_id])
#recipes = Recipe.all
respond_to do |format|
format.html
format.json { render json: Recipe.where(user_id: params[:user_id]).featured }
end
end
...
end
I tried adding a check for response.xhr?, and only rendering JSON if it was an AJAX request, but that didn't work.
Edit
This is a Rails 3 app not utilizing turbolinks.
Edit 2
Here is the relevant Backbone code.
# app/assets/javascripts/collections/recipe_list_.js.cofee
#App.Collections.RecipeList = Backbone.Collection.extend
url: ->
"/users/#{#userId}/recipes"
model: window.App.Models.Recipe
initialize: (opts) ->
#userId = opts.userId
# app/assets/javascripts/app.js.coffee
$ ->
urlAry = window.location.href.split('/')
userId = urlAry[urlAry.length - 2]
App = window.App
App.recipeList = new App.Collections.RecipeList(userId: userId)
App.recipeListView = new App.Views.RecipeListView

If you're referring to a chrome and turbolinks issue, then an easy fix is to disable caching on ajax requests:
$.ajaxSetup({cache: false})

you could try using /recipes.html
and /recipes.json
and /recipes/1.html and /recipes/1.json
instead of relying on backbone and history to always send the correct headers

I bet it's due to turbolink, or ajax based page rendering (backbone, remote=true, ...)
I always disable turbolink and keep control over which links are remote=true, and for all ajax response I insert this javascript line at the end
history.pushState(null, '', '/the/requested/url' );
If you don't want to manually implement this line for each of your link responses, you can wrap it in an ajax:complete event (more info), and I assume turbolink has an event you can use as well.
Second part of the trick is to bind popstate so when your users click on the "back" button the page will be refreshed from the server (through the url that was pushState-ed earlier) and the ajax/js/json/whatever response won't be displayed anymore.
setTimeout( function () {
$(window).bind('popstate', function () {
window.location = location.href;
});
}, 500);
As you see I wrap the popstate event binding in a setTimeout, because if you don't do that you may have trouble with some browser that would infinitely refresh the page.

Are you using Chrome? if so this is a known issue. When you hit the back button chromes serves the page from cache since what was returned was json that is what it dumps on the screen. This post has some suggested workarounds
https://code.google.com/p/chromium/issues/detail?id=108766

Related

Capybara not rendering .js.erb

Hi: I've been stuck on this test using Capybara for some days now and can't work out a solution.
Have some DOM modification to be performed using AJAX, long story short:
1) My ajax event triggers the request successfully:
$.ajax({ url: 'nice_url', data: data, complete: function() {debugger}
});
2) The controller receives the request just fine:
def update_dom
<other stuff>
respond_to do |format|
format.js
end
end
3) IMPORTANT: the file update_dom.js.erb is correctly rendered in DEV environment but NOT in the test using Capybara + RSpec.
update_dom.js.erb:
debugger; //first line, breakopint no stopp in Test
<% unless %W(boolean datetime).include?(#operators[:type])%>
$.each($('.values'), function(i, valueField){
<other stuff, etc...>
This is actually the whole issue and will appreciate any thoughts on it
I had an error on my *.js.erb file which was not being reflected on the developers' console stack trace. I was able to debug it using following guide (used plain firefox instead of firebug but still worked): solution

Rails controller - execute action only if the a Rails UJS method inside succeed (mutually dependent methods)

Following another question (Rails controller - execute action only if the two methods inside succeed (mutually dependent methods)), I would like to ensure that inside one of my controller's action, if the user does not see the message displayed by a Rails UJS method, then the first methods of the controller action are not implemented either.
CONTEXT
I have a Controller with a method called 'actions'. When the app goes inside the method 'example_action', it implements a first method (1) update_user_table and then (2) another update_userdeal_table. (both will read and write database) and then (3) a third method which is related to a Rails UJS(ajax) call.
My issue is the following: in case of timeout in the middle of the controller, I want to avoid the case where the User table (via method 1) is updated, the UserDeal table is updated (via method 2) but NOT the thrid method i.e the ajax request that displays a message FAILS (error, timeout,...status like 500 or 404 or canceled or timeout...).
In my app, for mobile users if they're in a subway with internet connection, they launch the request that goes through 'example_action' controller, performs successfully the first method (1) and second method (2) but then they enter a tunnel for 60 seconds with very very low (<5b/sec) or NO internet connection, so for UX reasons, I timeout the request and display to the user 'sorry it took too long, try again'. The problem is that if I could not show to them the result (3), I need to be able to not execute (1) and(2).
I need the two methods (1) and(2) and(3) to be "mutually dependent": if one does not succeed, the other one should not be performed. It's the best way I can describe it.
Today Here is my code. It's not working as I am manually testing by clicking and then after just 2 seconds I disconnect the internet connection. I see in my database that (1) and(2) were performed and the databases were updated but I saw the message 'sorry it took too long, try again'.
Is that the right approach ? if yes how to do this?
If not, should I try a different angle like: if (1) and(2) were successful but not(3) should I store the fact the rails UJS xhr status was an error or timeout, that consequently the modal wxas not effectively displayed to the user and then show to them the result/message once they get back online?
Here is the code
html page for the user
the user click on a button that triggers a Rails UJS aajax request that will display ultimately the modal message
<div id=zone">
<%= link_to image_tag(smallest_src_request),
deal_modal_path,
remote: true %>
</div>
This send to a route that points to this controller action
Deal controller
class DealsController < ApplicationController
def deal_modal
Deal.transaction do
update_user_table # that's the (1)
update_userdeal_table # that's the (2)
# show_modal_message
respond_to do |format|
format.js
end
end
private
def update_user_table
# update the table User so it needs to connect to internet and acces the distant User table
end
def update_userdeal_table
# update the table UserDeal table so it needs to connect to internet and access the distant UserDeal table
end
end
This points to a js.erb view file
deal_modal.js.erb
showModalMessage("Here is your result <variable and all>);
To manage the ajax, error, timeouts... (if necessary to the resolution of the question), I use Rails UJS settings.
IMPORTANT: It is here that in case of error or timeout, I send the error / timeout modal message that comes in place of the one you normally get (see just above "Here is your result..")
$(document).on('page:change', function () {
$("#zone").
on('ajax:error',function(event,xhr, status, error){
console.log(' ajax call failed:', error);
var msg;
msg = Messenger().post({
hideAfter: 4,
message: "sorry it took too long, try again."
});
});
$(document).on('page:change', function () {
//set timeout Rails UJS ajax option that will display message for ajax:error cases defined above
$.rails.ajax = function(options) {
if (!options.timeout) {
options.timeout = 5000;
}
return $.ajax(options);
};
});
So the transaction will only rollback if an error is thrown. If an unhandled error is thrown, your application will crash and it will show a 500 error in some way.
In order to display the response to the user, on success or error, you will need to render something. So you don't want to prevent the respond_to block from executing. One way to handle this would be to set a flag via an instance variable.
def deal_modal
begin
Deal.transaction do
update_user_table
update_userdeal_table
end
#success = true
rescue
#success = false
end
# show_modal_message
respond_to do |format|
format.js
end
end
Then in deal_modal.js.erb
<% if #success %>
showModalMessage("Here is your result <variable and all>");
<% else %>
showModalMessage("There was a problem");
<% end %>
EDIT:
Dealing with connection issues is definitely tricky and there isn't really one ideal solution. I would generally let the database continue uninterrupted and let it return either a success or failure on it's own time. For lengthy transactions, you can use a gem like delayed_job or sidekiq to process the action in the background and let the rails controller return a response saying "...pending..." or something. Unless you're using websockets on the frontend, this means continually polling the server with ajax requests to see if the background process is complete.

Redirect and then render

Okay, so real quick, I am using a file upload plugin http://plugins.krajee.com/file-input to upload my images. The plugin expects some sort of response from the server, and i want to send back an empty json object.
But when the images are uploaded, I also need to redirect immediately to another place so people can sort of make changes to the order.
Rails says I can't use render and redirect, but says i can redirect and return.
How do i redirect and return the empty json object??
def create
if !params[:images].nil?
package = Package.first
#photos = Array.new
#order = current_user.orders.new
#order.save
#order.order_items.each{|d| d.delete} #Stupid hack to prevent creation of fake order items. Don't know what is causing this yet
params["images"].each do |i|
photo = current_user.photos.create
photo.write(i.original_filename, i.read)
photo.save
#order.order_items.create(photo_id: photo.id, size_id: package.size_id, material_id: package.material_id)
end
redirect_to edit_order_path(#order) and return
else
flash[:danger] = "Please select at least one photo to upload"
redirect_to upload_photos_path
end
end
If the upload plugin you're using is expecting a JSON response and you would like to redirect after a successful upload, then you'll need to do it client side.
If you're not using Rails 4 or Turbolinks, you can simply redirect via window.location.replace. From your Rails code it looks like you're batch uploading in which case you'll want to assign a callback to the filebatchuploadsuccess event as per the docs
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
window.location.replace( '/your_path_here' );
});
If you are using Turbolinks, the above code will be exactly the same except that instead of window.location.replace, you can use Turbolinks.visit
Example:
$('#fileinputid').on('filebatchuploadsuccess', function(event, data, previewId, index) {
// files have been successfully uploaded, redirect
Turbolinks.visit( '/your_path_here' );
});

Ajax call on ruby on rails

I have a ajax call using ruby on rails. I'm getting a success but I don't know how to use the data result of the ajax call.
$.ajax({
url: "/search/get_listing?listing_id" + id,
dataType: 'JSON',
success: function(data) {
var listing = JSON.parse(data);
$("#modalPrice").html(data.city);
}
});
Controller:
#listings_data = Listings.find_by(id: params[:id])
render :json => #listings_data.to_json
Using data.city won't work. I'm expecting to get the values retrieve from the model by simply putting . on the variable
var listing = JSON.parse(data);
Still no luck. Help guys. Thanks!
JSON.parse is Ruby code, API of JSON gem. How can you guys use that in Javascript :)
jQuery can process JSON object data directly. Just use:
success: function(data) {
$("#modalPrice").html(data.city);
}
For example, you can render in the controller:
render :json => { :city => #listings_data }
On the JS:
success: function(data) {
var listing = data.city;
}
I'm having similar problems everytime I use AJAX in rails since the response seems to differ depending on how you return the value or how you are handling the success in JS. Try this:
success: function(data, status, xhr) {
var listing = JSON.parse(xhr.responseText);
$("#modalPrice").html(data.city);
}
I usually use Firebug (Firefox Plugin) to set a breakpoint in my success handlers to check the arguments where exactly the response is in. Sometimes it's in the first value, sometimes in some other and then it may be xhr.response or even xhr.responseText. It's confusing me every time.
To use Firebug for this, press F12 on your page, select the 'Script' pane and find the code you want to check. Click next to the row number of your code where you want your breakpoint. In this case, you could've chosen the var listing line. When the code is executed (after your click), the browser will stop there and you can check the passed arguments on the right side.

Detecting if this is an iframe load or direct

I am looking to only show a form if it is pulled on a page within an iframe. How do I do that? Is there a server side solution?
If you are using JQuery... (installation instructions here: http://jquery.com/ )
$(document).ready(function(){
if( window == window.top) { $('form#myform').hide(); }
});
Which just hides the form with id "myform" if the window is not the topmost window.
I can't think of purely serverside way, but you could use a bit of hybrid javascript/rails.
assuming that you have a dedicated iframe layout template e.g. 'layouts/iframe.erb'
you could put some javascript in the head to check if it is being loaded as an iframe, and if it is not, redirect to an action and maybe display a flash msg "can only load this page inside application"
The javascript/rails for the head
<script type="text/javascript">
function parentExists()
{
return (parent.location == window.location)? true : false;
};
function check_modal(){
if (parentExists()) {
window.location = '<%= url_for( :controller => "home", :action => 'iframe_action', :iframe_fail => 'true')%>'}
}
check_modal()
</script>
notice the param :iframe_fail which you could check for in a controller and do whatever you please if that param is present e.g. display flash msg or redirect
example controller
def iframe_action
if params[:iframe_fail]
flash[:notice] = 'can only load inside app'
else
#do something else
end
end
Not real pretty but might help you get the job done.
My iframe tag was like
%iframe{:height => "98%", :width => "98%",:"id" => "profileIframe"}
I wanted to hide header of my webpage within this iframe hence I used code as:
var $frame = $(window.parent.frames["profileIframe"]).contents();
$frame.find('.header-ui').hide();
If you observe then contents() returns a element as "#document", which is html of iframe, hence calling a javascript without this will try to access your actual webpage rendered in background of iframe.
You can only check it on the client side via JavaScript.
However: DO NOT DO THAT. There are plenty of legitimate uses of putting a site in a (i)frame. Breaking out of such iframe or changing your site in any way in such circumstances them will only make your users pissed unhappy.

Resources