Authorize controller actions with cancan in rails - ruby-on-rails

i have some actions in controller and put authorize for 3 actions,
class TestController
def test1
authorize! :view, :testcase1
#do things1
end
def test2
authorize! :view, :testcase2
#do things2
end
def test3
authorize! :view, :testcase3
#do things3
end
end
and in the corresponding action's view, i am checking like this
if can? :view, :test_case1
#do things
end
So the problem is i am calling the authorize in 3 functions, can i put it like a single, same as before_filter

I'm not quite sure what you're getting at, but load_and authorize_resource is typically the best way to go. If you need to skip a controller action, you can do something like this:
class TestController
load_and_authorize_resource
skip_authorize_resource :only => :new
end

Add this to the class
class TestController
load_and_authorize_resource
end
and you dont need to use authorize in every action
https://github.com/ryanb/cancan/wiki/authorizing-controller-actions

Related

How to handle concerns shared by module namespaced controllers

I have a controller that looks something like this:
module Guardians
class StudentsController < ApplicationController
def show
#student = Student.find params[:id]
authorize #student, policy_class: StudentPolicy
end
end
end
Because the controller is within a module, the policy class which is used is Guardians::StudentPolicy, which is what I want.
However I now have another controller:
module Teachers
class StudentsController < ApplicationController
def show
#student = Student.find params[:id]
authorize #student, policy_class: StudentPolicy
end
end
end
Here the policy_class used is Teachers::StudentPolicy
But because the show method itself is identical, ideally I would like to dry this up with a concern. However if I cannot seem to do this, as
authorize #student, policy_class: StudentPolicy
will no longer automatically call the namespaced policy class when it is called from inside the concern.
What is the DRYest way to achieve this?
If you create a concern like this:
module StudentsConcern
extend ActiveSupport::Concern
included do
before_action :find_and_authorize_student, only:[:show]
end
private
def find_student_and_authorize
#student = Student.find(params[:id])
if self.class.name.deconstantize == 'Teachers'
authorize #student, policy_class: Teachers::StudentPolicy
else
authorize #student, policy_class: Guardians::StudentPolicy
end
end
end
And then include this in both of the controllers and clear the show actions.

Rails: Do something once per page reload

How to invoke method once per page reload? defining before_filter in ApplicationController is failed because of multiple controllers used to perform action
When you say multiple controllers are used in one action, could you be more specific? Normally a before_filter in the controller responsible for the action would suffice.
If you want your before filter to affect specific methods in multiple controllers then place the method itself in the ApplicationController but the before_filter in each of the controllers that contain the action.
class ApplicationController
def foo
#bar = 'bar'
end
end
class UserController
before_filter :foo, :only => [:method1]
def method1
...
end
def method2
...
end
end
class StuffController
before_filter :foo, :only => [:method2]
def method1
...
end
def method2
...
end
end
class UnimportantController
# No before filter, neither of these methods will call :foo
def method1
...
end
def method2
...
end
end

Code in each controller method

I have a controller and every method of it starts with the following code:
#user = UserData.find_by_login(session[:cuser])
if #user == nil
redirect_to(:controller=> 'user_data', :action=> 'login')
return
end
I'm just wondering if it is possible to avoid code duplication in this case ?
Yes, use a before_filter
class YourController < ApplicationController
before_filter :check_user
def check_user
..
end
end
Absolutely.
class MyController < ApplicationController
before_filter :ensure_logged_in
# actions here.
def ensure_logged_in
#user = UserData.find_by_login(session[:cuser])
if #user == nil
redirect_to(:controller=> 'user_data', :action=> 'login')
end
end
end
You shouldn't need to worry about the 'return', as rails will bail out of the filter pipeline once the redirect happens.
To avoid duplication you just need to add before_filter in every controller where you want to check user authentication.
class SomeController < ApplicationController
before_filter :authenticate_user
end
then add your user authentication logic in application controller something like this,
class ApplicationController < ActionController::Base
private
def current_user
#current_user ||= UserData.find_by_login(session[:cuser]) if session[:cuser]
end
helper_method :current_user
def authenticate_user
redirect_to({:controller=> 'user_data', :action=> 'login'}, :alert => "Not authorized") if current_user.nil?
end
end
You can use current_user helper method in every controller to get current user.
Try to use before filter. This should be fine

