How to override the default method in CanCanCan - ruby-on-rails

CanCanCan makes two assumptions about the application:
1*An Ability class which defines the permissions.
2*A current_user method in the controller which returns the current user model.
In my case, i have two models object and subject (I can create, update, destroy) both of them using CRUD operations.
Now i want to restrict access to this object using CanCanCan library authorization. I know that CanCanCan expects a current_user method to exist in the controller and set up some authentication (like Devise). How can i override the default method that expects a current_user and pass it a current_subject, since that one shoud have access to object.

Use an alias to the new helper method:
Simply alias the current_user helper method in the controller to the actual helper method that you are using:
class ApplicationController < ActionController::Base
alias_method :current_user, :current_subject # or whatever your helper method is called - this is untested btw
end
# Source: https://github.com/CanCanCommunity/cancancan/wiki/changing-defaults

Related

rails service object with current_user

I am using devise and have created a service object - everything works as expected and perfectly. However I want to know how I would include the current_user? as the moment I have to always pass in the current_user or user_signed_in? variables which is annoying when I am using the same methods in different classes and views how would I go about doing this?
Currently I have this for testing:
class User
class GetStart
attr_reader :current_user
def initialize(user)
#current_user = user
p current_user
end
end
end
current_user is usually a helper method of ApplicationController which means it will only be accessible within your controllers and views. This is a good thing -- your service object has no business accessing current_user directly since current_user is created from information passed in the request/session which is a concern of the controller.
In my opinion, your approach is correct, it's preferable to pass your user object in as an external object (i.e. dependency injection) than directly couple it to method within a separate object. After all, does your service object need to know or care whether user is from the session?
Also, as was mentioned, keep in mind that attr_reader creates an instance variable for the class, so you should have #current_user = user.

How to make Devise return a subclass of user

I have devise wich authenticates a user. User is split up into subclasses such as User::AsProfile < User or User::AsRegistrant< User. These are not Single Table Inheritance classes, just plain old ruby-objects that inherit from User.
I'd like devises' current_user helper to return an instance of User::AsProfile.
Unfortunately, Device has a lot of metaprogamming hidden away in class_eval so it is hard to understand what is going on.
Can I configure Devise to return another class (User::AsProfile) when running current_user in a controller?
Or must I override current_user in my controller to do so, and if so, how should I call warden to authenticate properly?
I ended up re-implementing current_user in my ApplicationController.
Since I don't need the ability to have one user use multiple sessions, I could simplify the current_user a lot:
class ApplicationController < ActionController::Base
def current_user
return #current_user if #current_user
current = warden.authenticate(scope: :user)
#current_user = current.becomes(User::AsProfile) if current
end
end
By using ActiveRecords becomes, the user is turned into a User::AsProfile. I'd rather not have this conversion and use User::AsProfile#find directly, but that seems only possible whith monkeypatching Warden.
As a sidenote: This problem shows well what happens when a library implements a poorly designed class-inheritance; or, in this case, no inheritance at all. The Devise code simply monkeypatches your ApplicationController and adds methods there. Never a good idea. With hindsight, Devise should simply be some modules (Concerns) that a developer can pick-and-choose and add to his controllers.

Writing custom method using Devise's current_user?

I am using Rails 4.0.2 and Devise 3.2.2 to handle user registration / authentication.
I would like to write a custom method for Devise's current_user , this method is for checking how many times does the current_user sign in. I will be using sign_in_count
Do I write the method in the User model or should I define the method in Users Controller ?
Is it possilbe to write something like the below
def count
user = current_user
user.sign_in_count
end
and call current_user.count ?
Thanks
----edited----
What if I need to add other methods, am I able to add something like the below
#app/controllers/post_controller.rb
before_action :check_time
def check_time
time = User.last_sign_in_at(current_user)
if # something
# do bla bla
end
end
Do I write the method in the User model or should I define the method in Users Controller ?
It depends where (& when) you want to use the method
If you're going to use it as part of the "controller-level" interactivity, you'll want to put it into the UsersController, but if it's going to be used on "model-level" (by multiple controllers / models), you may wish to put it into the model
Something you need to be aware of is that current_user is a helper, and is not available at model level:
#app/controllers/products_controller.rb
def lookup
sign_ins = User.sign_in_count(current_user)
if sign_ins > 10
#do something
end
end
#app/models/user.rb
Class User < ActiveRecord::Base
def self.sign_in_count(user)
user = find(user.id)
user.sign_in_count
end
end
But as stated by #apneadiving, a far more efficient way to do this is to reference the current_user.sign_in_count attribute directly
Update
In reference to your update, you'll be best reading up about class & instance methods
You could perform the method like this:
#app/controllers/post_controller.rb
before_action :check_time
private
def check_time
time = current_user.last_sign_in_at
if # something
# do bla bla
end
end
In my references to model / controller methods - you'd use model methods if you wanted to give standard functionality on an app-wide level (such as User.weight_gain?). If you're using controller-centric data, you're best to keep it all in the controller

using helper_method AbstractController::Helpers::ClassMethods in a model

In my application controller I have the following code:
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user]) if session[:user]
end
I would like to use the "current_user" method in a model. According to the rails API the helper method can be accessed at "AbstractController::Helpers::ClassMethods".
See link:
http://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html
When I add this to my model I get a method not found error:
include AbstractController::Helpers::ClassMethods
Am I missing something in how to include these helper methods?
Thanks!
Short answer: you can't and you shouldn't.
The model doesn't have (and should not have) any visibility of the view and the controller. The clear separation is one of the key principle of the MVC pattern.
If you want a method in your model to have access to the current user, then pass the user when invoking the method.
For instance, assuming you want to pass the user on the Post creation, define a custom method
class Post
def do_something_with_user(user)
# ...
end
end
and call it from the controller
def action
Post.find(...).do_something_with_user(current_user)
end
There are possible workarounds, such as storing the current user into the current thread or in a global variable, but this is gonna break the rules (and you should not break the rules).

Rails convention for method in multiple controllers

I have an app that has users whose profiles are accessible via site.com/username. When choosing a username, I make an AJAX call to a method in my UsersController to make sure the username is available (and check on the back end as well when submitted). I now want to add groups that will also be accessible through site.com/groupname. Since group and user names cannot collide, whatever controller method that responds to the AJAX call will need to check both so the check_username_available and check_groupname_available methods will do the exact same thing. What's the best practice / Rails way to handle this since I don't want to replicate code in both UsersController and GroupsController?
Having a method for each controller seems a bit redundant, even if the functionality is pulled out to a helper, since there will still be two routes that do the same thing. Having a separate controller solves the problem too but not sure this is good Rails practice.
code that is reused can be shared via a module
class UsersController < ActionController::Base
include NameUniqueness
end
class GroupsController < ActionController::Base
include NameUniqueness
end
module NameUniqueness
protected
def check_name
# implementation here
end
end
both controllers will now have access the check_name instance method.
DanPickett's answer is great.
Another choice is to make a class method in the user model and just call it from each controller. Since this name checking seems like a job for the model, that's what I would do.
class User
def self.check(stuff) ...

Resources