Search code examples
ruby-on-railsuuidrolify

Getting undefined method `add_role' with Rails 6, Rolify using UUID


I just added rolify to my Rails 6 app, which is using UUID for all tables.

After the initial errors I found that I need to change my migrations slightly to deal with the UUID. I am also using a model named 'Person' instead of the default 'User'. I have tried restarting my server (several times) but I still get the following:

2.6.2 :002 > p.add_role :admin

Traceback (most recent call last): 1: from (irb):2 NoMethodError (undefined method `add_role' for #Person::ActiveRecord_Relation:0x00007fe37ec29408>) 2.6.2 :003 >

Here are the applicable models:

role.rb

class Role < ApplicationRecord
    has_and_belongs_to_many :people, :join_table => :people_roles


belongs_to :resource,
           :polymorphic => true,
           :optional => true


validates :resource_type,
          :inclusion => { :in => Rolify.resource_types },
          :allow_nil => true

scopify
end

person.rb

class Person < ApplicationRecord
rolify

def full_name
    "#{self.last_name}, #{self.first_name}"
end
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :trackable
end

applicable schema:

create_table "people", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "last_name"
t.string "first_name"
t.string "gender"
t.uuid "personable_id"
t.string "personable_type"
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.inet "current_sign_in_ip"
t.inet "last_sign_in_ip"
t.integer "failed_attempts", default: 10, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "type"
t.index ["email"], name: "index_people_on_email", unique: true
t.index ["reset_password_token"], name: "index_people_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_people_on_unlock_token", unique: true
 end

create_table "people_roles", id: false, force: :cascade do |t|
    t.uuid "person_id"
    t.uuid "role_id"
    t.index ["person_id", "role_id"], name: "index_people_roles_on_person_id_and_role_id"
  end

create_table "roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.uuid "resource_id"
    t.string "resource_type"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
    t.index ["name"], name: "index_roles_on_name"
  end

Any help on this would be much appreciated!


Solution

  • Your problem is that p is not a Person but a collection of Person objects as indicated by ActiveRecord_Relation.

    If you would like all of those people to have the admin role then it would be

    p.each {|person| person.add_role :admin } 
    

    Otherwise you will need to change the code that populates p to return a single Person object. For instance if you are populating p as

    p = Person.where(uuid: some_uuid) 
    

    Then change this to

    p = Person.find_by(uuid: some_uuid) # or Person.find_by_uuid(some_uuid)  
    

    find_by will return an instance of the class (or nil none are found) by selecting the first record where the condition is true. Since uuid by virtue is unique this should not be an issue.