Search code examples
ruby-on-railsrubyruby-on-rails-5cancan

undefined method `admin?' with Cancan


I have a problem finding out how to check for admin rights in CanCanCan.

With

  • if user.admin? I get undefined method
  • if user.is? I get undefined method
  • if user.has_attribute?(:admin)
  • if user.user_type == "admin" I get undefined method

I had some hopes with has_attribute, but it does not help, even if I get no alert. A puts 'hey' proves it in the console.

I started learning Rails a month ago and I experienced some limitations due to windows. Is it possible that my problem occurs because of windows?

On the other hand, if user.present? works and it gives some hopes again.

My user model:

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :posts, dependent: :destroy

end

And the DB fields

  create_table "active_admin_comments", force: :cascade do |t|
    t.string "namespace"
    t.text "body"
    t.string "resource_type"
    t.integer "resource_id"
    t.string "author_type"
    t.integer "author_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["author_type", "author_id"], name: "index_active_admin_comments_on_author_type_and_author_id"
    t.index ["namespace"], name: "index_active_admin_comments_on_namespace"
    t.index ["resource_type", "resource_id"], name: "index_active_admin_comments_on_resource_type_and_resource_id"
  end

  create_table "admin_users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_admin_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_admin_users_on_reset_password_token", unique: true
  end

  create_table "posts", force: :cascade do |t|
    t.string "title"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "image_file_name"
    t.string "image_content_type"
    t.integer "image_file_size"
    t.datetime "image_updated_at"
    t.integer "author_id"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.string "current_sign_in_ip"
    t.string "last_sign_in_ip"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["email"], name: "index_users_on_email", unique: true
    t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
  end

From application_controller.rb

def access_denied(exception)
    respond_to do |format|
      format.json { head :forbidden, content_type: 'text/html' }
      format.html { redirect_to main_app.root_url, notice: exception.message }
   end
end 

EDIT

I thought for a while that the code suggested by @mrzasa would bring a solution as I had no more alert. This was because of my ability.rb :

if user.present?
  if user.admin?
    puts 'hey admin'
    can :manage, :all
  end
  can :read, all
  can :manage, Post, :author_id => user.id
    puts 'hey user'
end

If I comment # if user.present? the alert undefined method 'admin?'comes back again. A proof that user.present works, but here to say that there is no user, until I log in outside of the admin panel as a user and then I can see the puts in the console. But I can't perform any action, unless I state can :manage, :all to ANY user.

At this stage, I've added user ||= User.new to create an instance of user before checking for an admin. Even if I allow any visitor to log in as an admin, user.admin? is never verified, unless I set def admin? to true in user.rb

I see that many people using Cancancan define roles instead. Maybe I should go for it...

EDIT 2

It works! I worked on it again from the install of Cancancan to the point where I was with the additions of @mrzasa. This time, active admin understands admin? from the class AdminUser which was not the case yesterday. The beautiful thing is that I did not change any line of code, except commenting # user ||= User.new to get the expected results.


Solution

  • It seems that you have two separate models - one for regular users (users table) and another one for admin (admin_users table). You can add admin? method to both of them - for users returning false and for admin - true.

    class User < ApplicationRecord
      # ...
      def admin?
        false
      end
    end
    
    class AdminUser < ApplicationRecord
      # ...
      def admin?
        true
      end
    end