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 %>
Related
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
There are different kinds of users in my system. One kind is, let's say, a designer:
class Designer < ActiveRecord::Base
attr_accessible :user_id, :portfolio_id, :some_designer_specific_field
belongs_to :user
belongs_to :portfolio
end
That is created immediately when the user signs up. So when a user fills out the sign_up form, a Devise User is created along with this Designer object with its user_id set to the new User that was created. It's easy enough if I have access to the code of the controller. But with Devise, I don't have access to this registration controller.
What's the proper way to create a User and Designer upon registration?
In a recent project I've used the form object pattern to create both a Devise user and a company in one step. This involves bypassing Devise's RegistrationsController and creating your own SignupsController.
# config/routes.rb
# Signups
get 'signup' => 'signups#new', as: :new_signup
post 'signup' => 'signups#create', as: :signups
# app/controllers/signups_controller.rb
class SignupsController < ApplicationController
def new
#signup = Signup.new
end
def create
#signup = Signup.new(params[:signup])
if #signup.save
sign_in #signup.user
redirect_to projects_path, notice: 'You signed up successfully.'
else
render action: :new
end
end
end
The referenced signup model is defined as a form object.
# app/models/signup.rb
# The signup class is a form object class that helps with
# creating a user, account and project all in one step and form
class Signup
# Available in Rails 4
include ActiveModel::Model
attr_reader :user
attr_reader :account
attr_reader :membership
attr_accessor :name
attr_accessor :company_name
attr_accessor :email
attr_accessor :password
validates :name, :company_name, :email, :password, presence: true
def save
# Validate signup object
return false unless valid?
delegate_attributes_for_user
delegate_attributes_for_account
delegate_errors_for_user unless #user.valid?
delegate_errors_for_account unless #account.valid?
# Have any errors been added by validating user and account?
if !errors.any?
persist!
true
else
false
end
end
private
def delegate_attributes_for_user
#user = User.new do |user|
user.name = name
user.email = email
user.password = password
user.password_confirmation = password
end
end
def delegate_attributes_for_account
#account = Account.new do |account|
account.name = company_name
end
end
def delegate_errors_for_user
errors.add(:name, #user.errors[:name].first) if #user.errors[:name].present?
errors.add(:email, #user.errors[:email].first) if #user.errors[:email].present?
errors.add(:password, #user.errors[:password].first) if #user.errors[:password].present?
end
def delegate_errors_for_account
errors.add(:company_name, #account.errors[:name].first) if #account.errors[:name].present?
end
def persist!
#user.save!
#account.save!
create_admin_membership
end
def create_admin_membership
#membership = Membership.create! do |membership|
membership.user = #user
membership.account = #account
membership.admin = true
end
end
end
An excellent read on form objects (and source for my work) is this CodeClimate blog post on Refactoring.
In all, I prefer this approach vastly over using accepts_nested_attributes_for, though there might be even greater ways out there. Let me know if you find one!
===
Edit: Added the referenced models and their associations for better understanding.
class User < ActiveRecord::Base
# Memberships and accounts
has_many :memberships
has_many :accounts, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :account
end
class Account < ActiveRecord::Base
# Memberships and members
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
has_many :admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => true }
has_many :non_admins, through: :memberships,
source: :user,
conditions: { 'memberships.admin' => false }
end
This structure in the model is modeled alongside saucy, a gem by thoughtbot. The source is not on Github AFAIK, but can extract it from the gem. I've been learning a lot by remodeling it.
If you don't want to change the registration controller, one way is to use the ActiveRecord callbacks
class User < ActiveRecord::Base
after_create :create_designer
private
def create_designer
Designer.create(user_id: self.id)
end
end
Currently I'm using Rolify & CanCan to manage roles and abilities in my Rails 3 app. My question is: How can I get a user to have a role by default on creation? for example, if I have a "user" role, ¿How can I make all the users that register in my app have a user Role by default? My Ability.rb has this code:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :user
can :update, User, :id => user.id
end
end
end
My User Model has this one:
class User < ActiveRecord::Base
rolify
authenticates_with_sorcery!
attr_accessible :username, :email, :password, :password_confirmation
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :username
validates_uniqueness_of :username
validates_presence_of :email
validates_uniqueness_of :email
end
The Role Model This One:
class Role < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :users_roles
belongs_to :resource, :polymorphic => true
end
And From the UsersController we have:
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to users_path, :notice => "Tu usuario se ha guardado"
else
render "new"
end
end
Finally the Rolify Migration is this one:
class RolifyCreateRoles < ActiveRecord::Migration
def change
create_table(:roles) do |t|
t.string :name
t.references :resource, :polymorphic => true
t.timestamps
end
create_table(:users_roles, :id => false) do |t|
t.references :user
t.references :role
end
add_index(:roles, :name)
add_index(:roles, [ :name, :resource_type, :resource_id ])
add_index(:users_roles, [ :user_id, :role_id ])
end
end
Now, I can assign roles manually from the rails console by using:
1 User.all
2 User.find(id)
3 User.add_role(:role)
But how can I assign automatically a default role when every user it's created?
Thanks!
You can use an active record callback to assign the role after the user is created. Something like
class User < ActiveRecord::Base
after_create :assign_default_role
def assign_default_role
add_role(:role)
end
end
Note that there's also an after_save callback but it's called EVERY time the user is saved. So if you edit the user and save it would try to add the role again. That's why I'm using the after_create callback instead.
You'd better check if a role is assigned before add_role. so I prefer:
class User < ActiveRecord::Base
after_create :assign_default_role
def assign_default_role
add_role(:normal) if self.roles.blank?
end
end
Forget it, Just had to add:
#user.add_role(:user)
in my create action right after the #user = User.new(params[:user]) line.
Figured it out by myself... I'm still learning :)
Thanks!
after_create :default_role
private
def default_role
self.roles << Role.find_by_name("user")
self.save
end
How can i use scoping with active admin & cancan.
I have admin users & those have (has_one) relation with institution
and institution has many profiles
Now when admin user login then i want display all profiles which has same institution.
Doesn't find following link much helpful.
http://activeadmin.info/docs/2-resource-customization.html#scoping_the_queries
if you just do simply this, do you get a problem?
# ability.db
def initialize(user)
case
# ...
when user.super_admin?
can :manage, :all
when user.admin?
can :manage, Profile, :institution_id => user.institution.id
#
# ...
end
this will allow: Profile.accessible_by(current_user), which here is same as current_user.profiles
class AdminUser
has_one :institution
has_many :profiles, :through => :institution
end
ActiveAdmin.register Profile do
scope_to :current_user #here comes the variable which set in initializer
end
if you want superadmin to access all posts, you can use the :association_method option
ActiveAdmin.register Profile do
scope_to :current_user, :association_method => :admin_profiles
end
# in class User
def admin_profiles
if super_admin?
Profile.unscoped
else
profiles
end
end
A tricky solution could generalize this and use a delegator class as proxy to unscope all models for superadmins. i can spell out on request.
I have two Models, Events and Users that share a many to many association. A user could be a Admin, Manager or a Producer.
Only a producer that belongs to a event should be able to read that event. I tried to apply that restriction on the ability model but it's fails and every producer can read all events. What am I doing wrong?
class Event < ActiveRecord::Base
has_and_belongs_to_many :producers, :class_name => "User", :join_table => "events_producers"
end
class CreateEventUserJoinTable < ActiveRecord::Migration
def self.up
create_table :events_producers, :id => false do |t|
t.integer :event_id
t.integer :user_id
end
end
def self.down
drop_table :events_producers
end
end
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new() # Guest user
if user.role? :manager
can :manage, :all
elsif user.role? :admin
can :read, Event
can :update, Event
can :create, Event
elsif user.role? :producer
can :read, Event do |event|
event.try(:producers).include?(user)
end
end
end
end
Bit old your question but without block-usage you can define the above condition like that:
can :read, Event, :producers => {:user_id => user.id}
In addition with that line, it should not be needed to ask if the user has a producer role.
The problem is that conditions in an ability block aren't used when its a class-based call, e.g. index actions. See https://github.com/ryanb/cancan/wiki/Defining-Abilities-with-Blocks for clarification.
For index actions, you must define the restrictions outside the block.