2

I am creating an app with 3 types of Users which could end up with a 4th...

  • Admin
  • Provider
  • Member (patient)

The tricky part is each share some common attributes such as 'first_name' and 'last_name', but will have more attributes that differ. If they all share the same 'User' table, I will have too many columns 'nil' which doesn't feel like good practice.

What would be a good way to go about this? Is there a way to have role-based model relationships?

My plan is to use Devise and Pundit, Postgres for DB.

shroy
  • 918
  • 2
  • 10
  • 24

3 Answers3

1

You should create a separate User model. And those three model (Admin, Provider, Member) where everyone should

belongs_to :user

Then you can easily have the first_name or last_name like,

member.user.first_name

or

provider.user.last_name

etc.

So you have to create a user when you are creating an Admin or Provider or Member. Devise can use User model. For role base things, you can use

if current_user.admin?
  do something
end

I hope that helps.

  • I'm definitely leaning towards this approach. I read in another similar forum that this caused the user_session to reset more often then attended. Have you tried this approach before? If so, did you run into any issues like that? – shroy May 19 '15 at 05:48
  • Yes, I used this approach. But I haven't encountered such issue yet. May be that's not because of this approach I guess. – Rifatul Islam Chayon May 19 '15 at 05:57
  • If you go with this approach, checkout http://stackoverflow.com/a/11956734/673079 for a good explanation of using a polymorphic association for user.role. – psparrow May 19 '15 at 06:02
  • Thanks psparrow. The User data I'm retrieving is very sensitive and unique so I'll definitely need to have them on separate tables. The polymorphic association was very appealing but i found another way which I'll post as the answer. – shroy May 19 '15 at 18:31
1

Use a single model/table for all user types and a role attribute for admin/provider/member. Then, you can use the same sign-in form for all of them and you won't have to add more tables and models each time you add a new role.

I would recommend checking out the CanCanCan gem at https://github.com/cancancommunity/cancancan. You can authorize user actions based on the role.

# app/models/ability.rb
class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new

    if user.role == "admin"
      can :manage, :all
    elsif user.role == "provider"
      can :manage, SomeModel, user_id: user.id
    elsif user.role == "member"
      can :read, SomeModel
      can :read, SomeOtherModel
    end
  end
end

If you want different form fields for each role, simply use a partial template for those fields.

# app/views/users/_form.html.erb
<%= form_for @user do |f| %>
  <%= f.text_field :first_name %>
  <%= f.text_field :last_name %>
  <%= render "#{@user.role}_fields", f: f %>
<% end %>

# app/views/users/_admin_fields.html.erb
<%= f.text_field :some_field_for_admins_only %>
psparrow
  • 9,808
  • 1
  • 17
  • 11
  • My only concern with this is that all attributes would be inside the User model which would leave me with many 'nil' columns. Providers have about double the amount of attributes then a Member. – shroy May 19 '15 at 05:46
  • There's always trade-offs with a framework like Rails. I would weigh having some empty database columns against the overhead of having to create new models and tables each time you add a role. Also, if you're using devise and you want all users to share the same sign-up, sign-in, and password reset forms, you'll have some work on your hands connecting it it all together. – psparrow May 19 '15 at 05:48
  • If you have a lot of fields for some roles, you can extract the different attributes into separate role or profile models for each user type. Just don't have multiple User models for authentication or you're in for some head-scratching! – psparrow May 19 '15 at 06:04
1

After a great deal of research into using Polymorphic Associations, a friend suggested a gem that provided an even simpler approach that simulates Multi-Table Inheritance via Modules.

The gem is called Active_Record-Acts As.
https://github.com/hzamani/active_record-acts_as

My set up will looks something similar to this:

class User < ActiveRecord::Base
  actable

  validates_presence_of :first_name, :last_name

  def name
    "#{first_name} #{last_name}"
  end
end

class Member < ActiveRecord::Base
  acts_as :user
end

class Provider < ActiveRecord::Base
  # In case you don't wish to validate
  # this model against User

  acts_as :user, validates_actable: false
end

class Hospital < ActiveRecord::Base
  has_many :users
end

Then I need to migrate a foreign key..

change_table :products do |t|
  t.integer :actable_id
  t.string  :actable_type
end

Creating users becomes easy..

Member.create(first_name: "Bob", last_name: "Miller")

I'll base the authentication on the ":actable_type".

shroy
  • 918
  • 2
  • 10
  • 24