Rails HAML View Rendering Raw HTML - ruby-on-rails

I'm trying to render a simple view using HAML that shows a collection of JSON objects with a header above. The route I'm using is get 'posts.json', to: 'posts#posts_as_json'. This renders the below view.
posts_as_json.haml
%h1 Posts As JSON
%div.gutter-spacing
=raw(#posts)
The problem I'm running into is that the page will render raw HTML tags as well as the JSON itself. I've been trying to figure out what the issue is and based on some experimentation I think it's directly tied to using .json in my routing. If I remove that it seems to render the HTML as expected. Update: This is definitely the root of the issue
Using the route 'posts.json' is a requirement of my project, but I need to be able to search the parsed JSON as well which I have previously solved with a simple HTML text input tied to a javascript function. How can I use this endpoint while still rendering HTML alongside the JSON data?

The problem is that if you request a .json format, rails will automatically try to send it as a response with the corresponding MIME type application/json.
Now the browser gets a response that he thinks should be a JSON so it renders it as text.
You can override the MIME type for your render method:
render content_type: content_type: "text/html"
Or if you want to serve both request from the same controller action, you can do it with the respond_to-method:
def index
#posts = Post.all
respond_to do |format|
format.html { render }
format.json { render 'index_as_json.html', content_type: "text/html" }
end
end

Related

respond_to works only for the first MIME type in Rails 6

Straight-forward enough problem: everything renders just fine if I remove either geojson or csv, but when both are in the block, the first one is rendered no matter what Content-Type header I send along (text/csv or vnd.geo+json). I do have custom MIME types and renderers, but these don't seem to be the problem since when I use one or the other, everything works as expected, but it is only when I try to reference multiple formats does only the first format end up being used, regardless of header.
def all
resource = Thing.where.not(thing_type: BaseThing::ONE_OF_THE_THINGS).order(:unique_id)
respond_to do |format|
format.geojson do
render geojson: Things::GeoSerializer.to_geojson(resource)
end
format.csv do
render csv: Things::CsvSerializer.to_csv(resource), filename: 'things'
end
end
end
ah; Rails expects the header to be Accept, not Content-Type.

Rails: handling errors within respond_to block (csv)

I have a search method on my controller that responds to either the html or CSV format. The html format renders the search results as expected, and I want the CSV format to work by downloading a CSV file of the results.
Most of the time, send_data is called and the CSV file is generated. However there are situations in which I don't want to generate the CSV, and instead show an error (for example when a user has used all of their allotted exports for the month). The following code is a simplified version of what I'd like to do, however this doesn't seem to like how I'm attempting to handle the error.
respond_to do |format|
format.html do
#results = ...
render "index"
end
format.csv do
#results = ...
if user_can_export?(#results)
send_data generate_csv(#results), filename: "Search-Results.csv"
else
flash[:error] = "Unable to export search results."
render "index"
end
end
end
Is there any way for me to break out of this block and render HTML or am I stuck generating a csv file here? I'd prefer to not handle this situation by sending a csv file with an error message contained in it, but that seems like my best option at the moment. Help is appreciated!
You need to set the content-type header to text/html instead of text/csv.
render template: "things/index.html.erb", content_type: "text/html"
Also if you want to display a flash message in the current request cycle you need to use flash.now[:error] = "Unable to export search results"

How to ask two queries from a single controller

I am trying to do everything on a single page. The page is called users/index.html.erb.
On this page I return a simple data base query in json format.
I would like to also return a seperate query in non json format from the same database.
I'm trying to avoid partials for the moment as they make variable handling a bit more complicated that I want for my beginner level.
This is what I have:
class UsersController < ApplicationController
def index
#users = User.including_relationships
#followas= User.find_by name: params[:name]
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
end
end
but it doesnt work unsurprisingly. How can I get it to work in one action?
Not sure about your purpose of returning two formats of query from a single controller call. It usually happens when you use Javascript heavily (eg. Angular.js). Controller first returns the HTML page including all the HTML template and javascript, then data in JSON is returned in the second round.
So in your users/index.html.erb, you might have something like:
<p ng-repeat='user in users'>{{user.name}}</p>
<script>
// do a AJAX call to retrieve users in JSON format
</script>
Then you will call users/index.json again to retrieve users information in JSON.
In short, your users/index is called twice, once for html, once for JSON.
It might seem unwise to call the same controller method twice just to render different format. In such case, you might consider to embed the JSON data in HTML. In such way, you will only render users/index.html.erb once and parse the data in JSON through Javascript on the client side.

Passing json from index action and rendering of another application layout simultaneously. Ruby on Rails

I have Vacancies controller and I need to pass #vacancies to json and also render another layout. The following code does not work (json is not passed however I have "wide" layout). If I remove format.html { render layout: "wide"} } json passes correctly. How to combine these two things?
class VacanciesController < ApplicationController
respond_to :html, :json
...
def index
#vacancies = Vacancy.all
respond_with(#vacancies) do |format|
format.html { render layout: "wide"} }
format.json { render json: #vacancies }
end
end
...
You can't call render twice, that's problem #1. You also can't send two responses to a single request.
There is also no purpose in rendering HTML (which means a fresh page load) and sending JSON (which is for AJAX requests, ie requests that don't reload the page) at the same time. It isn't possible, but it also would be pointless even if it was possible.
If you want to tell a request to use a specific layout, you can pass the layout option to a render call. However, a render call does not take a data object as the first argument, it takes a view name, or only an options hash. So to call this correctly you should use:
render :index, :layout => 'example'
I expect that will make your HTML views show up correctly.
Please understand however, the layout option is only useful for HTML responses, not JSON responses. Layout is telling your render call what outer HTML to wrap around the view your action is calling, and if you don't specify it uses 'application.html'
To help you understand one more thing: your respond block is telling the computer how to respond to different kinds of requests. It's like a switchboard. If you wrote it with if/else statements it might look like this:
if request_type == 'html'
render :index, :layout => 'wide'
elsif request_type == 'json'
render :json => #vacancies
else
raise raise ActionController::UnknownFormat
end
So, with your respond_with block, if you fix your html render call, and assuming you're developing on localhost, if you enter the following URL in your browser and hit enter...
http://localhost:3000/vacancies
That would be making an HTML format GET request, which will load the page with layout: 'wide' but no other data. If you type:
http://localhost:3000/vacancies.json
That will simulate a JSON request, and you'll get just the JSON representation of the #vacancies data.
I hope that helps you solve your problem. If not, please describe what you're trying to accomplish in more detail so I can help you understand how to do it.
PS: one last tip: if you want to specify layouts at the controller level you can just call layout at the top of your controller, like so:
class ExampleController < ApplicationController
layout 'awesome', :only => [:new,:edit]
...
end
This works like any other filter, you can pass :only, or :except, or no options at all.

When does "respond_to do |format|" make sense for Javascript?

I saw this code in a Rails controller:
respond_to do |format|
format.js {}
end
I've seen this for XML and HTML formats but not for Javascript.
Is this the way you specify a return format if you use for REST, like if you use replace_html or remote_form_for? I know RJS templates return compiled Javascript so I'm thinking maybe this is where this code might kick in.
If you put code inside the hash symbols(format.js {}), is that what gets send back as javascript to the browser?
It is used when an AJAX request is sent from the browser to a controller. The controller can respond with a script (which is generated by ruby statements in the view) which will be executed on the client.
Rails does a little magic on figuring out what 'template' to send out
in controller:
def foo
end
in view: (app/views/controller/) you can have
foo.html.erb (usual, html template)
foo.rjs (javascript template)
rails will send out the right template back to the browser, HTML for regular requets and RSJ for Ajax requests. You might want to put in javascript code like 'page.replace_html' ..etc in your RJS template. This way, you keep the controller clear of view code.
yuo can always just add the format to the url and see what it responds, /something.js would respond using the format.js code, if you want to use it, you can do the following to avoid rendering your entire layout:
format.js { render :layout => false, :text => #models.to_json }
that would respond with a json string
format.js { render :layout => false }
would require a template called [action].js.erb

Resources