I have the following code:
#/app/models/users/user.rb
class Users::User < ActiveRecord::Base
has_many :phones, class_name: "Users::Phone"
end
#/app/models/users/phone.rb
class Users::Phone < ActiveRecord::Base
belongs_to :user, class_name: "Users::User"
attr_accessible :phone
end
#/app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
can :read, :all
unless user.nil? #logged_in
if user.is? :admin
can :manage, :all
else
can :create, Users::Phone, user_id: user.id
end
end
end
end
I wanna check ability for create only their own phones for users
#/app/views/users/users/show.html.slim
- if can? :create, Users::Phone.new
a[href="#{new_user_phone_path(#user)}"] Add phone
Thats does not work, because I should pass user_id to phone model (like Users::Phone.new user_id: user.id), but I can't do that since Phone's mass assignment.
So how I can check :create phones ability for users?
I do something similar to this in my app by making Ability aware of the underlying parameter structure. You have a few options depending on your requirements. So in your controller you'd have approximately:
def create
#phone = Users::Phone.new(params[:users_phone])
# Optional - this just forces the current user to only make phones
# for themselves. If you want to let users make phones for
# *certain* others, omit this.
#phone.user = current_user
authorize! :create, #phone
...
end
then in your ability.rb:
unless user.nil? #logged_in
if user.is? :admin
can :manage, :all
else
can :create, Users::Phone do |phone|
# This again forces the user to only make phones for themselves.
# If you had group-membership logic, it would go here.
if phone.user == user
true
else
false
end
end
end
end
Related
I want the Admin user to not have the ability to create users with the role of Super Admin but still be able to create other Admins and Regular Users. How do I accomplish this? Here is my Ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
if user.super_admin?
can :manage, :all
elsif user.admin?
can :manage, [Article, Comment]
can [:destroy, :update], User, :role_id => 2 # If Admin
can [:destroy, :update], User, :role_id => 3 # If User
can :read, User
can :create, User
elsif user.user_regular?
#cannot :read, ActiveAdmin::Page, :name => "Dashboard"
#can :manage, :all
end
end
end
Use cannot with in admin block like cannot :creat, User, :role_id => 1 # let 1 is super admin role id. You can get more info about combine ability at here
I have been trying to get cancan to work all day. I have started over several times using different tutorials but I keep getting the same errors.
It's pretty simple, I have User account (created with Devise) which can have one role, either Admin or User. Here is the ability class:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.role? :admin
can :manage, :all
end
end
end
In the profile controller I have the line load_and_authorize_resource, the class User contains ROLES = %w[admin user]. Going to http://localhost:3000/profiles/ gives me the error
wrong number of arguments (2 for 1) in app/models/ability.rb:6:ininitialize'`
Using the alternative method user.admin? gives
undefined method `admin?' for #<User:0x5b34c10>
Googling the error above gives many results so I'm not the only person having this problem, but none of the solutions have worked for me.
And yes I have added the role column to the User table
class AddRoleToUsers < ActiveRecord::Migration
def change
add_column :users, :role, :string
end
end
added the Gem, ran bundle install and restarted the server.
Providing you have the following this should work:
Ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
# raise user.role?(:administrator).inspect
if user.role? :administrator
can :manage, :all
can :manage, User
elsif user.role? :user
can :read, :all
end
end
end
RoleUser.rb
class RoleUser < ActiveRecord::Base
# attr_accessible :title, :body
belongs_to :user
belongs_to :role
end
Role
class Role < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
User.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
def role?(role)
self.roles.find_by_name(role.to_s.camelize)
end
You need this as you are defining your roles, finding them, converting the role to a string and camelizing it.
seeds.rb
%w(Employee Administrator).each { |role| Role.create!(:name => role)}
creates an array User and Administrator.
Should you follow these steps correctly you this should work. And also ensure you have the following migrations:
class UsersHaveAndBelongsToManyRoles < ActiveRecord::Migration
def self.up
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
end
def self.down
drop_table :roles_users
end
end
Then in your view you should use the can? by doing something like
<% if can? :manage, User %>
.......
....
...
<% end %>
i'm trying to solve an user's ability problem with cancan gem.
company and users are associated through user_company_assignment in such a way that a company has many user and the users has and belongs to many companies
I would like to restrict the show action of a company only to those users associated with the company. below there is the code of the two models and a snip of ability.rb with the initialize role inheritance and the method for the seller user, but this is not working, it show me always the company detail.
Company.rb
has_many :user_company_assignments
has_many :user, :through => :user_company_assignments
User.rb
has_many :user_company_assignments
has_many :companies, :through => :user_company_assignments
Ability.rb
def initialize(user)
#user = user || User.new # for guest
#user.roles.each { |role| send(role.name.downcase) }
end
def seller
can :manage, :all
cannot :destroy, :all
can :show, Company do |company|
company.user_ids.include? #user.id
end
end
Your error is due to ability precedence: https://github.com/ryanb/cancan/wiki/Ability-Precedence
This line overrides all following abilities: can :manage, :all
Since you've already stated that a seller can manage all, the seller can perform any kind of action on a Company, regardless of the other can statement.
One solution would be to use cannot, as you did with :destroy. It will override the :manage, :all clause.
def seller
can :manage, :all
cannot :destroy, :all
cannot :show, Company do |company|
!company.user_ids.include? #user.id
end
end
You need to call the load_and_authorize_resource method in your controller.
Ok jesper, i have changed my ability.rb and it works but i'm not sure that this is the best method to set the ability, it is strange the i need to specify each Models that a seller has the permission to the show action. tell me if is it the best way to do that:
Ability.rb
def seller
can [:index, :create], :all
cannot :destroy, :all
can :show, Company do |company|
company.user_ids.include? #user.id
end
can :show, [Report, Client]
end
I'm having some trouble defining permissions for my albums#index action. The path to it is /user/:user_id/albums - this is the ability for my :show action (:read => [:index, :show]) which is working well. The path to it is /user/:user_id/albums/:id/.
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
I'm not sure how to write a similar rule for the index action, or if I even want to use CanCan here. The rule is:
current_user MUST be .friends_with?(user_id) to view any albums belonging to user_id.
user_id is of course taken from params[:user_id]. Note: /user/eml/albums/ would be the path, I'm not fetching users by their .id but by their .username!
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new # for guest, probably not needed
#user.roles.each { |role| send(role) }
end
def user
can :read, Album do |album|
#user.friends_with?(album.user_id)
end
can :manage, Album do |album|
album.user_id == #user.id
end
end
end
UPDATE:
Turns out the solution is really simple, I was just not paying attention to my routes:
resources users do
resources albums
end
In the controller that becomes pretty easy then:
load_and_authorize_resource :user, :find_by => :username
load_and_authorize_resource :album, :through => :user
And the rule:
can :read, Album, :user_id => #user.friend_ids # I don't need #user.id
I'm not perfectly happy with it though, as using the user.friends_with?(other_user) method would be much quicker and doesn't have to fetch (potentially) a thousand ids from the database. Any other solution is most welcome.
On IRC you told me that the .roles_mask isn't important... then shouldn't this be something like:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :read, Album, :user_id => [user.id] + user.friend_ids
can :manage, Album, :user_id => user.id
end
end
end
I have Projects resource which is nested in Users resource.
My Cancan Ability class is:
class Ability
include CanCan::Ability
def initialize(user)
#everyone
can :read, Project
if user.blank?
# guest user
...
else
#every signed in user
case user.role
when User::ROLES[:admin]
#only admin role user
can :manage, :all
when User::ROLES[:member]
#only member role user
can :update, User, :id => user.id
can [:create, :update, :destroy], Project, :user_id => user.id
else
end
end
end
end
And Projects controller:
class ProjectsController < ApplicationController
load_and_authorize_resource :user
load_and_authorize_resource :projects, :through => :user, :shallow => true
...
end
I have few questions:
Is it possible to deny :read User and allow to :read Project, so that everyone could access /users/10/projects, but not /users/10 or /users?
How can I deny user accessing :new action with other user_id? For example, if I add
#everyone
can :read, User
can :read, Project
this code allows user with id 42 to access /user/41/projects/new.
Solved it by doing:
class Ability
include CanCan::Ability
def initialize(user)
#everyone
can :read, Project
can :read, User # required to access nested resources
cannot :index, User
cannot :show, User
if user.blank?
# guest user
...
else
#every signed in user
case user.role
when User::ROLES[:admin]
#only admin role user
can :manage, :all
when User::ROLES[:member]
#only member role user
can :update, User, :id => user.id
can :manage, Project, :user => { :id => user.id }
else
end
end
end
end