Alternative method for not allowing GET requests [duplicate] - ruby-on-rails

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

Related

Rails 5 best controller action to write a like query

I want to make an ajax search query by a client's name, so I'm using a like clause (see this question). I was thinking of using the index action to respond to json format from clients_controller but I'm already using it to repond to html format and at the same time paginates my listed rows with will_paginate and will_paginate-bootstrap.
What is the best way? making a new method to respond to json format or should I use the index one with format? and How to do that?
I'm new with ruby on rails
clients_controller.rb
def index
respond_to do |format|
format.html
{
#something like this I know that I would return me a syntax error
#client = Client.paginate(:page => params[:page])
}
format.json
{
#something like this I know that I would return me a syntax error
#client = Client.where("client_name LIKE ? ", "%#{params[:client_name]}%" )
}
end
end
def other_method
#client = Client.where("client_name LIKE ? ", "%#{params[:client_name]}%" )
respond_to do |format|
format.json {...}
end
end
In my opinion, you should keep only your index action and just accumulate the scopes on your #client variable.
Remember that your SQL query is only sent to the database when performing an Array method like each on your variable, not before.
So you can write something like:
def index
#client = Client.all
if params[:client_name].present?
#client = #client.where("client_name LIKE ? ", "%#{params[:client_name]}%")
else
#client = #client.paginate(page: params[:page])
end
respond_to do |format|
format.html
format.json
end
end
You should create a new action with some fruitful name like search or search_by_client_name . It will solve your issue also and you will stick with rails restful routing.
If you want index action to serve both request then you can do something like this:
def index
#client = Client.paginate(:page => params[:page])
respond_to do |format|
format.html
format.json do
client = Client.where("client_name LIKE ? ", "%#{params[:client_name]}%"
render json: { client: client }
end
end
end

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

Rails: respond_to vs "case" conditional

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.

Why redirect_to in around_filter or after_filter won't work?

How to make redirect_to works in those filters?
I'm trying to change
def start
....
redirect_to index
end
def end
...
redirect_to index
end
to
around_filter :around
def around
...
yield
redirect_to index
end
def start
..
end
def stop
...
end
After the action is complete it renders the template automatically, thus you cannot render / redirect after the request is complete. You could solve this by putting the redirect_to at the end of the actions that you need it for. This is not what around_filters were designed to do.
Presumably, your actions already have a redirect_to or render call. You cannot call these methods twice per request.
You can change response.location, which has the same effect as calling redirect_to. An example with an after_filter (the same could be done with around):
after_filter :different_redirect, only:[:create]
def different_redirect
if self.status == 302
response.location = other_thing_path
end
end
def create
#my_thing = MyThing.new(params[:my_thing])
respond_to do |format|
if #my_thing.save
format.html { redirect_to(my_things_path) }
else
format.html { render :action => "new" }
end
end
end

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

Resources