Rails: respond_to vs "case" conditional - ruby-on-rails

What is the advantage of using respond_to in Rails instead of a case statement?
I have several instance variables that I want to set same way for some formats, but not for HTML. This does not seem to work:
respond_to do |format|
format.html do
# ...
end
format.any(:csv, :xml) do
# common stuff
end
format.csv do
# ...
end
format.xml do
# ...
end
end
I think I'll end up using a couple of case request.format and not using respond_to at all:
case request.format
when 'html'
# ...
when 'csv', 'xml'
# common stuff
end
# more common stuff
case request.format
when 'html'
# render
when 'csv'
# custom render csv
when 'xml'
# render xml with a template
end
So I wonder what is a good use case for respond_to, where case request.format wouldn't look better?

respond_to is not just a way to find out what type of response the client expected but also a way to tell rails what type of response you are willing to provide.
For example a simple scenario where we have this controller:
class SimpleController < ApplicationController
def index
respond_to :html, :json
end
end
A client sends a request expecting xml response
curl -H "Accept: application/xml" \
-H "Content-Type: application/xml" \
-X GET "host:port/simple/index"
Rails will response with 406
Completed 406 Not Acceptable in 0ms (ActiveRecord: 0.0ms)
However, if you simply filter request.format using case like in your example the client will receive a 500 error because rails cannot find a corresponding template to the request format.
Of course you can also call respond_to on class level as well as specify respond format in routes.rb
Dive into rails source code and api documentation if you want to get more in-depth explanation to this.

First of all, the reason the respond_to block does not work is because you are using format.any(:csv, :xml) in conjunction with format.csv. You can only use one of these.
This should be a clear indication that you are trying to do too much in your controller. For example, if you are doing common stuff for csv and xml responses, then maybe you should create a class in your lib directory:
# lib/foo_serializer.rb
class FooSerializer
def initialize(bar)
#bar = bar
end
def do_stuff
#bar.to_s
end
end
And then call one of its method for each type of response:
respond_to do |format|
format.html do
# ...
end
format.csv do
FooSerializer.new(data).do_stuff
# ...
end
format.xml do
FooSerializer.new(data).do_stuff
# ...
end
end
The respond_to syntax:
Is part of the standard Rails Way™
Takes care of rendering the correct file
Not only looks better but is also more maintainable
You should try to stick with it.

