4

I have this line in my votes controller:

before_filter :authenticate

So when I vote while logged out, it should fail this filter.

My authenticate method in my application controller looks like this:

def authenticate
    logged_in? ? true : access_denied
end

So it returns access_denied which looks like this:

def access_denied
    redirect_to login_path, :notice => "You must log in to perform this action." and return false
end

Therefore, I should be redirected to my login page, right?

I have this in my routes.rb file:

match '/login' => "sessions#new", :as => "login"

and I have a new view with a login form that I should be redirected to when I vote and I'm not logged in. I also have this code in the create method of my votes controller:

if @vote.save
    respond_to do |format|
      format.html { redirect_to @video }
      format.js
    end
  else
    respond_to do |format|
      format.html
    end
  end

But when I vote, I am not redirected, and I get this error message in my logs:

Started GET "/login" for 127.0.0.1 at Thu Mar 24 21:18:08 -0700 2011
  Processing by SessionsController#new as JS
Completed   in 17ms

ActionView::MissingTemplate (Missing template sessions/new with {:locale=>[:en, :en], :formats=>[:js, "*/*"], :handlers=>[:rhtml, :rxml, :erb, :builder, :rjs]} in view paths "/rubyprograms/dreamstill/app/views"):

Why is this happening, and how can I make this redirect work?

Justin Meltzer
  • 13,318
  • 32
  • 117
  • 182

2 Answers2

6

It seems like you are making a javascript request to your VotesController#create action, but you are missing a JS template for Sessions#new.

Here's what happens: you make a call to VotesController#create as a JS call, and when the user is logged in all is fine, because VotesController#create response to javascript actions. The problem is that if the user is not logged in, the request redirects to SessionsController#new, and the request is still a javascript request. The problem is that SessionsController#new does not respond to JS so you get a missing template error.

I would recommend adding an app/views/sessions/new.js.erb file to handle the JS call. You probably only have an app/views/sessions/new.html.erb file right now.

You can also handle this case by redirecting, using javascript, to an HTML request. You can do this by adding the following to your new.js.erb template:

window.location = "<%= escape_javascript(login_path) %>";

This will basically tell the browser to redirect to the login_path as if the request were an HTML request, instead of a Javascript request.

Pan Thomakos
  • 34,082
  • 9
  • 88
  • 85
  • I've updated my answer with more details as well as a simple redirect solution. – Pan Thomakos Mar 25 '11 at 01:39
  • why do I need a js file for a new session? I don't understand why I would need it and what should go in it... And why can't I just resort to a normal HTTP request when `@vote.save` fails? – Justin Meltzer Mar 25 '11 at 01:41
  • If a request is issued as a Javascript request from the browser, for example via an AJAX request, then you can't respond with HTML content because the browser won't know what to do with the response. You need to respond with Javascript, like my example code, that can be run using the browser's javascript engine. That's the way the HTTP protocol works. This is not particular to Rails. – Pan Thomakos Mar 25 '11 at 01:45
  • That makes sense, but what I don't understand is why it's still a js request. I have `format.js` within an `if @vote.save` block, so that shouldn't get called. What's still making it a js request? – Justin Meltzer Mar 25 '11 at 02:00
  • What you're referring to is the response block, not the request block. The format.js means respond to a js request. The format.html part says respond with "redirect_to @video" to an html request. – Pan Thomakos Mar 25 '11 at 02:52
1

Since the request that is causing problems is ajax try adding a check within your before_filter to whether the request is js.

if request.xhr?
    render :text => "document.location = '#{login_path}';", :layout => false
else
  redirect_to login_path, ...
end
chris
  • 4,332
  • 5
  • 41
  • 61
  • As said js requests don't responsd to http responses in the same way. So if a redirect of 301 or 302 is returned it ignores them, which is why you to make the redirect happen you have to add the js code. I don't like putting code like this in a new.js.erb file since the handling of the js is hidden. Keep it clear by rendering back the js code within the controller method. – chris Mar 25 '11 at 01:53
  • I found this useful in my ApplicationController, where I have a couple of global `rescue_from` conditions. I discovered that I could insert a `flash[:alert] = message` statement before the `render` to get a "normal" error message on the page I'm redirecting to. I assigned `window.location` as that is apparently more universally writable (http://stackoverflow.com/a/2430948/550712). I don't suppose I can assign a 302 status? When I added `:status => 302` to the `render`, the JavaScript didn't execute. – Mark Berry Sep 05 '13 at 00:14