undefined local variable or method `authenticate_admin'

I'm trying to view my new action in my blogs controller, but I keep getting the following error message:
NameError in BlogsController#new
undefined local variable or method `authenticate_admin'
In my blogs controller, I want to restrict the new action to admins only (admins and users are two different models). I was able to get this to work in another model. If I'm not mistaken, helpers are open to all classes. I also tried to add the code from my admins helper to the blogs helper, but that didn't work.
Why can't my blogs controller use my authenticate_admin method?
Thanks for lookign :)
Here are relevant files:
blogs_controller.rb
class BlogsController < ApplicationController
before_filter :authenticate_admin, :only => [:new]
def new
#blog = Blog.new
#title = "New Article"
end
end
admins_helper.rb
def authenticate_admin
deny_admin_access unless admin_signed_in?
end
def deny_admin_access
redirect_to admin_login_url, :notice => "Please sign in as admin to access this page."
end
def admin_signed_in?
!current_admin.nil?
end
def current_admin
#current_admin ||= Admin.find(session[:admin_id]) if session[:admin_id]
end
In this case Helpers are accessible in your Views not in Controllers.
Solution is to move your methods from admins_helper.rb to ApplicationController and set them as helper_methods. You will be able to access them in your Controllers and Views.
Example:
class ApplicationController < ActionController::Base
# Helpers
helper_method :authenticate_admin
def authenticate_admin
deny_admin_access unless admin_signed_in?
end
end
Read documentation about helper_method:
http://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html#method-i-helper_method

Rails Cache Sweeper

I'm trying to implement a Cache Sweeper which would filter a specific controller action.
class ProductsController < ActionController
caches_action :index
cache_sweeper :product_sweeper
def index
#products = Product.all
end
def update_some_state
#... do some stuff which doesn't trigger a product save, but invalidates cache
end
end
Sweeper Class:
class ProductSweeper < ActionController::Caching::Sweeper
observe Product
#expire fragment after model update
def after_save
expire_fragment('all_available_products')
end
#expire different cache after controller method modifying state is called.
def after_update_some_state
expire_action(:controller => 'products', :action => 'index')
end
end
The ActiveRecord callback 'after_save' will work fine, but the callback on the controller action 'after_update_some_state' never seems to be called.
Looks like I was just missing the controller name when trying to get the callbacks for controller actions working. My Sweeper should be:
class ProductSweeper < ActionController::Caching::Sweeper
observe Product
#expire fragment after model update
def after_save
expire_fragment('all_available_products')
end
#expire different cache after controller method modifying state is called.
def after_products_update_some_state
expire_action(:controller => 'products', :action => 'index')
end
#can also use before:
def before_products_update_some_state
#do something before.
end
end
I think your sweeper should look like this:
class ProductSweeper < ActionController::Caching::Sweeper
observe Product
def after_save(product)
expire_cache(product)
end
def after_destroy(product)
expire_cache(product)
end
private
def expire_cache(product)
expire_fragment('all_available_products')
expire_page(:controller => 'products', :action => 'index')
end
after_index isn't a callback unless you define it.
In the controller you should specify those actions in which the sweeper should be triggered, in a restful way those actions should be create, update, destroy, so your controller declaration should look like:
class ProductsController < ActionController
caches_action :index
cache_sweeper :product_sweeper, :only => [:create, :update, :destroy]
def index
#products = Product.all
end
def create
#product = Product.new(params[:product])
if #product.save # triggers the sweeper.
# do something
else
# do something else
end
end
# update and stuff ...
end
I hope it helps you!

Resources