This would probably be simpler for me if Ruby was my first language, but anyway, here's my question:
Using Rails 3.1, I'm trying to access some of the Warden Manager callbacks with Devise to create a new 'Cart' each time a user signs in. I'm placing this logic in my ApplicationController. Problem is, when I create a Cart, I want to give it a user id. I've been trying to use Devise's helper method current_user but that isn't working.
Most importantly, I want to know why I can't access my helper methods or methods defined in the ApplicationController from within the Warden::Manager block. But I also want to know how I can edit my code so I can use Devise's current_user method (and my current_cart method, shown below) within the block without errors like the one listed below being called.
Here's my code:
class ApplicationController < ActionController::Base
helper :all
helper_method :current_user
protect_from_forgery
before_filter :fetch_categories
.
.
.
def current_cart
#current_cart ||= Cart.find_by_user_id(current_user.id)
end
Warden::Manager.after_authentication do |user, auth, opts|
Cart.create!(:user_id => current_user.id)
end
end
Here's the error:
NameError in Devise::SessionsController#create
undefined local variable or method `current_user' for ApplicationController:Class
Warden::Manager.after_authentication do |user, auth, opts|
Cart.create!(:user_id => user.id)
end
In the after_authentication block you don't have access to the current_user. Instead, use the newly-authenticated user object passed as a parameter.
Well, I don't really like answering my own questions, but since I feel obligated to leave no question unanswered:
What I ended up doing was essentially side-stepping the whole callback thing altogether. Although this might be idiosyncratic to my situation, here's what I did:
In the application controller:
before_filter :authenticate_user!, :only => :current_cart
That way, a user must be signed in for current_cart to be called. And also change current_cart to:
def current_cart
session[:cart_id] ||= Cart.create(:user_id => current_user.id).id
#current_cart ||= Cart.find(session[:cart_id])
end
So current_cart instantiates a new cart if it doesn't yet exist. You can also do the before_filter stuff in other controllers which might affect your cart, such as LineItems or Products.
Related
I'm working on a login/logout system. Instead of using devise, I created an active records User model and use sessions to remember if a user is logged in. Everything was working fine until I added these lines in the application_controller.rb to have a layout before login and one after.
layout :set_layout
def set_layout
if session[:current_user_id]
'afterlogin'
else
'application'
end
end
Now, after I log in and cancancan is being used somewhere in a html page I get undefined local variable or method 'current_user'. I think that I have to add a current_user method but I'm not exactly where and how to define it.
Edit: I already had something similar in another class that is being used by login:
class Admin::ApplicationController < ApplicationController
before_action :authorize
def authorize
begin
#current_user ||= User.find(session[:current_user_id]) if session[:current_user_id]
rescue ActiveRecord::RecordNotFound
session.destroy
redirect_to '/login',alert: 'Please login'
end
end
end
Should I modify this after I add that method ?
CanCanCan expects a current_user method to exist in the controller.
First, set up some authentication (such as Authlogic or Devise).
See Changing Defaults if you need different behavior.
I would suggest you to install Devise so that it comes with a complimentary current_user method.
FYI: https://github.com/plataformatec/devise
UPDATE
when a user logins successfully, you can store the user's id in session.
session[:current_user_id]=user.id
so that, in your applicationcontroller, you can do
def current_user
#current_user ||= session[:current_user_id] && User.find_by_id(session[:current_user_id])
end
helper_method :current_user
In a create method in a controller I have:
if logged_in_admin?
#invitation.set_ids
In the Invitation model:
def set_ids
self.person_one_id = current_user.id
end
current_user is a method in app/helpers/sessions_helper.rb and defines the currently logged in user. I use this method successfully in many controller methods. However, for the use case above I get the error message undefined local variable or method 'current_user' for #<Invitation:0x007f699086bf40>.
Why do I get this error message? Is this because this time I'm using the helper method in a model file and is this not allowed? If such is not allowed, what would be the best way to securely set person_one_id for #invitation equal to the id of the currently logged in user?
current_user not available in a model layer(it's MVC, your helpers on the CV layer and model know nothing about the current_user helper). Pass user_id from your helper as argument:
some_helper.rb
def my_helper
if logged_in_admin?
#invitation.set_ids(current_user.id)
# .....
model.rb:
def set_ids(user_id)
self.person_one_id = user_id
end
You have to add the following line to your ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
end
Now you should be able to use the methods inside your controllers / models.
I'm using Devise to handle users in a shopping application. What I want to do is create a new cart each time a user signs in (and ideally, destroy the same cart each time a user signs out, but I'll just stick to that first part for this question).
So far, I've looked at this question: Devise call backs
And I came up with this:
class ApplicationController < ActionController::Base
helper :all
protect_from_forgery
before_filter :fetch_categories
.
.
.
Warden::Manager.after_authentication do
session[:cart_id] ||= Cart.create!.id
end
end
...But clearly this isn't correct, because I'm getting this error:
NameError in Devise::SessionsController#create
undefined local variable or method `session' for ApplicationController:Class
Is there some other way I can tell the application controller to respond to a user sign in, or should I be putting this code elsewhere (other than the application controller)? Thanks for any help.
Glancing at the docs, it looks like you can do something like this:
Warden::Manager.after_authentication do |user, auth, opts|
auth.session[:cart_id] ||= Cart.create!.id
end
Simpally write a before filter
In application controller
before_filter :set_current_user
def set_current_user
Authorization.current_user = current_user
end
and
you can check throughout application
using
` if !current_user.nil?
end`
I'm trying to set the current user into a variable to display "Logged in as Joe" on every page. Not really sure where to begin...
Any quick tips? Specifically, what file should something like this go in...
My current user can be defined as (I think): User.find_by_id(session[:user_id])
TY :)
You might want to use something like Authlogic or Devise to handle this rather than rolling your own auth system, especially when you aren't very familiar with the design patterns common in Rails applications.
That said, if you want to do what you're asking in the question, you should probably define a method in your ApplicationController like so:
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
You inherit from your ApplicationController on all of your regular controllers, so they all have access to the current_user method. Also, you might want access to the method as a helper in your views. Rails takes care of you with that too (also in your ApplicationController):
helper_method :current_user
def current_user ...
Note: If you use the find_by_x methods they will raise an ActiveRecord::RecordNotFound error if nothing is returned. You probably don't want that, but you might want something to prevent non-users from accessing user only resources, and again, Rails has you covered:
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user
before_filter :require_user
private
def current_user
#current_user ||= User.limit(1).where('id = ?', session[:user_id])
end
def require_user
unless current_user
flash[:notice] = "You must be logged in to access this page"
redirect_to new_session_url
return false
end
end
end
Cheers!
It belongs in your controllers.
All your controllers inheirit from Application Controller for exactly this reason. Create a method in your Application Controller that returns whatever you need and then you can access it in any of your other controllers.
I'm trying to get the permit method to work using the rails-authorization-plugin and authlogic, and I keep running into this error:
When I try:
class ApplicationController < ActionController::Base
...
before_filter permit 'admin'
...
I get this:
Authorization::CannotObtainUserObject in HomeController#index
Couldn't find #current_user or #user, and nothing appropriate found in hash
Now I do have my current_user method setup, and it works, because I used it just about everywhere else in my app:
class ApplicationController < ActionController::Base
...
helper_method :current_user
private
def current_user_session
return #current_user_session if defined?(#current_user_session)
#current_user_session = UserSession.find
end
def current_user
return #current_user if defined?(#current_user)
#current_user = current_user_session && current_user_session.record
end
...
I also know that I have users with the appropriate roles in my database, because this method works:
def require_admin
unless current_user.is_admin? || current_user.is_root?
flash[:warning] = 'You are not an administrator and cannot access this page.'
redirect_to root_path
end
end
I can make everything work if I just check on the user level using this:
before_filter :require_admin, :only => 'index'
... but shouldn't I be able to the same thing effectively with permit and permit??
Any help would be much appreciated. Let me know if you need to see more code and I'll be happy to post it. There really is nothing on Google that I can make heads-or-tails of regarding getting these two systems to work with each other.
Okay, I think I figured it out.
As Jared correctly pointed out, the proper usage is
permit 'admin'
(Not as part of a before_filter).
HOWEVER...
... the default :get_user_method is set to #current_user, which is what the acts_as_authenticated plugin uses. I, as noted earlier, am using AuthLogic, in where I have the method defined as current_user (without the pound sign).
So, I had tried the following:
permit 'admin', :get_user_method => current_user
Only to be greeted by a nice error message explaining that I had no such variable or method. What I was missing, however, is that the hash option takes a string, not a direct call to the method!! (stupid mistake, I know!)
So
permit 'admin', :get_user_method => 'current_user'
... seems to work for me.
I love Ruby and Rails, but sometimes its simplicity can be a curse of its own; I always get owned by the simple things. :)
You are using the plugin incorrectly. It should not be placed in a before filter.
On the global level, you simply declare:
permit 'admin'
That's it.
All of your actions will look for a current_user or #user object and redirect to the login page if not.
On a per-action level, you use it as a block:
def index
permit 'admin' do
#some_models = SomeModel.all
end
end