In my controller i have an action which does not have a corresponding view. Precisely: the upload action for uploading images. However, i require the current users id to store the image url. But the current_user method always returns nil, as the action by itself does not have a view. In such scenarios how do i fetch the current_user? I am using authlogic. My application_controller.rb contains the following:
class ApplicationController < ActionController::Base
helper :all
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
protect_from_forgery
def correct_safari_and_ie_accept_headers
ajax_request_types = [ 'application/json', 'text/javascript', 'text/xml']
request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
private
def set_cache_buster
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
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
end
EDIT: All other actions in the controller are able to access the current_user helper method. Only the upload action is not able to. Code:
Controller:
class ImageStacksController < ApplicationController
def upload
# Using swfupload.
image_stack_params = {
:caption => params[:caption],
:swf_uploaded_data => params[:link]
}
# current_user returns nil, even with user logged in!!
# Occurs only in the upload action and no other action in this controller.
logger.info("Current User: #{current_user}") #Returns nil
#image_stack = current_user.image_stacks.create! image_stack_params
respond_to do |format|
format.js { render :json => #image_stack }
end
end
def edit
logger.info("Current User: #{current_user}") #Returns current user
end
def new
logger.info("Current User: #{current_user}") #Returns current user
end
def update
logger.info("Current User: #{current_user}") #Returns current user
end
def destroy
logger.info("Current User: #{current_user}") #Returns current user
end
end
Model:
class ImageStack < ActiveRecord::Base
belongs_to :user, :class_name => "User", :foreign_key => "user_id"
upload_image_to_s3 :link
def swf_uploaded_data=(data)
data.content_type = MIME::Types.type_for(data.original_filename)
self.link = data
end
end
The controller method is really just that, a class method. It does not require a view. My making it a private method the method is not available outside the class or other classes inheriting from it and as such it is correctly not available to the view. Your problem suggests that your user is not logged in or something else is wrong. Do you have a require_user method?
#application_controller
private
def require_user
unless current_user
store_location
flash[:notice] = t(:must_be_logged_in)
redirect_to user_login_path
return false
end
end
def store_location
session[:return_to] = request.request_uri
end
#user.rb
has_many :images
#image.rb
belongs_to :user
# image_controller.rb
before_filter :require_user
def create
#photo = #item.images.new(:photo => params[:photo], :user => current_user)
Edit:
Your current_user method is a ApplicationController method which is already inherited:
ImageStacksController < ApplicationController
This:
helper_method :current_user_session, :current_user
is providing the methods to the view.
The difference between the upload action and all the others is update is being called by javascript. I remember doing a similar uploader and having to pass the authenticity token. Is anything else being reported in the log?
This might be of use to you: http://github.com/JimNeath/swfupload---paperclip-example-app
Making the authenticity token available to js goes something like this:
- content_for :head do
= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery?
Now you add the field to swflupload the same way you added current_user.
Related
I'm using Active Model Serializers.
I am trying to access #current_user which is defined inside ApplicationController like this:
class ApplicationController < ActionController::API
before_action :authenticate_request
private
def authenticate_request
auth_header = request.headers['Authorization']
regex = /^Bearer /
auth_header = auth_header.gsub(regex, '') if auth_header
begin
#current_user = AccessToken.get_user_from_token(auth_header)
rescue JWT::ExpiredSignature
return render json: {error: "Token expired"}, status: 401
end
render json: { error: 'Not Authorized' }, status: 401 unless #current_user
end
end
I can use #current_user anywhere I want except inside my ProjectSerializer, which looks like this:
class V1::ProjectSerializer < ActiveModel::Serializer
attributes(:id, :name, :key, :type, :category, :created_at)
attribute :is_favorited
belongs_to :user, key: :lead
def is_favorited
if object.favorited_by.where(user_id: #current_user.id).present?
return true
else
return false
end
end
end
ProjectSerializer is located in my app/ project tree structure:
app/
serializers/
v1/
project_serializer.rb
I get an error trying to access #current_user:
NoMethodError in V1::UsersController#get_current_user
undefined method `id' for nil:NilClass
This happens when I'm calling the function from UserController, which then goes to UserSerializer and then that serializer has has_many :projects field which calls ProjectSerializer.
You can access the variables using instance_options. I believe you can access #current_user in projects controller. For example :
def projects
#projects = Project.all
render_json: #projects, serializer: ProjectSerializer, current_user: #current_user
end
Inside the serializer you can access the current_user like wise:
def is_favorited
if object.favorited_by.where(user_id: #instance_options[:current_user].id).present?
return true
else
return false
end
end
i have this routes:
Rails.application.routes.draw do
root 'login#new'
get '/home/inicio', to: 'home#index'
scope '/login' do
get '/acesso', to:'login#new'
post '/acessorecebendo', to:'login#create', as:'user'
get '/sair', to:'login#destroy'
end
resources :login
resources :home
resources :produtos
resources :fornecedors
end
the Login controller:
class LoginController < ApplicationController
protect_from_forgery
def new
if session[:user]
#user = User.find(session[:user])
end
end
def destroy
reset_session
redirect_to "/login/acesso", notice: "VocĂȘ foi deslogado"
end
def create
user = User.validate(login_params[:email], login_params[:senha])
if user
session[:user] = user.id
redirect_to "/home/inicio", notice: "login feito com sucesso"
else
redirect_to "/login/acesso", notice: "Dados incorretos"
end
end
private
def login_params
params.require(:login).permit(:email, :senha)
end
end
The home controller:
class HomeController < ApplicationController protect_from_forgery with: :exception
def new
#user = User.find_by(id: session[:user]) end
def index
#produtos = Produto.all
render 'inicio' end
def show
if session[:user]
#user = User.find(session[:user])
end end end
I'm getting an error on the Home view (new.html.erb):
<header>
<h2>Bem-vindo <%= #user.nome %></h2>
<nav>
undefined method `nome' for nil:NilClass
Why i have some problems with the session? I can do the login and i wanna see the user informations of this session on the redirected page, like if i can pass the #user variable assigned on the login action to the home controller to use it.
This is happening because your #user is being set only in index and show, but you are trying to reference it from the new action.
Consider moving this logic to a before_action
class HomeController < ApplicationController
before_action if: ->{ session[:user] } do
#user = User.find_by(id: session[:user])
end
end
If this controller needs to assume that #user is present, you should also have a before_action that handles the case of a missing user account. I usually put this behavior into a AuthenticatedController class and inherit from it where needed.
User.find is not optimal here, because it will throw an exception if no record is found.
I have two layouts Admin and Domain. And I don't need any extra configuration in Admin layout. but if user tries to access Domain layout they must be in their valid domain.
This means that, I need to customize all of my Domain policy to include both current_user as well as current_domain. I found this can be done with UserContext and pundit_user... so here is what I have done:
application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
def pundit_user
UserContext.new(current_user, current_domain)
end
def after_sign_out_path_for(resource)
root_path
end
def current_domain
#current_domain ||= Domain.where(name: requested_domain).first
end
helper_method :current_domain
private
def requested_domain
return request.env["SERVER_NAME"]
end
def user_not_authorized
# reset_session
flash[:alert] = "You are not authorized to perform this action"
redirect_to(request.referrer || root_path)
end
end
Note that, when I access Admin layout, current_domain will be nil and if I visit any routes of Domain layout, then current_domain will set to currently accessing domain.
user_context.rb
class UserContext
attr_reader :current_user, :current_domain
def initialize(current_user, current_domain)
#current_user = current_user
#current_domain = current_domain
end
end
PROBLEM
Suppose I have this policy:
user_policy.rb
class UserPolicy < ApplicationPolicy
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def index?
binding.pry # debugging
current_user.admin? ||
current_user.domain == current_domain
end
private
def current_user
# return user.is_a?(User) ? user : user.current_user
user.current_user
end
def current_domain
# return user.is_a?(User) ? nil : user.current_domain
user.current_domain
end
end
when application runs current_user and current_domain must available in UserPolicy as per documentation(https://github.com/elabs/pundit#additional-context).
But I am getting
undefined method `current_user' for #<User:0x007fcefbc2b150>
That means, still I have user object in it, not user.current_user and user.current_domain
Please let me know, if you need further description. What am I missing here?
It was my own dumb mistake.
PROBLEM
I had a before_filter call in domain/base_controller.rb something like:
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# PROBLEM: this line was updating pundit_user again to user object
raise Pundit::NotAuthorizedError unless DomainConsolePolicy.new(current_user, current_domain).authorized?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
SOLUTION:
I have used headless policy for this because now I have both current_user and current_domain set with pundit_user in application_controller
domain/base_controller.rb
class Domain::BaseController < ApplicationController
before_action :authenticate_user!
before_action :domain_exists?
before_action :verify_domain!
private
def verify_domain!
# SOLUTION
authorize :domain_console, :has_access?
end
def domain_exists?
if current_domain.blank?
redirect_to root_path, alert: 'Domain that you provided is not valid or is permanently removed!'
end
end
end
policy/domain_console_policy.rb
class DomainConsolePolicy < Struct.new(:user, :domain_console)
def has_access?
user.current_user.admin? ||
user.current_user.domain_id == user.current_domain.id
end
end
Thanks
What are the best practices to implement a "Remember Me" function in Rails 3 application ?
I store session information (session id + user id) in the database when user logs in, and I don't want to use any plugins at this moment.
Any pointers or code samples will be much appreciated.
You can just set the expiration on a signed cookie to accomplish this. (Signed cookies are tamper-proof just like the Rails-provided session cookie.)
class SessionsController < ApplicationController
def create
...
user = User.authenticate(params[:email_address], params[:password])
if params[:remember_me]
cookies.signed[:user_id] = { value: user.id, expires: 2.weeks.from_now }
else
# expires at the end of the browser session
cookies.signed[:user_id] = user.id
end
end
def destroy
cookies.delete :user_id
end
end
class ApplicationController < ActionController::Base
...
def current_user
User.find(cookies.signed[:user_id])
end
end
Railscasts has an episode on achieving this as well as well as a great HOWTO on implementing those features via BDD with RSpec and Capybara.
I store session information (session id + user id) in the database when user logs in
I believe that's one approach and the casts above does the same with cookies by issuing each User account a unique authentication token.
Have been reading the Rails tutorial book and it has an implementation for Remember Me
You can check for some hints (The implementation may be different from yours)
http://ruby.railstutorial.org/book/ruby-on-rails-tutorial#sec:remember_me
This is how I implemented remember_me (the below snippet is from my example Rails app on authentication):
class SessionsController < ApplicationController
skip_before_filter :login_required, :only => [:new, :create]
def new
end
def create
#current_user = User.authenticate(params[:email], params[:password])
if #current_user
#current_user.track_on_login(request)
if params[:remember_me]
cookies[:remember_token] = { :value => #current_user.remember_token, :expires => 24.weeks.from_now }
else
cookies[:remember_token] = #current_user.remember_token
end
redirect_to dashboard_url, :notice => "Logged in successfully."
else
flash.now[:alert] = "Invalid login or password."
render 'new'
end
end
def destroy
current_user.track_on_logout
current_user.reset_remember_token_and_save # can't rely on the 'save_current_user_if_dirty' after_filter here
cookies.delete(:remember_token)
reset_session
redirect_to root_url, :notice => "You have been logged out."
end
end
Just an example without salt:
class ApplicationController < ActionController::Base
protected
def signin!(user_id)
return unless user_id
#current_user = User.find(user_id)
self.session_user_id = #current_user.id
self.permanent_user_id = #current_user.id if session[:accept_remember_me]
end
def signout!
self.session_user_id = nil
self.permanent_user_id = nil
session[:accept_remember_me] = nil
#current_user = nil
end
def remember_me
session[:accept_remember_me] = true
end
private
def permanent_user_id
cookies.signed[:permanent_user_id]
end
def permanent_user_id= value
cookies.permanent.signed[:permanent_user_id] = value
end
def session_user_id
session[:user_id]
end
def session_user_id= value
session[:user_id] = value
end
end
I'm working through "Learning Rails by Example" tutorial by Michael Hartl(11.33).The relevant code is:
class PagesController < ApplicationController
def home
#title = "Home"
if signed_in?
#micropost = Micropost.new
#feed_items = current_user.feed.paginate(:page => params[:page])
end
end
class User < ActiveRecord::Base
def feed
Micropost.all(:conditions => ["user_id = ?", id])
end
module SessionsHelper
def sign_in(user)
user.remember_me!
cookies[:remember_token] = { :value => user.remember_token,
:expires => 20.years.from_now.utc }
self.current_user= user
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= user_from_remember_token
end
#feed_items is then rendered as a collection to "will_paginate" and everything works fine.
My problem is that I cannot understand how the current_user is passed to the "feed" method in the PagesController line ie
"#feed_items = current_user.feed.paginate(:page => params[:page])"
Any help will be greatly appreciated
Alan
current_user is never "passed" to the feed method - the feed method is called on the current_user object. Take a look at the User model:
def feed
Micropost.all(:conditions => ["user_id = ?", id])
end
This is an instance method which is executed when you call current_user.feed. The id in the :conditions hash refers to the id of the user object instance, in this case current_user.
Most likely "current_user" is defined in the paret class of your controller: ApplicationController. Search for "application_controller.rb" in your controller directory.