Why before_filter is not working?

I have been scratching my head on this one for most of the day. Hopefully someone can help explain why before_filter isn't working for my codes.

In my Application controller, I have this:

before_filter :login_required, :except => [:newacct, :create_newacct, :passwd_reset ]

def login_required     unless session[:user_id]       flash[:notice] = "Please log in"       redirect_to new_session_url     end end

I have the passwd_reset method defined in my Users controller.

My intention is if I call the method /users/passwd_reset it should skip executing the login_required method in the Application Controller, which in turn skip the login screen.

The result I have is I keep getting the login screen. My conclusion was that the exception in the before_filter isn't working for some reason. Perhaps someone will be able to spot my error and help me understand why it isn't working as I intended it to.

Thanks in advance for the help.

Steve

maestro777 wrote:

In my Application controller, I have this:

before_filter :login_required, :except => [:newacct, :create_newacct, :passwd_reset ]

def login_required     unless session[:user_id]       flash[:notice] = "Please log in"       redirect_to new_session_url     end end

I am not 100% sure... but:

It is correct to place the "login_required" method in your Application controller, but I think, that you should place the "before_filter" declaration inside your Users controller.

Have you tried that?

- Carsten

Carsten is right. Here's what's happening (I believe):

Since the before_filter call is in application.rb, it's actually getting called for *every* action in *every* controller. The :except is not working because it's looking in application.rb for :passwd_reset, which it is not finding.

One way to solve this is to move the before_filter statement to the UsersController, so it would look like:

application.rb

def login_required ... end

users_controller.rb

before_filter :login_required, :except => [:passwd_reset]

Another possibility, though I have not used it myself, is to use skip_before_filter. In that case, you'd put the before_filter call back in application.rb, but with *no* :only/:except, and then skip it where appropriate in other controllers. That's assuming I'm understanding skip_before_filter correctly. I might not be.

One other thing to keep in mind is that filters need to return true or false to indicate where the chain stops. I *believe* that if you don't specifically return one or the other, true is returned by default. So in your case, you'd want to do

redirect_to new_session_url and return false

Peace, Phillip

One other thing to keep in mind is that filters need to return true or false to indicate where the chain stops. I *believe* that if you don't specifically return one or the other, true is returned by default.
So in your case, you'd want to do

not true as of rails 2.0. Return value is irrelevant, they halt if you
render or redirect.

Fred

Thanks all for the input.

The reason I'm putting the before_filter in the Application.rb is simply that I want it to apply to all controllers. Since the purpose is to check whether the user has a valid session before executing any controller methods. I could move it to the Users controller but then I would also need to put it in other controllers as well.

Phillip, As for using the skip_before_filter as you suggested, I don't think that would work in my setup because it will skip the before_filter for every methods in my Users controller, if I understand skip_before_filter correctly. I only want it to apply to one or two methods within the Users controller and not the entire Users Controller. I could probably move all the methods to a separate controller and put the skip_before_filter in that controller. But the reason I want to keep that method within the Users controller is because it is a user related method and I thought it best to keep it within the same controller.

As I played more on this, I have come to think that perhaps it is my routing config that is causing the issue. Let me explain. I have a link in one of my views that calls up the passwd_reset method like this:    href="/users/passwd_reset">forgot your password

When I click that link, the before_filter didn't seem to catch that exception, and it presented me with a login page, which isn't what I intended.

But if I change that link to /users/passwd_reset/0 the before_filter exception will work. Apparently by adding an extra id to the link url makes the difference. But the zero is redundant for me. What I ended up doing is adding in the routing file the following:     map.connect "passwd_reset", :controller => "users", :action => "passwd_reset"

And then use the url link as simply /passwd_reset. And that works like a charm. So my conclusion is that my before_filter problem has something to do with the routing config, although I don't really know why or how. (routing is not an easy subject for me to grasp). Incidentally I also have "map.resources users" at the top of my route.rb

cheers, Steve

maestro777 wrote:

Thanks all for the input.

The reason I'm putting the before_filter in the Application.rb is simply that I want it to apply to all controllers. Since the purpose is to check whether the user has a valid session before executing any controller methods. I could move it to the Users controller but then I would also need to put it in other controllers as well.

