Writing custom method using Devise's current_user? - ruby-on-rails

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

Related

Is it possible to use single method to authorise multiple controller action in Rails Pundit?

I am created new rails application and I want to restrict user actions based on only one condition like record can be editable by owner(created_by) and sub-owner(Added by owner). I have models like App, User and controller like AppController. In AppController I have more than one actions like index, create, show, update, delete. I have one policy like AppPolicy. Here I need to create only one method to verify all actions but by default each action requires another method like action_name? in policy class.
Example
Existing code:
class AppPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope
end
end
def action1?
record.users.include? (user)
end
def action2?
record.users.include? (user)
end
def action3?
record.users.include? (user)
end
end
From above code we can see a same condition reside in all methods. I need to use only one method to verify action1, action2, action3. I don't know this is possible or not in Pundit.
I know this is an old question but I just had the same problem.
I can think about 2 solutions:
solution 1
When you know all the actions that could be called.
You can use define_method, like this
[:action1?, :action2?].each do |m|
define_method(m) { record.users.include? (user) }
end
solution 2
When you don't know all the actions. (this could be dangerous)
You can use a combination of method_missing and respond_to_missing. The latter is needed since pundit will call internally respond_to before calling the corresponding method of the policy.
Example:
def method_missing(m, *args, &block)
record.users.include? (user)
end
def respond_to_missing?(method_name, include_private = false)
true #Here it would be better to add some conditions
end
You can use cancan (or cancancan) gem rubygems link
You can create the ability configuration file with
rails g cancan:ability
The authorize! method in your controller will raise an exception if the user is not able to perform the given action, so call it on before_action callback.
Documentation here

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).

Ruby - How to access module's methods?

I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.

Determine the domain in an ActiveRecord model

I am in the middle of migrating my application from using subdirectories for userspace to subdomains (ie. domain.com/~user to user.domain.com). I've got a method in my user class currently to get the "home" URL for each user:
class User
def home_url
"~#{self.username}"
# How I'd like to do it for subdomains:
#"http://#{self.username}.#{SubdomainFu.host_without_subdomain(request.host)}"
end
end
I'd like to update this for subdomains, but without hardcoding the domain into the method. As you can see, I am using the subdomain-fu plugin, which provides some methods that I could use to do this, except that they need access to request, which is not available to the model.
I know it's considered bad form to make request available in a model, so I'd like to avoid doing that, but I'm not sure if there's a good way to do this. I could pass the domain along every time the model is initialized, I guess, but I don't think this is a good solution, because I'd have to remember to do so every time a class is initialized, which happens often.
The model shouldn't know about the request, you're right. I would do something like this:
# app/models/user.rb
class User
def home_url(domain)
"http://#{username}.#{domain}"
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def domain
SubdomainFu.host_without_subdomain(request.host)
end
# Make domain available to all views too
helper_method :domain
end
# where you need it (controller or view)
user.home_url(domain)
If there is such a thing as a canonical user home URL, I would make a configurable default domain (e.g. YourApp.domain) that you can use if you call User#home_url without arguments. This allows you to construct a home URL in places where, conceptually, the "current domain" does not exist.
While molf's answer is good, it did not solve my specific problem as there were some instances where other models needed to call User#home_url, and so there would be a lot of methods I'd have to update in order to pass along the domain.
Instead, I took inspiration from his last paragraph and added a base_domain variable to my app's config class, which is the set in a before_filter in ApplicationController:
module App
class << self
attr_accessor :base_domain
end
end
class ApplicationController < ActionController::Base
before_filter :set_base_domain
def set_base_domain
App.base_domain = SubdomainFu.host_without_subdomain(request.host)
end
end
And thus, when I need to get the domain in a model, I can just use App.base_domain.

How to always set a value for account-scope in Rails?

I'm working on a multi-user, multi-account App where 1 account can have n users. It is very important that every user can only access info from its account. My approach is to add an account_id to every model in the DB and than add a filter in every controller to only select objects with the current account_id. I will use the authorization plugin.
Is this approach a good idea?
What is the best way to always set the account_id for every object that is created without writing
object.account = #current_account
in every CREATE action? Maybe a filter?
Also I'm not sure about the best way to implement the filter for the select options. I need something like a general condition: No matter what else appears in the SQL statement, there is always a "WHERE account_id = XY".
Thanks for your help!
This is similar to a User.has_many :emails scenario. You don't want the user to see other peoples emails by changing the ID in the URL, so you do this:
#emails = current_user.emails
In your case, you can probably do something like this:
class ApplicationController < ActionController::Base
def current_account
#current_account ||= current_user && current_user.account
end
end
# In an imagined ProjectsController
#projects = current_account.projects
#project = current_account.projects.find(params[:id])
I know, I know, if you access Session-variables or Instance variables in your Model you didn't understand the MVC pattern and "should go back to PHP". But still, this could be very useful if you have - like us - a lot of controllers and actions where you don't always want to write #current_account.object.do_something (not very DRY).
The solution I found is very easy:
Step 1:
Add your current_account to Thread.current, so for example
class ApplicationController < ActionController::Base
before_filter :get_current_account
protected
def get_current_account
# somehow get the current account, depends on your approach
Thread.current[:account] = #account
end
end
Step 2:
Add a current_account method to all your models
#/lib/ar_current_account.rb
ActiveRecord::Base.class_eval do
def self.current_account
Thread.current[:account]
end
end
Step 3: Voilá, in your Models you can do something like this:
class MyModel < ActiveRecord::Base
belongs_to :account
# Set the default values
def initialize(params = nil)
super
self.account_id ||= current_account.id
end
end
You could also work with something like the before_validation callback in active_record and then make with a validation sure the account is always set.
The same approach could be used if you always want to add the current_user to every created object.
What do you think?
To answer your second question, check out the new default_scope feature in Rails 2.3.
I understand that you don't want to bother about scoping you account all time. Lets be honest, it's a pain in the a**.
To add a bit magic and have this scoping done seamlessly give a look at the following gem
http://gemcutter.org/gems/account_scopper
Hope this helps,
--
Sebastien Grosjean - ZenCocoon

Resources