I have users in my system that can elect to 'hibernate', at which point they can remove themselves and all of their associated records entirely from the system. I have queries all over my site that search within the User table and its associated tables (separated by as many as 5 intermediate tables), and none explicitly test whether the user is hibernating or not.
Is there a way to redefine the User set to non-hibernating users only, so all my current queries will work without being changed individually?
How can I most elegantly accomplish what I'm trying to do?
This is typically done with default scopes. Read all about them
Code from Ryan's site:
class User < ActiveRecord::Base
default_scope :hibernate => false
end
# get all non-hibernating users
#users = User.all
# get all users, not just non-hibernating (break out of default scope)
#users = User.with_exclusive_scope { find(:all) } #=> "SELECT * FROM `users`
Related
I have three types of users "Admin," "Manager," and "Employee." Also, I have an accounts model. All of them can show these accounts. I want to limit employees' access to these accounts. I put the Ids of these accounts in an array and did a limit like the code below. My question is, I had 15 endpoints related to the accounts, Is the best way to do it like this, or may there be a solution that does it without editing all these endpoints?
def index
if #current_user.user_type == 'Admin'
#accounts = Accounts.all
#accounts = optional_paginate(#accounts)
elsif #current_user.user_type == 'Employee'
#accounts = Account.where(id: ACCOUNTS_IDS)
#accounts = optional_paginate(#business_accounts)
else
#business_accounts = optional_paginate(Account.all.includes(:account_managers).exclude_pending)
end
end
The ACCOUNTS_IDS is an array of accounts ids that the employees can access to them only.
Your strategy works, but I think you're sensing that it's not ideal.
You had to hard-code an array of Account IDs from your database, which is never a good idea. If you ever needed to migrate or seed or recreate the database, those IDs could change.
You have this code repeated 15 times(so it's not DRY)
If an account with an id in ACCOUNTS_IDS is ever deleted, imagine what will happen to each of these controller actions.
Adding to or removing from the accounts (ACCOUNTS_IDS) that Employees can see requires you to change your codebase and push those changes.
I'd suggest doing a few things differently:
1. Use an authorization gem
I personally love pundit, but there are other popular options
2. Don't hard-code database information
As mentioned before, the IDs can change and code that relies on a specific record having a specific ID (or relying on that specific record to exist) is brittle.
It's much better to add a column to your table to control this.
Here's an example migration:
add_column :accounts, :restricted, :boolean, default: true, null: false
ACCOUNTS_IDS = [1, 2, 3, 4] #just an e.g.
Account.where(id: ACCOUNTS_IDS).update_column(restricted: false)
:restricted could also be something easier to understand, e.g. :visible_to_employees
Now you can make scopes in your Account model:
# /models/account.rb
class Account < ApplicationRecord
scope :restricted, -> { where(restricted: true) }
scope :unrestricted, -> { where(restricted: false) }
...
end
This allows you to do Account.all to get everything, Account.restricted to get some, and Account.unrestricted to get the others.
This also allows you to add a view form where accounts could be made visible to Employees without having to change the code of your application.
3. Do this in the model, not in the controller
Lean towards having more code in your models and less code in your controllers.
(this will also help you when you implement a Pundit scope)
# models/user.rb
class User < ApplicationRecord
...
def authorized_accounts
case user_type
when 'Admin'
Account.all
when 'Employee'
Account.unrestricted
else
Account.all.includes(:account_managers).exclude_pending
end
end
Now your controller can be greatly simplified:
def index
#accounts = optional_paginate(#current_user.authorized_accounts)
end
I am using rails 4 with devise gem and following this article -"https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-Role", I created a model for admin also. Now I have both admin and user model. How can I get the current_admin as in application_helper, these are present:-
def resource_name
:user
end
def resource
#resource ||= User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
Please help to main a admin session. Thanks in advance
Devise's current_X helper methods are defined like this:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
where mapping is the role type. *
As such, you should be able to access the current admin using:
current_admin
If this doesn't work, I would recommend checking to make sure that devise has been properly instanciated on the Admin model, as follows:
class Admin < ActiveRecord::Base
devise :database_authenticatable # additional attributes, i.e. :trackable, :lockable
end
*Source: Line 106 of https://github.com/plataformatec/devise/blob/master/lib/devise/controllers/helpers.rb
If you continue to experience issues with this, it might be worth reconsidering whether you need to separate administrators and users using different models, instead of simplifying both the model and the implementation by adding an administrative attribute to the users model.
Edit:
To give you an idea as to how much of a negligible performance difference there would be querying a single users table for administrative users vs having two tables, I seeded a MySQL database (running on XAMPP for OSX, so it is by no means heavily optimized) with 1000 user records, with only 2 of the users having 'isAdmin' set to true.
Query using SQL (Selecting Admins from a pool of 1000 Users):
SELECT * FROM `users` WHERE isAdmin = 1
Result:
Showing rows 0 - 1 (2 total, Query took 0.0004 seconds.)
Query using SQL (Selecting Admins from an Admin table):
SELECT * FROM `administrators`
Result:
Showing rows 0 - 16 (17 total, Query took 0.0003 seconds.)
This is by no means an incredibly thorough example - it was just to provide an idea as to the minimal difference when used like this. Active Record may be slightly quicker/slower, but I can't imagine it would be too different to the results above.
Personally, I find it easier to add an 'isAdmin' (or similar) attribute to my users model, like this:
Active Record migration (db\migrate\TIMESTAMP_create_users.rb)
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.boolean :isAdmin, default: false
...
end
end
end
and then filter between Users and Administrators like so:
Users, except Admins
users = User.where(:isAdmin => false)
All users
allUsers = User.all
Admins only
admins = User.where(:isAdmin => true)
So for some reason, my client will not drop inactive users from their database. Is there a way to globally exclude all inactive users for all ActiveRecord calls to the users table?
EX: User.where("status != 'Inactive'")
I want that to be global so I don't have to include that in EVERY user statement.
Yes, you can set a default scope:
class User < ActiveRecord::Base
default_scope where("status != 'Inactive'")
end
User.all # select * from users where status != 'Inactive'
... but you shouldn't.
It will only lead to trouble down the road when you inevitably forget that there is a default scope, and are confused by why you can't find your records.
It will also play havoc with associations, as any records belonging to a user not within your default scope will suddenly appear to belong to no user.
If you had a simple setup with posts and users, and users had a default scope, you'd wind up with something like this:
# we find a post called 1
p = Post.first # <#post id=1>
# It belongs to user 2
p.user_id # 2
# What's this? Error! Undefined method 'firstname' for `nil`!
p.user.first_name
# Can't find user 2, that's impossible! My validations prevent this,
# and my associations destroy dependent records. Can't be!
User.find(2) # nil
# Oh, there he is.
User.unscoped.find(2) <#user id=2 status="inactive">
In practice, this will come up all the time. It's very common to find a record by it's ID, and then try to find the associated record that owns it to verify permissions, etc. Your logic will likely be written to assume the associated record exists, because validation should prevent it from not existing. Suddenly you'll find yourself encountering many "undefined method blank on nil class" errors.
It's much better to be explicit with your scope. Define one called active, and use User.active to explicitly select your active users:
class User < ActiveRecord::Base
scope :active, -> where("status != 'Inactive'")
end
User.active.all # select * from users where status != 'Inactive'
I would only ever recommend using a default_scope to apply an order(:id) to your records, which helps .first and .last act more sanely. I would never recommend using it to exclude records by default, that has bitten me too many times.
Sure, in your model define a default scope
see here for more info
eg
class Article < ActiveRecord::Base
default_scope where(:published => true)
end
Article.all # => SELECT * FROM articles WHERE published = true
As an alternative to #meagar's suggestion, you could create a new table with the same structure as the Users table, called InactiveUsers, and move people into here, deleting them from Users when you do so. That way you still have them on your database, and can restore them back into Users if need be.
I want to make a record management system. The system will have 4 different user roles: Admin, Viewer, Editor and Reviewer.
While the first two are easy to implement using gems such as cancan and declarative authorization, the other two are not so simple.
Basically each new record is created by an Admin (only an Admin can create new records), and should have its own separate Editor and Reviewer roles. That is, a user can be assigned many different roles on different records but not others, so a user might be assigned Editor roles for Record A and C but not B etc.
Editor: can make changes to the record, and will have access to specific methods in the controller such as edit etc.
Reviewer: will be able to review (view the changes) made to the record and either approve it or submit comments and reject.
Viewer: Can only view the most recent approved version of each record.
Are there any ways of handling such record-specific user roles?
This can be accomplished without too much effort with the cancan gem and a block condition. A block condition checks for authorization against an instance. Assuming your Record class had an editors method that returns an array of authorized editors the cancan ability for updating a Record might look something like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
...
can :update, Record do |record|
record.editors.include?(user)
end
...
end
end
See "Block Conditions" on the CanCan wiki:
https://github.com/ryanb/cancan/wiki/Defining-Abilities
Update
Storing which users have which access to which records could be done many ways depending on your specific needs. One way might be to create a model like this to store role assignments:
class UserRecordRoles < ActiveRecord::Base
# Has three fields: role, user_id, record_id
attr_accessible :role, :user_id, :record_id
belongs_to :user_id
belongs_to :record_id
end
Now create a has_many association in the User and Record models so that all role assignments can be easily queried. An editors method might look like this:
class Record < ActiveRecord::Base
...
has_many :user_record_roles
def editors
# This is rather messy and requires lot's of DB calls...
user_record_roles.where(:role => 'editor').collect {|a| a.user}
# This would be a single DB call but I'm not sure this would work. Maybe someone else can chime in? Would look cleaner with a scope probably.
User.joins(:user_record_roles).where('user_record_roles.role = ?' => 'editor')
end
...
end
Of course there are many many ways to do this and it varies wildly depending on your needs. The idea is that CanCan can talk to your model when determining authorization which means any logic you can dream up can be represented. Hope this helps!
I have some records in my database which only the owning users, or administrators are allowed to view/edit etc.
What is the best way to secure these records on an application level so that only these people can see these records? I could do this manually with conditions, but I would need to ensure I use the same conditions on every single find().
Any ideas?
Scoping a finder to the current_user is useful in most instances, but does not account for a user being an administrator and having access to objects to which it is not directly associated.
Create a named scope within the model to restrict the selection to records owned by the user or any if the specified user is an administrator. The User model must implement a method called "is_admin?" that returns true if the User is considered an admin.
This way you can call:
my_widgets = Widget.accessible_by(user).find(:all, :conditions => ["created_at > ?", 1.day.ago.to_s(:db)])
class Widget
named_scope :accessible_by, lambda {|u|
conditions = nil
unless u.is_admin?
conditions = ["widgets.user_id = :user_id", {:user_id => u.id}]
end
return {:conditions => conditions}
}
end
I find the best way is to avoid finders on the actual model like this...
SecretRecord.find(:id, :conditions => 'user = ?', current_user.id)
and instead use the collections off the user object
class User
has_many :secret_records
end
current_user.secret_records.find(id)
This automatically scopes the select to those secret records that belong to the current user.
I'm assuming that you have a variable called current_user of type User provided by your authentication system (such as restful_authentication)
Put a filter in front of all controllers that can be used to access this information
current_user.admin? or params[:person_id].to_i == current_user.person.id
If this condition is not met redirect them somewhere else