Rails dynamic search engines based on role
I am looking for the best way to build a clean way to build role / author based crawlers?
In my model, the model user
can have one of several (administrator-defined) roles such as Administrator, Regional Manager, Sales Assistant:
An example . Given a user with the role of Regional Manager and joined to Region A, I would like to be able to query what other users she might see, for example:
regional_manager_for_region_a.users
=> [...] # Array of users joined to region a
regional_manager_for_region_b.users(:all, conditions => { :active => true })
=> [...] # Array of active users joined to region b
administrator.users
=> [...] # Array of all users in system
Thanks, really appreciate any help!
source to share
I think you need to create an authorization mechanism.
The best gem I know for this is declarative_authorization . I've personally used it in a production environment and I'm happy with it. There's a railscast about this too.
The idea is that you declare config/authorization_rules.rb
"roles and permissions" in one specific file ( ). You say things like "the manager can only read the clients associated with him" or "the administrator can read and write all users". In your case it will look like this:
authorization do
role :guest do
# actions here can be done by everyone, even not logged in people
end
role :user do
includes :guest
# actions here can be done by logged people
end
role :manager do
includes :user #managers do everything users do, plus:
has_permission_on :sales_region, :to => :read do
if_attribute :id => is_in {user.sales_region_ids}
end
has_permission_on :users, :to => [:update, :read] do
if_attribute :id => is {user.user_ids_by_sales_region} #defined on the model
end
end
role :admin do
includes :user
has_permission_on [:sales_regions, :users], :to :manage
end
end
privileges do
privilege :manage do
includes :create, :read, :update, :delete
end
end
Once this is indicated, you will change your models to use declarative_authorization
. Also, define a methoduser_ids_by_sales_region
class User < ActiveRecord::Base
using_access_control # this enables DA
def users_by_sales_region
sales_regions.collect{ |sr| sr.users }.flatten.uniq
end
def user_ids_by_sales_region
users_by_sales_region.collect{ |u| u.id }
end
end
You should also have a method current_user
and a way to get the current user role. See the Providing Requirements for Plugins section on the readme .
Then you can do what you want with with_permissions_to
:
manager = User.find(...)
manager.users.with_permissions_to(:read) # the users from his region
manager.users.with_permissions_to(:read).find(:all, conditions => { :active => true })
manager.users.with_permissions_to(:write) #returns no users, managers can't edit them
admin = User.find(...)
admin.users.with_permissions_to(:write) #will return all users
This means that it takes a little effort in the beginning, but will greatly simplify the application later. In addition, you have additional functionality such as hiding / showing parts of the views depending on the permissions of the current user, as well as denying access to certain controller actions.
Also, it should work just fine with pagination etc.
There's another declarative authorization graph called cancan . I have no experience with this, but if done by Ryan Bates it should be fine (he got a railscast for him too). However, I don't think it allows the model extension that you need right now.
source to share
My answer below is suitable for simple searchers; however, it is not very flexible and not plugin compatible will_paginate
. Does anyone know how to best use the users @current_user
who can manage?
thank
Just answered my own question by overriding the default association extension as shown below. It would be great to know the comments or alternatives though!
class User < ActiveRecord::Base
has_many :users do
def find(*args)
scope = args.first || :all
options = args.extract_options!
return User.find(args.first, options) if proxy_owner.admin?
users = []
proxy_owner.sales_regions.collect do |sales_region|
users += sales_region.users
end
users.uniq
end
end
end
source to share
To follow up on my comment on egarcia's answer, I ended up settling on an ad named_scopes
on limited models. For example:
# app/models/account.rb
class Account < ActiveRecord::Base
named_scope :visible_to, lambda { |user|
return {} if user.can_see_all_accounts?
{ :conditions => ['sales_area_id IN (?)', user.sales_area_ids] }
}
end
# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
def index
@accounts = Account.visible_to(@current_user)
...
end
end
source to share