Search code examples
ruby-on-rails-4devisesingle-table-inheritancestimultiple-users

Multiple Devise models with unique attributes


Short explanation:

I seek architectural advice and help in implementing multiple Devise models in a single app.

More detailed explanation:

My application needs to perform the following behavior:

There are 3 types of users (Partner, Attendee, Speaker) which have some common fields and some unique ones (also, the fields might have different permissions, i.e. Attendee must have a username whereas the Speaker might have it but they don't have to necessarily fill this field in). And moreover, the different user models must have different associations with other tables in the db.

The users need to be able to log in through single log-in form, but the sign-up forms should be different.

So, my first thought was that I should divide the users by roles using Pundit or declarative_authorization or something, but the users don't really have different roles (i.e. permissions) in the app, they rather have different behavior, they might see different content and stuff, so I continued thinking.

My second though was implementing STI and after reading several articles about it, I tried to do that in code.

I generated a Devise User model by doing rails g devise User and after that ran rails g model Attendee and the same for other two users.

Then, I inherited my models from User:

class Attendee < User
end

My User migration looks like this:

create_table :users do |t|
  t.string :first_name
  t.string :last_name
  t.string :type

  # Devise stuff ...
  ..................

  t.timestamps null: false
end

And other migrations are like this:

create_table :attendees do |t|
  t.string :username
  t.string :company_name
  t.boolean :subscription

  t.timestamps null: false
end

Now I realize it was wrong to create separate tables. I had to put all the possible fields into the User table, is that correct? Because now when I try to create any new Attendee or Speaker or Partner in rails console, all of these three models have the exact same fields, those the User model has.

But if I add all the possible fields in the User model, how would I perform validations on field presence?


I've read quite a few articles and questions here on SO, but still can't really wrap my head around how to implement all that.

Anyway, is that a correct way to do what I need?

Could anybody explain me in a detailed way how I should implement this kind of behavior and functionality from start to finish, and how should I work with the models after having implemented them?

PS: here's the history of my migrations and the whole github repo


Update

Remembered another issue that stopped my from doing just role separation:

How should I sign up the different users with different sign-up forms? Different routes? I cannot make the user choose their role from the combobox.


Solution

  • You can create conditional validation rules based on the role, but the first place you need to address this is in the new/edit User form, only showing the allowed fields dynamically based on the role:

    class User < ActiveRecord::Base
      validates :company, presence: true, if: :is_company?
    
      def is_company
       # check for the role
       self.role == 'company'
      end
    end
    

    UPDATE: You can pass an extra parameter to the same registration form and use that to differentiate the type of form you display. That's the nicest way. You can also create separate methods in the UserController -> def register_user, def register_company, def register_xxxx