aschmid
aschmid

Reputation: 229

rails devise undefined method username when I want to use it with cancancan

I want to achieve a role based authorisation in rails. for login I use the gem devise, which works perfect. but after I am including some code of cancancan like in this tutorial I get an error undefined method username and when I remove username I get an error undefined method email...

here is my code:

ability.rb (to manage what a user with a specific role can do)

    class Ability

  include CanCan::Ability

  def initialize(user)

      user ||= User.new

     if user.role?(:admin)
       can :manage, :all
     elsif user.role?(:mitarbeiter)
       can :manage, :documents
       can :manage, :entries
     end

  end

end

a part of my user.rb

      ROLES = {0 => :admin, 1 => :mitarbeiter}

  attr_reader :role :

  def initialize(role_id = 0)
    @role = ROLES.has_key?(role_id) ? ROLES[role_id] : ROLES[0]

  end

  def role?(role_name)
    role == role_name
  end

a part of my application_controller.rb

  protect_from_forgery with: :exception

  check_authorization

  rescue_from CanCan::AccessDenied do |exception|
    flash[:warning] = exception.message
    redirect_to root_path
  end

  private

  def current_user
    User.new(session[:id])
  end

  helper_method :current_user

I really can't get whats wrong... I thought that I have to set @user like @role in user.rb but it doesn't helped

Upvotes: 1

Views: 272

Answers (2)

aschmid
aschmid

Reputation: 229

I found a solution for that, And the role_id get stored in the database correctly too. here my solution:

my new ability.rb (not finish only for testing functionality)

    class Ability

  include CanCan::Ability

  def initialize(user)

     user ||= User.new

     if user.has_role? :admin
       can :manage, :all
     elsif user.has_role? :mitarbeiter
       can :manage, :documents
       can :manage, :entries
     else
        can :read, :all
     end

  end

end

new user.rb

      ROLES = %i[admin mitarbeiter]

  def roles=(roles)
    roles = [*roles].map { |r| r.to_sym }
    self.role_id = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
  end

  def roles
    ROLES.reject do |r|
      ((role_id.to_i || 0) & 2**ROLES.index(r)).zero?
    end
  end

  def has_role?(role)
    roles.include?(role)
  end

new application_controller.rb part

  def configure_permitted_parameters
    added_attrs = [:username, :email, :password, :password_confirmation, :remember_me, roles: [] ]
    devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
    devise_parameter_sanitizer.permit :account_update, keys: added_attrs
  end

here a I give the role to a user views/devise/registrations/new.html.erb

    <% for role in User::ROLES %>
      <%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
      <%= label_tag "user_roles_#{role}", role.to_s.humanize %><br />
    <% end %>
    <%= hidden_field_tag "user[roles][]", "" %>

I also removed some gem(paperclip, roo, rolify) that maybe were an issue I got this in my Gemfile:

    gem 'devise', github: 'plataformatec/devise'
    gem 'cancancan'

Upvotes: 0

maicher
maicher

Reputation: 2745

I suggest removing the User#initialize method and attr_reader :role declaration and create own role method as follows:

def role
  @role ||= ROLES.has_key?(role_id) ? ROLES[role_id] : ROLES[0]
end

def role?(role_name)
  role == role_name
end

This way you achieve what you intended, but without overriding ActiveRecord's User#initialize method.

Above code assumes, that role_id is stored in database in role_id column.

Upvotes: 2

Related Questions