I have a devise User and inside I have admin as boolean default to false. How can I fixed my routes in my ruby on rails app for it to give access to certain pages ONLY for the admin who has admin as true.
UPDATE:
I changed followed the first answer I got which said to create a is_admin? method in my controller and specify what actions. However, when I do that, I get a:
undefined method `admin' for nil:NilClass
UPDATE 2:
Products Controller:
class ProductsController < ApplicationController
before_action :is_admin?, only: [:edit, :update, :destroy]
before_action :set_product, only: [:show]
Application Controller:
def is_admin?
if signed_in?
redirect_to root_path unless current_user.admin
end
end
You shouldn't do that in the routes file, the best place to do it's on the controller filtering part. Attention to the :authenticate_user! method being before the is_admin?. Otherwise current_user will be nil.
class PagesController < ApplicationController
before_action :authenticate_user!
before_action :is_admin?, only: [:action1, :action2]
...
private
def is_admin?
unless current_user.is_admin?
flash.alert = "Sorry, you don't have permissions to perform this action."
redirect_to root_path
end
end
end
I recommend you use pundit gem and policies for everything related to authorizations.
Related
I have a controller named HomeController with index and show actions. I want to check if the user subscription has ended and show him a message and redirect to HomeController#index.
Currently i am doing it as below
class HomeController < ApplicationController
before_action :check_if_trial_expired, only: [:index]
before_action :redirect_if_trial_expired, only: [:show]
protected
def check_if_trial_expired
#trial_expired = current_user.trial_expired?
end
def redirect_if_trial_expired
redirect_to home_path if current_user.trial_expired?
end
end
Is there a better way to do this? I want to redirect the user to HomeController#index in case a condition satisfies.
Many Thanks in advance.
You'll at least need the index and show methods defined on the controller; make sure you have them in your routes. I don't think you need to use before_action for the index. Also, you can memoize trial_expired if it is an expensive operation.
class HomeController < ApplicationController
before_action :redirect_if_trial_expired, only: [:show]
def index; end
def show; end
private
def redirect_if_trial_expired
redirect_to home_path if current_user.trial_expired?
end
end
this is my before_action in controller
before_action :redirect_to_home, unless: :logged_in?, only: %i[destroy]
before_action :redirect_to_home, if: :logged_in?, only: %i[new create]
My purpose is redirect to home when call new and create action for authenticated user and destroy for unauthenticated user
this is my redirect_to_home callback
def redirect_to_home
redirect_to root_path
end
this is my logged_in? method
def logged_in?
p 'HELLO FROM LOGGED_IN'
session[:user_id].present?
end
when I ran the destroy test spec nothing printed out to the console but when I swap the line and run the destroy test spec again everything looks fine but new and create test spec are broken.
Do you guys have any ideas?
Thanks
Ref this
Calling the same filter multiple times with different options will not work,
since the last filter definition will overwrite the previous ones.
You can do following
before_action :redirect_to_home, only: %i[new create destroy]
And in controller
def redirect_to_home
if logged_in?
redirect_to root_path
else
redirect_to destroy_path #You have to use actual destroy path here.
end
end
before_action doesn't prevent action to be executed if callback returns false.
You can make another method:
def ensure_user_is_logged_in
unless logged_in?
redirect_to_home
end
Then you can use it before_action like this:
before_action :ensure_user_is_logged_in, only: %i[new, create]
It will redirect to home if the user is not logged in.
You can refer to this for more info:
how to execute an action if the before_action returns false
I have this in my application_controller
class ApplicationController < ActionController::Base
before_action :login_required, :only => 'users/login'
protect_from_forgery with: :exception
protected
def login_required
return true if User.find_by_id(session[:user_id])
access_denied
return false
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
I want to use the login_required for each controller def method
Is there a better way instead of this?
class UsersController < ApplicationController
before_action :set_user, :login_required, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
login_required
#users = User.all
end
def new
login_required
#user = User.new
end
end
Is there a better way to include login_required for all controllers methods since before_action doesn't seem to work?
I don't know the motivation of your logic, so I'll just focus on how you can solve this particular problem.
You can do something like this:
In your application controller:
class ApplicationController < ActionController::Base
before_action :login_required
private
def login_required
current_params = params["controller"] + "/" + params["action"]
if current_params == "users/new" or current_params == "users/index"
return true if User.find(session[:user_id])
access_denied
return false
end
end
def access_denied
flash[:error] = 'Oops. You need to login before you can view that page.'
redirect_to users_login_path
end
end
The login_required method will just run only on users controller's index and new action, for the rest, it'll just ignore. Also you can just use User.find() and no need to use User.find_by_id()
Now, in your users_controller.rb, you don't need to mention anything about login_required, everything will happen already in application_controller before coming here.
class UsersController < ApplicationController
before_action :set_user, :only => 'users/login'
#before_action only: [:show, :edit, :update, :destroy, :new]
def index
#users = User.all
end
def new
#user = User.new
end
end
Firstly, I'm going to suggest that you use devise for authentication, it's a lot more secure and should deal with this for you.
As for your problem, you should be able to specify the before_action like this:
before_action :set_user, :login_required, only: [:new]
Which you can put in your UserController. However if you want this globally, just put it in the ApplicationController, without the only: key.
If you want to require login for all pages except /users/login, then you almost have it right except you are specifying only: when you should be using except::
class ApplicationController < ActionController::Base
before_action :login_required, except: 'users/login'
...
end
This configuration will be applied to all sub-classes of ApplicationController as well.
I have the following before_actions in my ApplicationController:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
before_action :logged_in_user
before_action :admin_user
private
# Confirms a logged-in user.
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
And I have skip_before_actions in my PostsController:
class PostsController < ApplicationController
skip_before_action :logged_in_user, except: [:new, :edit, :update, :destroy]
skip_before_action :admin_user, except: [:new, :edit, :update, :destroy]
before_action :find_post, only: [:edit, :update, :show, :delete]
The find_post action works perfectly, and the method is in that controller. I want to be able to access posts#index and posts#show without logging in or being an admin, but everything I try, it won't skip those actions, and I'm redirected to log in. It's working in my other controllers. I worked around the index by routing to static_pages#home and defining that action to render posts/index with skip_before_action in the static_pages controller. In a previous attempt, I tried not putting the before action in ApplicationController, and just calling before_action in PostsController and UsersController when I need it, but PostsController wasn't doing that either. I wrote a test action to just redirect_to in PostsController and tried calling before_action :test_action on everything, and it wouldn't do that either. What am I missing?
According OOP, logged_in_user method is private so that in PostsController, skip_before_action doesn't find this method.
After Josh Brody took a look at my git file, I found the problem... apologies to everyone, but it seems there was an issue with my IDE instead. It wasn't properly saving my posts controller. Reboot the computer, and everything worked. I went back and switched the logic to instead use before_action. Ha, wasted six hours of my day banging my head against the computer thinking it was my code. Good learning experience.
Should I load and check the presence of the DB source in before filter?
In our application, we always load and check presence of DB source which has an id passed in by params. I'm not sure if this is a good pattern.
like:
before_action :set_org
private
def seg_org
#org ||= Organization.find params[:id]
resource_not_found unless #org
end
Yes, finding a record and setting it as an instance variable is a common convention for controller filters. Generally though, any piece of code that gets run for multiple actions is a good candidate. Say you want to redirect to the log in page if the current user is not logged in.
class UsersController < ApplicationController
before_action :require_login
before_action :set_user, only: [:show, :edit, :update, :destroy]
private
def require_login
unless logged_in?
flash[:error] = "You must be logged in to access this section"
redirect_to new_login_url # halts request cycle
end
end
def set_user
#user = User.find(params[:id])
end
end