Right. The whole point of putting a before_filter call in application.rb is so it automatically applies to all controllers that descend from ApplicationController. I think where we are misunderstanding one another is in the way you avoid the filter being run for a particular action in a particular controller. What I'm understanding you to be saying is you can specify, in application.rb, all of the actions to either include or exclude regardless of the controller. So if I do this in application.rb

before_filter :login_required, :only => [:create, :update, :destroy]

then it would apply to :create, :update, and :destroy actions in any controller. I have never understood it to work that way, but maybe it does. Rather, my understanding is that if you specify :only or :except, it will look in the "current" controller, that is, whichever one the before_filter statement is found. So in my contrived case above, the filter would actually never get called because you don't have :create, :update, :destroy actions in application.rb. On the other hand, in your example where you have :except, it seems that it would always get called because you don't have the exceptions in your application.rb.

I certainly won't be surprised to find out that my understanding is wrong (I'm often wrong and have learned to accept it cheerfully), but I will be surprised if that is actually how it works. It seems it would be an extremely complicated way to go about things.

As for the skip_before_filter, the API docs say

You can control the actions to skip the filter for with the :only and :except options, just like when you apply the filters.

so I assume you could do something like

skip_before_filter :login_required, :only => [:passwd_reset]

But as I said before, I have not made use of skip_before_filter, so I might be blabbering on about something I shouldn't be :slight_smile:

Please don't take this the wrong way, but I'm not sure I'd be satisfied with your route "solution". To have to put a bogus value on your route to get it to work suggests to me that something else is amiss.

Peace, Phillip

a particular controller. What I'm understanding you to be saying is you can specify, in application.rb, all of the actions to either include or exclude regardless of the controller. So if I do this in application.rb

before_filter :login_required, :only => [:create, :update, :destroy]

then it would apply to :create, :update, and :destroy actions in any controller. I have never understood it to work that way, but maybe it does. Rather, my understanding is that if you specify :only or :except, it will look in the "current" controller, that is, whichever one the before_filter statement is found. So in my contrived case above, the filter would actually never get called because you don't have :create, :update, :destroy actions in application.rb. On the other hand, in your example where you have :except, it seems that it would always get called because you don't have the exceptions in your application.rb.

I certainly won't be surprised to find out that my understanding is wrong (I'm often wrong and have learned to accept it cheerfully), but I will be surprised if that is actually how it works. It seems it would be an extremely complicated way to go about things.

I am not an expert in ROR, so I am only taking a stab in the dark here. It is my understanding that, from your example above, the before_filter with the :only option will apply to the named actions every controller. In other words the :create, :update, :destroy actions in all the controllers, if this is declared in the Application.rb. If it is declared in any other controllers, then it will only apply to those actions within that controller. That is my understanding of how the Application controller works. Then again I may probably be totally off the target in my understanding, and it wouldn't surprise me either.

As for the skip_before_filter, the API docs say

You can control the actions to skip the filter for with the :only and :except options, just like when you apply the filters.

so I assume you could do something like

skip_before_filter :login_required, :only => [:passwd_reset]

But as I said before, I have not made use of skip_before_filter, so I might be blabbering on about something I shouldn't be :slight_smile:

You are right in this. For some reasons it totally slipped my mind about using the :only options.

Please don't take this the wrong way, but I'm not sure I'd be satisfied with your route "solution". To have to put a bogus value on your route to get it to work suggests to me that something else is amiss.

Nah, I wouldn't take it the wrong way. Always open to suggestions and constructive criticisms. I wouldn't see it as putting a bogus value in the route but a way to simplify the routing. Instead of using "/users/passwd_reset" in my url, I use only "/passwd_reset". Since there is only one such action called passwd_reset. Looks better too. :slight_smile:

Anyway that wasn't the reason why I did it. As I mentioned before, for some reason my routing does not understand if I use "/user/ passwd_reset" as a link to call upon the passwd_reset action. Apparently my route is expecting a /controller/action/id format. (Probably due to the fact that I have declared "map.resources :users" in the routing as well). So as a quick work-around based on limited understanding of routing, I added the extra directive in my routing. It "solved" my issue that I can now use "/passwd_reset" and it seems to work with the exclusion in the before_filter as well.

Incidentally I tried what you previously suggested. Which was using before_filter without exclusion in application.rb and place "skip_before_filter.....:only => ..." in the User controller. It still give me the same result as before.

Thanks for all your input. Steve

maestro777 wrote:

I am not an expert in ROR,

