I have this code:
class ApplicationController < ActionController::Base
def authenticate_user!(*_args)
super
end
end
In authenticate_user! I call super, which should call authenticate_user! from ActionController::Base, which is an abstract class.
I couldn't find authenticate_user! inside ActionController::Base, so I suppose there is some meta programming involved(?).
How is that authenticate_user! from Devise ends up in ActionController::Base? Or any other method that is called by using super in ApplicationController?
authenticate_user! is a method that id dynamically built by devise, so you have to monkey patch devise. Helpers are available in both controllers and views, so that's probably why you're not hitting ActionController::Base
If you need to override the functionality you'll need to monkey patch this class: https://github.com/plataformatec/devise/blob/88724e10adaf9ffd1d8dbfbaadda2b9d40de756a/lib/devise/controllers/helpers.rb#L148
You can override the authenticate_user! method by defining your own. First, you'll have to create the directory lib/devise/controllers and add a file called helpers.rb, to which you can add:
module Devise
module Controllers
module Helpers
module ClassMethods
def authenticate_user!
# if something
# do something
# else
super
# end
end
end
If you aren't using devise, post a comment and I'll delete this answer
Related
Devise has a handy feature called devise_group (link to documentation), which creates a group with your multiple devise models.
The docs are really self explanatory. If you have 2 devise models named, for instance, Admin and User, you can use devise_group like so:
class ApplicationController < ActionController::Base
devise_group :blogger, contains: [:user, :admin]
end
This would give you, alongside with authenticate_user! and authenticate_admin!, the method authenticate_blogger!, which would redirect unless user or admin were signed in.
We have been using this in production and it works great. We have the flexibility to restrict some Controller/actions to admins using authenticate_admin! and use authenticate_blogger! when we can have both accessing it.
Due to some complex business logic we have, we had to override authenticate_user! in ApplicationController, following this nice StackOverflow answer here.
Basically it proposes to override ApplicationController#authenticate_user! and calling super when we want the flow follow trough Devise's.
The problem arose when we tried to do the same solution with `authenticate_blogger!. If we do this:
class ApplicationController < ActionController::Base
devise_group :blogger, contains: [:user, :admin]
def authenticate_blogger!
super
end
end
// Another controller
class DashboardController < ApplicationController
before_action :authenticate_blogger!
end
Rails raises this error:
super: no superclass method `authenticate_blogger!' for #<DashboardController:0x00007fd453ca5d80> Did you mean? authenticate_user!
Any idea why calling super inside an override of authenticate_user! in ApplicationController works fine, but the same doesn't happen with the devise group equivalent ?
EDIT 1: found out the reason, but could use some help improving the solution
Looking at devise source code, devise_group uses Ruby's class_eval do define instance methods like authenticate_blogger! in the context of the class it was called.
So when we use devise_group inside ApplicationController, it's like we're defining authenticate_blogger! as an instance method in ApplicationController.
That's why when we manually define that method authenticate_blogger! in ApplicationController and call super it raises the exception, because we actually overwrote the same instance method in the same class (ApplicationController) and there's nothing to find up in the ancestor chain.
authenticate_user!, on the other hand, is way up in the ancestor chain in Devise::Controllers::Helpers (I can see it calling ApplicationController.ancestors`.
The hacky-proof-of-concept-fix we did was to create a TempController, define devise_group inside it, and make ApplicationController inherit from it:
class TempController < ActionController::Base
devise_group :advertiser, contains: [:user, :broker]
end
// In application_controller.rb
class ApplicationController < TempController
def authenticate_blogger!
super // this now works since it goes up in the ancestor chain and finds authenticate_blogger in TempController
end
end
Even tough I'm happy with my investigation ... any suggestions on fixing this without actually having to make ApplicationController not inherit ActionController::Base, like it's default in Rails?
I'm relatively new to Rails and am working on creating a simple user authentication system to get to grips with how Rails works.
I'm currently at the point where I'd like to create some methods that I can use in my controllers like so:
is_logged? # => true
and
current_user_id # => 6
These would be used to interact with sessions, mainly so I'm not repeating myself in the controller.
Where would I define these functions and how would I include them in a controller?
Thanks a lot in advance for any help.
Method 1
You can define these method in helper files, inside app/helpers/my_module.rb. You can create a module there, put all the methods inside of it, and then include the modules in your control to use these method.
module MyMoule
def is_logged?
...
end
end
Then in you class include the module
class MyClassController < ApplicationController
include MyModule
def my_method
#Use it like this
logged_in = MyModule.is_logged?
end
end
Method 2
If you using session related stuff you can always put them inside application_controller.rb. And since all your controller will inherit ApplicationController the methods will be available to you.
class ApplicationController < ActionController::Base
def is_logged?
...
end
end
In your other controller you can use them directly.
class MyClassController < ApplicationController
def my_method
logged_in = is_logged?
end
end
I'm upgrading an app to Rails 3. We have an Application Controller like the following:
class ApplicationController < ActionController::Base
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
Because we define "helper_method", "current_user" is available to all the views. It's available to the controllers because they all inherit from class ApplicationController.
However, when we were on 2.3.8, access to "current_user" was available through a model, but now it's not. Is there a way to get this exposed to the models?
I could suggest moving current_user into a helper like UserHelper, then you can include it in your model with include UserHelper.
You'll also have to include UserHelper in your ApplicationController, but helper :user will include that like normal into your views too.
I created a helper method for some simple calculation. This helper method will just return an integer. I need the helper in both controllers and views.
Unfortunately, it work well in views but not in controllers. I get the undefined local variable or method error. How can I fix it?
Thanks all
In order to use same methods in both controller and views Add you method in application_controller.rb and make it helper methods.
For example
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
#protect_from_forgery # See ActionController::RequestForgeryProtection for details
helper_method :current_user
def current_user
session[:user]
end
end
Now you can use method current_user in both controllers & views
I use a following solution. Because I think helper's methods shoud be stored in an appropriate helper module.
module TestHelper
def my_helper_method
#something
end
end
class SomeController < ApplicationController
def index
template.my_helper_method
end
end
Strange thing – I have Authentication module in lib/ like this:
module Authentication
protected
def current_user
User.find(1)
end
end
and in ApplicationController I'm including this module and all helpers, but method current_user is available in controllers, but not from views :( How can I make this work?
If the method were defined directly in the controller, you'd have to make it available to views by calling helper_method :method_name.
class ApplicationController < ActionController::Base
def current_user
# ...
end
helper_method :current_user
end
With a module, you can do the same, but it's a bit more tricky.
module Authentication
def current_user
# ...
end
def self.included m
return unless m < ActionController::Base
m.helper_method :current_user # , :any_other_helper_methods
end
end
class ApplicationController < ActionController::Base
include Authentication
end
Ah, yes, if your module is meant to be strictly a helper module, you can do as Lichtamberg said. But then again, you could just name it AuthenticationHelper and put it in the app/helpers folder.
Although, by my own experience with authentication code, you will want to have it be available to both the controller and views. Because generally you'll handle authorization in the controller. Helpers are exclusively available to the view. (I believe them to be originally intended as shorthands for complex html constructs.)
Did you declare it with
helper :foo # => requires 'foo_helper' and includes FooHelper
helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
in your ApplicationController?
http://railsapi.com/doc/rails-v2.3.3.1/classes/ActionController/Helpers/ClassMethods.html#M001904