Art Art - 2 months ago 23
Ruby Question

Convinient way to implement user roles in my app

Thanks for reading!

I'm currently working on my new app and searching for the best way to implement next feature

By scenario I need to implement "As a user a have role in the location"

WHAT I HAVE DONE:



Scenario:
When user adds new location to the profile
one of the requred fields is "role". That could be "guest", "manager" or "seller". What's the best way to accomplish his in the model side?

I accomplished this with has_many_through assosiation

CONTROLLER:

def create
@location = Location.new(location_params)
@location.profiles << current_user.profile
#set user role
current_user.profile.profile_location_throughs.where(location_id: location.id).set_role(params[:location][:role])
respond_to do |format|
if @location.save
....
end
end
end


MODELS:

class Profile < ActiveRecord::Base do
has_many :profile_location_throughs
has_many :locations, through: :profile_location_throughs
end

class Location < ActiveRecord::Base do
has_many :profile_location_throughs
has_many :locations, through: :profile_location_throughs
end

class ProfileLocationThrough < ActiveRecord::Base
# with boolean fields: manager, seller, guest

belongs_to :location
belongs_to :profile

def set_role(role)
case role
when "guest"
self.guest = true
when "seller"
self.seller = true
when "manager"
self.manager = true
end
end

end


=====

QUESTION:
Could you suggest more beatiful way to implement his feature?

max max
Answer

There are several ways to do role based authorization.

The simplest way is by adding a enum to the users themselves:

class Profile < ApplicationRecord
  enum role: [:guest, :seller, :manager]
end

This is pretty limited though as it only allows "global" roles. If you want resource scoped roles you need a join table.

class Profile < ApplicationRecord
  has_many :roles
  has_many :locations, through: :roles

  def has_role?(role, location = nil)
    self.roles.exists?( { name: role, location: location}.compact )
  end

  def add_role(role, location)
    self.roles.create!( { name: role, location: location } )
  end
end

class Role < ApplicationRecord
  belongs_to :profile
  belongs_to :location
end

class Location < ApplicationRecord
  has_many :roles
  has_many :profiles, through: :roles
end

In this example we are simply using a string for the roles.name column. You could also use an enum if the kinds of roles are limited. If you want to use the same Role model (no pun intended) to scope roles on different kinds of resources you can use a polymorphic belongs_to relationship.

class Role < ApplicationRecord
  belongs_to :profile
  belongs_to :resource, polymorphic: true
end

class Location < ApplicationRecord
  has_many :roles, as: :resource
  has_many :profiles, through: :roles
end

class OtherThing < ApplicationRecord
  has_many :roles, as: :resource
  has_many :profiles, through: :roles
end

Note that roles are just one part of an authentication solution. You would combine this with a authorization lib such as Pundit or CanCanCan which defines the rules about what role gets to do what and enforces those rules.

Comments