The request.format field can be useful where the respond_to block is not appropriate.
For example, I'm using the sunspot gem for searching, and my controller looks like this:
def index
case request.format
when 'xlsx'
per_page: Person.count
when 'html'
per_page: 30
end
#search = Person.search do
with :name, params[:name]
fulltext params[:search_term]
paginate page: params[:page], per_page: per_page
end
#people = #search.results
respond_to do |format|
format.xlsx {send_file #people.to_xlsx, filename: 'people.xlsx'}
format.html
end
end

I've been working with Rails for a while, and I've always seen respond_to in this form:
respond_to do |format|
format.js
format.html # index.html.erb
format.xml { render :xml => #posts }
end
I don't know where you would've seen something with the nested do's.
EDIT:
For pagination, see
https://github.com/mislav/will_paginate
EDIT 2:
format.html { render :html => Post.paginate(:page => params[:page]) }
or something like that.

Related

Alternative method for not allowing GET requests [duplicate]

In my action I wish to only respond with processing if it was called from an AJAX request. How do I check?
I want to do something like this:
def action
#model = Model.find(params[:id])
respond_to do |format|
if (wasAJAXRequest()) #How do I do this?
format.html #action.html.erb
else
format.html {redirect_to root_url}
end
end
You can check for a header[X-Requested-With] to see if it is an AJAX request. Here is a good article on how to do it.
Here is an example:
if request.xhr?
# respond to Ajax request
else
# respond to normal request
end
If you're using :remote => true in your links or forms, you'd do:
respond_to do |format|
format.js { #Do some stuff }
You can also check before the respond_to block by calling request.xhr?.
Update:
As of Rails 6.1.0, xhr?() does actually (finally) return a boolean value.
https://github.com/rails/rails/commit/0196551e6039ca864d1eee1e01819fcae12c1dc9#diff-60b77e427ea7ba142faa477fac10b8d0134cede4e35a3b1953c425200fadf1acL267-L269
Original Answer:
The docs say that request.xhr?
Returns true if the “X-Requested-With” header contains “XMLHttpRequest”....
But BEWARE that
request.xhr?
returns numeric or nil values not BOOLEAN values as the docs say, in accordance with =~.
irb(main):004:0> /hay/ =~ 'haystack'
=> 0
irb(main):006:0> /stack/ =~ 'haystack'
=> 3
irb(main):005:0> /asfd/ =~ 'haystack'
=> nil
It's based on this:
# File actionpack/lib/action_dispatch/http/request.rb, line 220
def xml_http_request?
#env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/
end
so
env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/ => 0
The docs:
http://apidock.com/rails/v4.2.1/ActionDispatch/Request/xml_http_request%3F
I like using before_action filters. They are especially nice when you need the same filter/authorization for multiple actions.
class MyController < AuthController
before_action :require_xhr_request, only: [:action, :action_2]
def action
#model = Model.find(params[:id])
end
def action_2
# load resource(s)
end
private
def require_xhr_request
redirect_to(root_url) unless request.xhr?
end
end
request.xhr?
if this return 0 then it means its an ajax request, else it will return nil.
if you are using :remote => true in links,form, then your response would come in js form instead of HTML, JSON, etc.
def action
#model = Model.find(params[:id])
respond_to do |format|
format.js
if (wasAJAXRequest()) #How do I do this?
format.html #action.html.erb
else
format.html {redirect_to root_url}
end
end
end
you also need to create, action appropriate js file, in your case action name is action, so file name would be - action.js.erb. also shift our action code inside action.js.erb file

how to call javascript functions from the controller in rails

I'm trying to call a javascript function (actually coffeescript) from a controller in a Rails 3.2 app.
I'm getting a Render and/or redirect were called multiple times in this action error.
My code looks like this:
#Model.controller
def index
#models = Model.all
my_action if current_user.name == "Bob" #or some other general conditional
...and some stuff
respond_to do |format|
format.html
format.js #this is needed to handle ajaxified pagination
end
end
def my_action
respond_to do |format|
format.js { render :js => "my_function();" } #this is the second time format.js has been called in this controller!
end
end
#functions.js.coffee.erb
window.my_function = ->
i = xy
return something_amazing
What is the correct way to call a js function from the controller?
Man, you missed argument for block. Primary mistake.
def my_action
#respond_to do # This line should be
respond_to do |format|
format.js { render :js => "my_function();" }
end
end
And MrYoshiji's point is right. But your error was on server side, had not reached client side yet.
For the style, I think that's okay if the js code is one function call only. If more JS code, it's better to render js template
# controller
format.js
# app/views/my_controller/my_action.js.erb
my_function();
// and some more functions.
Update: How to fix double rendering problem
You must have your #index return if condition met, or the method will continue to execute and cause rendering twice or more. Fix it like this:
def index
#models = Model.all
if current_user.name == "Bob"
return my_action
else
# ...and some stuff
respond_to do |format|
format.html
format.js #this is needed to handle ajaxified pagination
end
end

set a rails controller's response type to xml

i'm quite new to rails. i'm trying to set a rails controller's response type to xml, but not having much luck. i could certainly afford to better understand how respond_to and respond_with work.
here's what my controller looks like:
class ResponsesController < ApplicationController
respond_to :xml
def index
require 'rubygems'
require 'telapi'
ix = Telapi::InboundXml.new do
Say('Hello.', :loop => 3, :voice => 'man')
Say('Hello, my name is Jane.', :voice => 'woman')
Say('Now I will not stop talking.', :loop => 0)
end
respond_with do |format|
format.xml { render }
end
puts ix.response
end
end
this leads to an http retrieval failure. can someone advise me how to how i can fix the controller and set its response type to xml? also, a cogent 1-2 liner of how respond_to and respond_with work would be awesome!
thanks everyone.
replace
respond_with do |format|
format.xml { render }
end
with
respond_with(ix)
There are 2 ways of rendering a xml. Example 1 uses respond_to that means "every single method will use xml and use the object parse in from respond_with"
Example 2 uses respond_to that means "use the block below to declare what type of respond and the object to be parse"
example 1:
class ResponsesController
respond_to :xml #respond_to A
def index
respond_with(#asd) # respond_with A
end
end
example 2:
def ResponsesController
def index
respond_to do |format|
format.xml { render xml: #asd}
end
end
end
http://blog.plataformatec.com.br/2009/08/embracing-rest-with-mind-body-and-soul/

Preventing direct URL access to Rails Controller Methods

I have a particular Rails controller method that returns some JSON when I do an javascript ajax request in the front-end.
However, I want to prevent users from directly typing in the url, which displays the JSON that the method returns. I also want to still be able to perform my ajax requests. How can I go about doing this simply? Thanks!!
Just a thought... You could do something custom in your respond_to block for html requests.
respond_to do |format|
format.html { ... } # give them a 404 response?
format.js { render :json => #obj }
end
Or maybe your html.erb template with that name could just show some kind of access denied message. Then you'd just have this:
respond_to do |format|
format.html
format.js { render :json => #obj }
end
You could wrap your action with
if request.xhr?
...
end
The respond_to filter in rails3 controllers is pretty sweet.
YourJsonController < ApplicationController
respond_to :json
def index
#non-json requests will receive a 406 error
end
end
In your routes.rb, you can add :via => :post so your URL accepts only POST requests. See "HTTP Verb Constraints" at http://guides.rubyonrails.org/routing.html

Add JSON support to Rails app

I am experimenting with Rails and was wondering what's needed to allow/add support for JSON requests?
I have a vanilla installation of Rails 2.3.5 and the default scaffolding seem to provide support for HTML & XML requests but not JSON.
class EventsController < ApplicationController
# GET /events
# GET /events.xml
def index
#events = Event.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #events }
end
end
# GET /events/1
# GET /events/1.xml
def show
#event = Event.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #event }
end
end
...
I'm new to this but it would appear as though i would need to add a format line in each method along the lines of:
format.js { render :js => #event.json }
couldn't this be done automatically? perhaps there's a template somewhere i need to update...or a flag i can set? Or perhaps, and most likely, I've missed the boat entirely?!?
You do:
format.json {render :json=>#event}
That will render the default activerecord JSON for the model
The option of ease of use is that you can write a private method which takes the format object and an object to render and then, based on the format, renders different things. Example:
class MyController<ApplicationController
def show
#event=Event.find(params[:id])
respond_to do {|format| myRenderer(format,#event)}
end
...
private
def myRenderer(fmt,obj)
fmt.json {render :json=>obj}
fmt.html
fmt.xml {render :xml=>obj}
end

Resources