I've had great success using the state_machine and love the class methods it dynamically creates through just few lines of code.
However, I'm not sure how to proceed with the system I'm creating. I'm currently developing a system where users have many roles. So it's not a simple as having a user whose state goes from unconfirmed to confirmed and then potentially to admin.
A user now has many roles, and can be a potential, a cyclist, a coorinator, a manger, a forum admin, a store admin, a super admin and a fundraiser.
So the hierarchy goes like this:
superadmin
forum admin, store admin
cyclist, coordinator, manger, fundraiser
potential
However, one state machine won't cut it here, because it's completely possible that one user can have all of the above roles at the same time.
I'm implementing my own class methods like this to sort of emulate state-machine somewhat:
class User < ActiveModel::Base
has_many :jobs
has_many :roles, through: :jobs
def role_array
self.roles.pluck(:role)
end
def has_role?(role)
role_array.include?(role)
end
# checking
def is_superadmin?
role_array.include?('superadmin')
end
# changing
def add_role(role)
self.update_attributes(accepted_at: Time.now) if self.is_only_potential?
self.user_roles.create(role_id: Role.find_by(role: role).id ) if !self.has_role?(role)
end
def remove_role(role)
self.user_roles.find_by( role_id: Role.find_by(role: role).id ).destroy if self.has_role?(role)
end
def make_superadmin!
add_role('superadmin')
end
def denounce_superadmin!
remove_role('superadmin')
end
end
And it's just a bit of a faff. So my questions are:
1) Am I doing it right? How would you handle users with multiple roles?
2) Even if I am doing it right, I'd like to create a state_machine-esque DSL, so when I need to create a new role, let's say 'runner', I can just do something like this in my model:
class User < ActiveModel::Base
has_many :jobs
has_many :roles, through: :jobs
multiroles initial: :potential do
roles [:superadmin, :forum_admin, :store_admin, :cyclist, :coordinator, :manager, :fundraiser, :potential]
# dynamically creates the above methods for getting and setting for all roles
end
How should I create that multiroles method? Inside lib
? ready to be packaged off as my first Gem?
I have no idea how to dynamically create methods, but I'd like to start :)
Just a thought, maybe the multiroles
method could dynamically get all the roles via Roles.all
and automatically add the above methods! Maybe even take care of the has_many :jobs
has_many :roles, through: :jobs
Also, how should I be authenticating these roles? I'm currently doing this in a before block in my controllers:
def only_superadmins
redirect_to root_url if !current_user.has_role?('superadmin')
end
I also have a bunch of these methods in my application controller, only_superadmins
, only_cyclists
ect and I call them via the before_method
method in various sub-controllers.
Is this okay? Should I be using cancan or something?
If I am doing it right, I'm wondering how I should dynamically create these methods with my Gem. I'm thinking something along these lines:
class panel_controller < ApplicationController
allowed_roles [:super_admin, :forum_admin, :store_admin]
end
and the allowed_roles method would create these methods
def allowed_roles(role_array)
role_array.each do |role|
define "only_#{role.to_s}s" do |arg|
redirect_to root_url if !current_user.has_role?(arg.to_s)
end
end
end
So that would programatically create these methods:
def only_super_admins
redirect_to root_url if !current_user.has_role?('super_admin')
end
def only_forum_admins
redirect_to root_url if !current_user.has_role?('forum_admin')
end
def only_store_admins
redirect_to root_url if !current_user.has_role?('store_admin')
end
While I don't see why that wouldn't work, that doesn't strike me as too efficient.
Maybe allowed_roles
should look like this:
def allowed_roles(wanted_roles)
redirect_to root_url unless (current_user.role_array & wanted_roles).empty? # it's ONLY empty when any of the current_user roles exists in the wanted_roles array
end
I just want some pointers really :)
How do I create a gem to make the allowed_roles
method available to the controllers and multiroles
available to the user model?
Can cancan
manage multiple roles like this? Should I just be using that?