Me either. Which is why I've been waiting for one of them to chime in. :slight_smile:

here. It is my understanding that, from your example above, the before_filter with the :only option will apply to the named actions every controller. In other words the :create, :update, :destroy actions in all the controllers, if this is declared in the Application.rb. If it is declared in any other controllers, then it will only apply to those actions within that controller. That is my understanding of how the Application controller works. Then again I may probably be totally off the target in my understanding, and it wouldn't surprise me either.

Well it would certainly be good to know that for sure.

Nah, I wouldn't take it the wrong way. Always open to suggestions and constructive criticisms. I wouldn't see it as putting a bogus value in the route but a way to simplify the routing. Instead of using "/users/passwd_reset" in my url, I use only "/passwd_reset". Since there is only one such action called passwd_reset. Looks better too. :slight_smile:

My comment about a bogus value was in regards to the /0 you toyed with. But it just dawned on me why it didn't recognize /user/passwd_reset. You said you have map.resources :user in your routes.rb file. Until you add a member to it (and I don't know the syntax off the top of my head), that seems logical.

Thanks for all your input. Steve

I'm glad it's working for you the way you want it to.

Peace, Phillip

maestro777 wrote:

Thanks all for the input.

The reason I'm putting the before_filter in the Application.rb is simply that I want it to apply to all controllers. Since the purpose is to check whether the user has a valid session before executing any controller methods. I could move it to the Users controller but
then I would also need to put it in other controllers as well.

before_filter :login_required, :only => [:create, :update, :destroy]

then it would apply to :create, :update, and :destroy actions in any controller. I have never understood it to work that way, but maybe it does. Rather, my understanding is that if you specify :only
or :except, it will look in the "current" controller, that is, whichever one the before_filter statement is found. So in my contrived case above, the filter would actually never get called because you don't have :create, :update, :destroy actions in application.rb. On the other hand, in
your example where you have :except, it seems that it would always get
called because you don't have the exceptions in your application.rb.

It doesn't 'look' anywhere (Unsurprisingly rails is rather dynamic
about this). When the action is processed, rails goes along the filter chain,
looking at each filter. for each filter it compares the :only
& :except values with the name of the current action and bails out if
appropriate I tried sticking

   def bang_filter      raise 'Foo'    end

   before_filter :bang_filter, :except => :index

in my application.rb and the filter is skipped properly

Fred

Frederick Cheung wrote:

It doesn't 'look' anywhere (Unsurprisingly rails is rather dynamic about this). When the action is processed, rails goes along the filter chain, looking at each filter. for each filter it compares the :only & :except values with the name of the current action and bails out if appropriate I tried sticking

   def bang_filter      raise 'Foo'    end

   before_filter :bang_filter, :except => :index

in my application.rb and the filter is skipped properly

Fred

Thanks for clarifying, Fred. It seems like a disaster waiting to happen, though. One would need to be very careful about which actions were listed in application.rb. Though, in the right circumstance, it would definitely be DRY.

I guess some people are more knowledgeable because they take the time to experiment. [Note to self: experiment before you post]

Peace, Phillip

Frederick Cheung wrote:

It doesn't 'look' anywhere (Unsurprisingly rails is rather dynamic about this). When the action is processed, rails goes along the filter chain, looking at each filter. for each filter it compares the :only & :except values with the name of the current action and bails out if appropriate I tried sticking

  def bang_filter     raise 'Foo'   end

  before_filter :bang_filter, :except => :index

in my application.rb and the filter is skipped properly

Fred

Thanks for clarifying, Fred. It seems like a disaster waiting to happen, though. One would need to be very careful about which actions were listed in application.rb. Though, in the right circumstance, it would definitely be DRY.

I would agree that it definitely wouldn't be something I'd normally do
(apart from anything else it's typically a combination of action &
controller that you want to exclude from an application wide filter,
and in that case it makes sense to me to have that exception in the
relevant controller).

Fred

I guess some people are more knowledgeable because they take the
time to experiment. [Note to self: experiment before you post]

Always a good one :slight_smile:

I'm pretty certain now that it has to do with my routing config. I commented out the map.resources :users in my route.rb and the before_filter works as intended. Why this is so, I do not know. I need to read up more on the map.resources to see how exactly it work behind the scene.

So the conclusion to my problem is that it is not an issue of before_filter not working but rather an issue of routing.